PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
dicom_dataset.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
11
12namespace kcenon::pacs::core {
13
14// ============================================================================
15// Element Access
16// ============================================================================
17
18auto dicom_dataset::contains(dicom_tag tag) const noexcept -> bool {
19 return elements_.contains(tag);
20}
21
23 auto it = elements_.find(tag);
24 if (it == elements_.end()) {
25 return nullptr;
26 }
27 return &it->second;
28}
29
30auto dicom_dataset::get(dicom_tag tag) const noexcept -> const dicom_element* {
31 auto it = elements_.find(tag);
32 if (it == elements_.end()) {
33 return nullptr;
34 }
35 return &it->second;
36}
37
38// ============================================================================
39// Convenience Accessors
40// ============================================================================
41
43 std::string_view default_value) const
44 -> std::string {
45 const auto* elem = get(tag);
46 if (elem == nullptr) {
47 return std::string{default_value};
48 }
49 return elem->as_string().unwrap_or(std::string{default_value});
50}
51
52// ============================================================================
53// Sequence Access
54// ============================================================================
55
56auto dicom_dataset::has_sequence(dicom_tag tag) const noexcept -> bool {
57 const auto* elem = get(tag);
58 return elem != nullptr && elem->is_sequence();
59}
60
61auto dicom_dataset::get_sequence(dicom_tag tag) const noexcept
62 -> const std::vector<dicom_dataset>* {
63 const auto* elem = get(tag);
64 if (elem == nullptr || !elem->is_sequence()) {
65 return nullptr;
66 }
67 return &elem->sequence_items();
68}
69
71 -> std::vector<dicom_dataset>* {
72 auto* elem = get(tag);
73 if (elem == nullptr || !elem->is_sequence()) {
74 return nullptr;
75 }
76 return &elem->sequence_items();
77}
78
80 -> std::vector<dicom_dataset>& {
81 auto* elem = get(tag);
82 if (elem != nullptr && elem->is_sequence()) {
83 return elem->sequence_items();
84 }
85 // Create or replace with empty sequence
87 return get(tag)->sequence_items();
88}
89
90// ============================================================================
91// Private Tag Access
92// ============================================================================
93
95 -> std::optional<std::string> {
96 const auto creator_tag = private_data_tag.private_creator_tag();
97 if (!creator_tag) {
98 return std::nullopt;
99 }
100 const auto* elem = get(*creator_tag);
101 if (elem == nullptr) {
102 return std::nullopt;
103 }
104 auto result = elem->as_string();
105 if (result.is_ok()) {
106 return result.value();
107 }
108 return std::nullopt;
109}
110
111auto dicom_dataset::get_private_block(std::string_view creator_id,
112 uint16_t group) const
113 -> std::vector<const dicom_element*> {
114 std::vector<const dicom_element*> result;
115
116 // Scan Private Creator elements (gggg,0010)-(gggg,00FF) for a match
117 for (uint16_t slot = 0x0010; slot <= 0x00FF; ++slot) {
118 const dicom_tag creator_tag{group, slot};
119 const auto* creator_elem = get(creator_tag);
120 if (creator_elem == nullptr) {
121 continue;
122 }
123 auto str_result = creator_elem->as_string();
124 if (str_result.is_err() || str_result.value() != creator_id) {
125 continue;
126 }
127
128 // Found the creator — collect all data elements in its block
129 const auto range = creator_tag.private_data_range();
130 if (!range) {
131 break;
132 }
133 const auto [first, last] = *range;
134 auto it = elements_.lower_bound(first);
135 while (it != elements_.end() && it->first <= last) {
136 result.push_back(&it->second);
137 ++it;
138 }
139 break;
140 }
141
142 return result;
143}
144
145auto dicom_dataset::set_private_element(std::string_view creator_id,
146 uint16_t group,
147 uint8_t element_offset,
149 std::string_view value)
150 -> std::optional<dicom_tag> {
151 // Group must be odd and > 0x0008 for private tags
152 if ((group & 1) == 0 || group <= 0x0008) {
153 return std::nullopt;
154 }
155
156 // Find existing block for this creator, or allocate a new slot
157 uint16_t block_number = 0;
158 bool found = false;
159
160 for (uint16_t slot = 0x0010; slot <= 0x00FF; ++slot) {
161 const dicom_tag creator_tag{group, slot};
162 const auto* creator_elem = get(creator_tag);
163 if (creator_elem == nullptr) {
164 // Empty slot — allocate if we haven't found a match yet
165 if (!found) {
166 block_number = slot;
167 found = true;
168 }
169 continue;
170 }
171 auto str_result = creator_elem->as_string();
172 if (str_result.is_ok() && str_result.value() == creator_id) {
173 // Creator already owns this slot
174 block_number = slot;
175 found = true;
176 break;
177 }
178 }
179
180 if (!found) {
181 return std::nullopt; // No available slots
182 }
183
184 // Ensure the Private Creator element exists
185 const dicom_tag creator_tag{group, block_number};
186 if (get(creator_tag) == nullptr) {
187 set_string(creator_tag, encoding::vr_type::LO, creator_id);
188 }
189
190 // Place the data element at (group, block_number << 8 | element_offset)
191 const auto data_element_num = static_cast<uint16_t>(
192 (block_number << 8) | element_offset);
193 const dicom_tag data_tag{group, data_element_num};
194 set_string(data_tag, vr, value);
195
196 return data_tag;
197}
198
199auto dicom_dataset::remove_private_block(std::string_view creator_id,
200 uint16_t group) -> size_t {
201 size_t removed = 0;
202
203 for (uint16_t slot = 0x0010; slot <= 0x00FF; ++slot) {
204 const dicom_tag creator_tag{group, slot};
205 const auto* creator_elem = get(creator_tag);
206 if (creator_elem == nullptr) {
207 continue;
208 }
209 auto str_result = creator_elem->as_string();
210 if (str_result.is_err() || str_result.value() != creator_id) {
211 continue;
212 }
213
214 // Found the creator — remove all data elements in its block
215 const auto range = creator_tag.private_data_range();
216 if (range) {
217 const auto [first, last] = *range;
218 auto it = elements_.lower_bound(first);
219 while (it != elements_.end() && it->first <= last) {
220 it = elements_.erase(it);
221 ++removed;
222 }
223 }
224
225 // Remove the creator element itself
226 elements_.erase(creator_tag);
227 ++removed;
228 break;
229 }
230
231 return removed;
232}
233
235 size_t removed = 0;
236 std::vector<dicom_tag> orphans;
237
238 for (const auto& [tag, elem] : elements_) {
239 if (!tag.is_private_creator()) {
240 continue;
241 }
242 // Check if any data elements exist in this creator's block
243 const auto range = tag.private_data_range();
244 if (!range) {
245 continue;
246 }
247 const auto [first, last] = *range;
248 auto it = elements_.lower_bound(first);
249 if (it == elements_.end() || it->first > last) {
250 orphans.push_back(tag);
251 }
252 }
253
254 for (const auto& tag : orphans) {
255 elements_.erase(tag);
256 ++removed;
257 }
258
259 return removed;
260}
261
263 std::vector<dicom_tag> missing_creators;
264
265 for (const auto& [tag, elem] : elements_) {
266 if (!tag.is_private_data()) {
267 continue;
268 }
269 const auto creator_tag = tag.private_creator_tag();
270 if (!creator_tag || !contains(*creator_tag)) {
271 missing_creators.push_back(tag);
272 }
273 }
274
275 return missing_creators;
276}
277
278// ============================================================================
279// Modification
280// ============================================================================
281
283 elements_.insert_or_assign(element.tag(), std::move(element));
284}
285
287 std::string_view value) {
289}
290
292 return elements_.erase(tag) > 0;
293}
294
295void dicom_dataset::clear() noexcept {
296 elements_.clear();
297}
298
299// ============================================================================
300// Iteration
301// ============================================================================
302
303auto dicom_dataset::begin() noexcept -> iterator {
304 return elements_.begin();
305}
306
307auto dicom_dataset::end() noexcept -> iterator {
308 return elements_.end();
309}
310
311auto dicom_dataset::begin() const noexcept -> const_iterator {
312 return elements_.begin();
313}
314
315auto dicom_dataset::end() const noexcept -> const_iterator {
316 return elements_.end();
317}
318
319auto dicom_dataset::cbegin() const noexcept -> const_iterator {
320 return elements_.cbegin();
321}
322
323auto dicom_dataset::cend() const noexcept -> const_iterator {
324 return elements_.cend();
325}
326
327// ============================================================================
328// Size Operations
329// ============================================================================
330
331auto dicom_dataset::size() const noexcept -> size_t {
332 return elements_.size();
333}
334
335auto dicom_dataset::empty() const noexcept -> bool {
336 return elements_.empty();
337}
338
339// ============================================================================
340// Utility Operations
341// ============================================================================
342
343auto dicom_dataset::copy_with_tags(std::initializer_list<dicom_tag> tags) const
344 -> dicom_dataset {
345 return copy_with_tags(std::span{tags.begin(), tags.size()});
346}
347
348auto dicom_dataset::copy_with_tags(std::span<const dicom_tag> tags) const
349 -> dicom_dataset {
350 dicom_dataset result;
351
352 for (const auto& tag : tags) {
353 const auto* elem = get(tag);
354 if (elem != nullptr) {
355 result.insert(*elem);
356 }
357 }
358
359 return result;
360}
361
363 for (const auto& [tag, element] : other) {
364 insert(element);
365 }
366}
367
369 for (auto& [tag, element] : other.elements_) {
370 insert(std::move(element));
371 }
372 other.clear();
373}
374
375} // namespace kcenon::pacs::core
auto cleanup_orphaned_creators() -> size_t
Remove orphaned Private Creator elements that have no data elements.
auto has_sequence(dicom_tag tag) const noexcept -> bool
Check if the dataset contains a sequence element with the given tag.
auto get(dicom_tag tag) noexcept -> dicom_element *
Get a pointer to the element with the given tag.
auto remove(dicom_tag tag) -> bool
Remove an element from the dataset.
auto set_private_element(std::string_view creator_id, uint16_t group, uint8_t element_offset, encoding::vr_type vr, std::string_view value) -> std::optional< dicom_tag >
Insert a private data element with automatic creator management.
void insert(dicom_element element)
Insert or replace an element in the dataset.
auto empty() const noexcept -> bool
Check if the dataset is empty.
storage_type::iterator iterator
Iterator type.
auto size() const noexcept -> size_t
Get the number of elements in the dataset.
auto cend() const noexcept -> const_iterator
Get const iterator past the last element.
auto validate_private_tags() const -> std::vector< dicom_tag >
Validate private tag relationships in this dataset.
auto get_sequence(dicom_tag tag) const noexcept -> const std::vector< dicom_dataset > *
auto end() noexcept -> iterator
Get iterator past the last element.
auto cbegin() const noexcept -> const_iterator
Get const iterator to the first element.
void clear() noexcept
Remove all elements from the dataset.
auto begin() noexcept -> iterator
Get iterator to the first element.
void merge(const dicom_dataset &other)
Merge elements from another dataset.
auto copy_with_tags(std::initializer_list< dicom_tag > tags) const -> dicom_dataset
Create a copy containing only the specified tags.
void set_string(dicom_tag tag, encoding::vr_type vr, std::string_view value)
Set a string value for the given tag.
auto get_private_block(std::string_view creator_id, uint16_t group) const -> std::vector< const dicom_element * >
Get all private data elements belonging to a specific creator.
auto get_or_create_sequence(dicom_tag tag) -> std::vector< dicom_dataset > &
Insert or create a sequence element with the given tag.
auto contains(dicom_tag tag) const noexcept -> bool
Check if the dataset contains an element with the given tag.
storage_type::const_iterator const_iterator
Const iterator type.
auto remove_private_block(std::string_view creator_id, uint16_t group) -> size_t
Remove all private data elements and their creator for a given creator.
auto get_string(dicom_tag tag, std::string_view default_value="") const -> std::string
Get the string value of an element.
auto get_private_creator(dicom_tag private_data_tag) const -> std::optional< std::string >
Get the Private Creator identification string for a private data element.
static auto from_string(dicom_tag tag, encoding::vr_type vr, std::string_view value) -> dicom_element
Create an element from a string value.
constexpr auto tag() const noexcept -> dicom_tag
Get the element's tag.
DICOM Dataset - ordered collection of Data Elements.
vr_type
DICOM Value Representation (VR) types.
Definition vr_type.h:29
@ SQ
Sequence of Items (undefined length)
@ LO
Long String (64 chars max)
vr_encoding vr