dicom_viewer/dcm/
metadata.rs1use anyhow::{Context, Result};
4use dicom::core::dictionary::{DataDictionary, DataDictionaryEntryRef};
5use dicom::core::header::Header;
6use dicom::core::{Tag, VR};
7use dicom::object::mem::InMemElement;
8use dicom::object::OpenFileOptions;
9use dicom_dictionary_std::{tags, StandardDataDictionary};
10use std::path::Path;
11
12#[derive(Debug, Clone)]
19pub struct TagRow {
20 pub tag: Tag,
22 pub vr: String,
24 pub name: String,
26 pub value: String,
29}
30
31impl TagRow {
32 pub fn tag_label(&self) -> String {
34 format!("({:04X},{:04X})", self.tag.0, self.tag.1)
35 }
36 pub fn group_label(&self) -> String {
39 match self.tag.0 {
40 0x0002 => "0002 — File Meta",
41 0x0008 => "0008 — Identifying",
42 0x0010 => "0010 — Patient",
43 0x0018 => "0018 — Acquisition",
44 0x0020 => "0020 — Image",
45 0x0028 => "0028 — Image Pixel",
46 0x0032 => "0032 — Study",
47 0x0038 => "0038 — Visit",
48 0x0040 => "0040 — Procedure",
49 0x0054 => "0054 — Nuclear Medicine",
50 0x0070 => "0070 — Presentation",
51 0x0072 => "0072 — Hanging Protocol",
52 0x0088 => "0088 — Storage",
53 0x0400 => "0400 — Digital Signature",
54 0x2000 => "2000 — Film",
55 0x3006 => "3006 — RT Structure",
56 0x300A => "300A — RT Plan",
57 0x300C => "300C — RT Relationship",
58 0x300E => "300E — RT Approval",
59 0x4000 => "4000 — Text",
60 0x7FE0 => "7FE0 — Pixel Data",
61 g if g & 1 == 1 => "Private group",
62 _ => "Other",
63 }
64 .to_string()
65 }
66}
67
68pub fn collect_tags(path: &Path) -> Result<Vec<TagRow>> {
76 let obj = OpenFileOptions::new()
77 .read_until(tags::PIXEL_DATA)
78 .open_file(path)
79 .with_context(|| format!("open {}", path.display()))?;
80
81 let mut rows: Vec<TagRow> = Vec::new();
82 for elem in obj.iter() {
83 let tag: Tag = Header::tag(elem);
84 let vr_label = format!("{:?}", elem.vr());
85 let name = lookup_name(tag);
86 let value = format_value(elem);
87 rows.push(TagRow {
88 tag,
89 vr: vr_label,
90 name,
91 value,
92 });
93 }
94 rows.sort_by_key(|r| (r.tag.0, r.tag.1));
95 Ok(rows)
96}
97
98fn lookup_name(tag: Tag) -> String {
99 let dict = StandardDataDictionary;
100 let entry: Option<&DataDictionaryEntryRef<'static>> = dict.by_tag(tag);
101 match entry {
102 Some(e) => e.alias.to_string(),
103 None => "(unknown)".to_string(),
104 }
105}
106
107fn format_value(elem: &InMemElement) -> String {
108 const MAX_LEN: usize = 240;
109
110 if elem.vr() == VR::SQ {
111 let count = elem.items().map(<[_]>::len).unwrap_or(0);
112 return format!("<sequence, {count} item(s)>");
113 }
114
115 let raw = match elem.value().to_str() {
116 Ok(s) => s.to_string(),
117 Err(_) => {
118 return "<binary value>".to_string();
119 }
120 };
121
122 let cleaned: String = raw
123 .chars()
124 .map(|c: char| if c.is_control() && c != '\n' { ' ' } else { c })
125 .collect();
126 let trimmed = cleaned.trim_end_matches('\0').trim().to_string();
127 if trimmed.chars().count() > MAX_LEN {
128 let cut: String = trimmed.chars().take(MAX_LEN).collect();
129 format!("{cut}…")
130 } else {
131 trimmed
132 }
133}