use std::path::PathBuf;
use clap::Parser;
use dicom_core::{
value::{PixelFragmentSequence, PrimitiveValue},
DataElement, DicomValue, VR,
};
use dicom_dictionary_std::tags;
use dicom_object::{open_file, DefaultDicomObject, FileMetaTableBuilder};
use image::DynamicImage;
type Result<T, E = snafu::Whatever> = std::result::Result<T, E>;
#[derive(Debug, Parser)]
#[command(version)]
struct App {
dcm_file: PathBuf,
img_file: PathBuf,
#[arg(short = 'o', long = "out")]
output: Option<PathBuf>,
#[arg(long = "transfer-syntax", alias = "ts")]
transfer_syntax: Option<String>,
#[arg(long)]
encapsulate: bool,
#[arg(long)]
retain_implementation: bool,
#[arg(short = 'v', long = "verbose")]
verbose: bool,
}
fn main() {
tracing::subscriber::set_global_default(tracing_subscriber::FmtSubscriber::new())
.unwrap_or_else(|e| {
eprintln!("{}", snafu::Report::from_error(e));
});
let App {
dcm_file,
img_file,
output,
encapsulate,
transfer_syntax,
retain_implementation,
verbose,
} = App::parse();
let output = output.unwrap_or_else(|| {
let mut path = dcm_file.clone();
path.set_extension("new.dcm");
path
});
let mut obj = open_file(&dcm_file).unwrap_or_else(|e| {
tracing::error!("{}", snafu::Report::from_error(e));
std::process::exit(-1);
});
if encapsulate {
inject_encapsulated(&mut obj, img_file, verbose)
} else {
inject_image(&mut obj, img_file, verbose)
}
.unwrap_or_else(|e| {
tracing::error!("{}", snafu::Report::from_error(e));
std::process::exit(-2);
});
let class_uid = obj.meta().media_storage_sop_class_uid.clone();
let mut meta_builder = FileMetaTableBuilder::new()
.transfer_syntax("1.2.840.10008.1.2.1")
.media_storage_sop_class_uid(class_uid);
if let Some(ts) = transfer_syntax {
meta_builder = meta_builder.transfer_syntax(ts);
}
if retain_implementation {
let implementation_class_uid = &obj.meta().implementation_class_uid;
meta_builder = meta_builder.implementation_class_uid(implementation_class_uid);
if let Some(implementation_version_name) = obj.meta().implementation_version_name.as_ref() {
meta_builder = meta_builder.implementation_version_name(implementation_version_name);
}
}
let obj = obj
.into_inner()
.with_meta(meta_builder)
.unwrap_or_else(|e| {
tracing::error!("{}", snafu::Report::from_error(e));
std::process::exit(-3);
});
obj.write_to_file(&output).unwrap_or_else(|e| {
tracing::error!("{}", snafu::Report::from_error(e));
std::process::exit(-4);
});
if verbose {
println!("DICOM file saved to {}", output.display());
}
}
fn inject_image(obj: &mut DefaultDicomObject, img_file: PathBuf, verbose: bool) -> Result<()> {
let image_reader = image::ImageReader::open(img_file).unwrap_or_else(|e| {
tracing::error!("{}", snafu::Report::from_error(e));
std::process::exit(-1);
});
let img = image_reader.decode().unwrap_or_else(|e| {
tracing::error!("{}", snafu::Report::from_error(e));
std::process::exit(-1);
});
let color = img.color();
let bits_stored: u16 = match color {
image::ColorType::L8 => 8,
image::ColorType::L16 => 16,
image::ColorType::Rgb8 => 8,
image::ColorType::Rgb16 => 16,
_ => {
eprintln!("Unsupported image format {color:?}");
std::process::exit(-2);
}
};
update_from_img(obj, &img, verbose);
for tag in [
tags::NUMBER_OF_FRAMES,
tags::PIXEL_ASPECT_RATIO,
tags::SMALLEST_IMAGE_PIXEL_VALUE,
tags::LARGEST_IMAGE_PIXEL_VALUE,
tags::PIXEL_PADDING_RANGE_LIMIT,
tags::RED_PALETTE_COLOR_LOOKUP_TABLE_DATA,
tags::RED_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR,
tags::GREEN_PALETTE_COLOR_LOOKUP_TABLE_DATA,
tags::GREEN_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR,
tags::BLUE_PALETTE_COLOR_LOOKUP_TABLE_DATA,
tags::BLUE_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR,
tags::ICC_PROFILE,
tags::COLOR_SPACE,
tags::PIXEL_DATA_PROVIDER_URL,
tags::EXTENDED_OFFSET_TABLE,
tags::EXTENDED_OFFSET_TABLE_LENGTHS,
] {
obj.remove_element(tag);
}
let pixeldata = img.into_bytes();
obj.put(DataElement::new(
tags::PIXEL_DATA,
if bits_stored == 8 { VR::OB } else { VR::OW },
PrimitiveValue::from(pixeldata),
));
Ok(())
}
fn inject_encapsulated(
dcm: &mut DefaultDicomObject,
img_file: PathBuf,
verbose: bool,
) -> Result<()> {
let image_reader = image::ImageReader::open(&img_file).unwrap_or_else(|e| {
tracing::error!("{}", snafu::Report::from_error(e));
std::process::exit(-1);
});
let all_data = std::fs::read(img_file).unwrap_or_else(|e| {
tracing::error!("{}", snafu::Report::from_error(e));
std::process::exit(-2);
});
if let Ok(img) = image_reader.decode() {
update_from_img(&mut *dcm, &img, verbose);
}
dcm.put(DataElement::new(
tags::PIXEL_DATA,
VR::OB,
DicomValue::PixelSequence(PixelFragmentSequence::new_fragments(vec![all_data])),
));
Ok(())
}
fn update_from_img(obj: &mut DefaultDicomObject, img: &DynamicImage, verbose: bool) {
let width = img.width();
let height = img.height();
let color = img.color();
let (pi, spp, bits_stored): (&str, u16, u16) = match color {
image::ColorType::L8 => ("MONOCHROME2", 1, 8),
image::ColorType::L16 => ("MONOCHROME2", 1, 16),
image::ColorType::Rgb8 => ("RGB", 3, 8),
image::ColorType::Rgb16 => ("RGB", 3, 16),
_ => {
eprintln!("Unsupported image format {color:?}");
std::process::exit(-2);
}
};
if verbose {
println!("{width}x{height} {color:?} image");
}
obj.put(DataElement::new(
tags::PHOTOMETRIC_INTERPRETATION,
VR::CS,
PrimitiveValue::from(pi),
));
obj.put(DataElement::new(
tags::PRESENTATION_LUT_SHAPE,
VR::CS,
PrimitiveValue::from("IDENTITY"),
));
obj.put(DataElement::new(
tags::SAMPLES_PER_PIXEL,
VR::US,
PrimitiveValue::from(spp),
));
if spp > 1 {
obj.put(DataElement::new(
tags::PLANAR_CONFIGURATION,
VR::US,
PrimitiveValue::from(0_u16),
));
} else {
obj.remove_element(tags::PLANAR_CONFIGURATION);
}
obj.put(DataElement::new(
tags::COLUMNS,
VR::US,
PrimitiveValue::from(width as u16),
));
obj.put(DataElement::new(
tags::ROWS,
VR::US,
PrimitiveValue::from(height as u16),
));
obj.put(DataElement::new(
tags::BITS_ALLOCATED,
VR::US,
PrimitiveValue::from(bits_stored),
));
obj.put(DataElement::new(
tags::BITS_STORED,
VR::US,
PrimitiveValue::from(bits_stored),
));
obj.put(DataElement::new(
tags::HIGH_BIT,
VR::US,
PrimitiveValue::from(bits_stored - 1),
));
obj.put(DataElement::new(
tags::PIXEL_REPRESENTATION,
VR::US,
PrimitiveValue::from(0_u16),
));
}
#[cfg(test)]
mod tests {
use crate::App;
use clap::CommandFactory;
#[test]
fn verify_cli() {
App::command().debug_assert();
}
}