PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
test_data_generator_test.cpp
Go to the documentation of this file.
1
11#include "test_data_generator.h"
12
13#include <catch2/catch_test_macros.hpp>
14
15#include <algorithm> // for std::all_of
16
18
19// =============================================================================
20// Single Modality Generator Tests
21// =============================================================================
22
23TEST_CASE("test_data_generator::ct generates valid CT dataset", "[data_generator][ct]") {
24 auto ds = test_data_generator::ct();
25
26 SECTION("has required patient module attributes") {
27 REQUIRE(ds.contains(core::tags::patient_name));
28 REQUIRE(ds.contains(core::tags::patient_id));
29 CHECK(ds.get_string(core::tags::patient_name) == "TEST^CT^PATIENT");
30 }
31
32 SECTION("has required study module attributes") {
33 REQUIRE(ds.contains(core::tags::study_instance_uid));
34 REQUIRE(ds.contains(core::tags::study_date));
35 }
36
37 SECTION("has required series module attributes") {
38 REQUIRE(ds.contains(core::tags::series_instance_uid));
39 REQUIRE(ds.contains(core::tags::modality));
40 CHECK(ds.get_string(core::tags::modality) == "CT");
41 }
42
43 SECTION("has required SOP common attributes") {
44 REQUIRE(ds.contains(core::tags::sop_class_uid));
45 REQUIRE(ds.contains(core::tags::sop_instance_uid));
46 CHECK(ds.get_string(core::tags::sop_class_uid) == "1.2.840.10008.5.1.4.1.1.2");
47 }
48
49 SECTION("has pixel data") {
50 REQUIRE(ds.contains(core::tags::pixel_data));
51 }
52
53 SECTION("respects provided study UID") {
54 std::string custom_study_uid = "1.2.3.4.5.6.7.8.9";
55 auto ds2 = test_data_generator::ct(custom_study_uid);
56 CHECK(ds2.get_string(core::tags::study_instance_uid) == custom_study_uid);
57 }
58}
59
60TEST_CASE("test_data_generator::mr generates valid MR dataset", "[data_generator][mr]") {
61 auto ds = test_data_generator::mr();
62
63 REQUIRE(ds.contains(core::tags::modality));
64 CHECK(ds.get_string(core::tags::modality) == "MR");
65 CHECK(ds.get_string(core::tags::sop_class_uid) == "1.2.840.10008.5.1.4.1.1.4");
66 REQUIRE(ds.contains(core::tags::pixel_data));
67}
68
69TEST_CASE("test_data_generator::xa generates valid XA dataset", "[data_generator][xa]") {
70 auto ds = test_data_generator::xa();
71
72 REQUIRE(ds.contains(core::tags::modality));
73 CHECK(ds.get_string(core::tags::modality) == "XA");
74
75 SECTION("has XA SOP Class UID") {
76 CHECK(ds.get_string(core::tags::sop_class_uid) ==
78 }
79
80 SECTION("has XA-specific attributes") {
81 constexpr core::dicom_tag positioner_primary_angle{0x0018, 0x1510};
82 constexpr core::dicom_tag kvp{0x0018, 0x0060};
83
84 REQUIRE(ds.contains(positioner_primary_angle));
85 REQUIRE(ds.contains(kvp));
86 }
87
88 SECTION("has larger image dimensions than CT/MR") {
89 REQUIRE(ds.contains(core::tags::rows));
90 REQUIRE(ds.contains(core::tags::columns));
91
92 auto rows = ds.get_numeric<uint16_t>(core::tags::rows);
93 auto cols = ds.get_numeric<uint16_t>(core::tags::columns);
94
95 REQUIRE(rows.has_value());
96 REQUIRE(cols.has_value());
97 CHECK(rows.value() == 512);
98 CHECK(cols.value() == 512);
99 }
100}
101
102TEST_CASE("test_data_generator::us generates valid US dataset", "[data_generator][us]") {
103 auto ds = test_data_generator::us();
104
105 REQUIRE(ds.contains(core::tags::modality));
106 CHECK(ds.get_string(core::tags::modality) == "US");
107
108 SECTION("has US SOP Class UID") {
109 CHECK(ds.get_string(core::tags::sop_class_uid) ==
111 }
112
113 SECTION("has 8-bit pixel data") {
114 auto bits_allocated = ds.get_numeric<uint16_t>(core::tags::bits_allocated);
115 REQUIRE(bits_allocated.has_value());
116 CHECK(bits_allocated.value() == 8);
117 }
118}
119
120// =============================================================================
121// Multi-frame Generator Tests
122// =============================================================================
123
124TEST_CASE("test_data_generator::xa_cine generates valid multi-frame XA dataset", "[data_generator][xa][multiframe]") {
125 constexpr uint32_t num_frames = 15;
126 auto ds = test_data_generator::xa_cine(num_frames);
127
128 SECTION("has Number of Frames attribute") {
129 constexpr core::dicom_tag number_of_frames{0x0028, 0x0008};
130 REQUIRE(ds.contains(number_of_frames));
131 CHECK(ds.get_string(number_of_frames) == std::to_string(num_frames));
132 }
133
134 SECTION("has XA-specific cine attributes") {
135 constexpr core::dicom_tag cine_rate{0x0018, 0x0040};
136 constexpr core::dicom_tag frame_time{0x0018, 0x1063};
137
138 REQUIRE(ds.contains(cine_rate));
139 REQUIRE(ds.contains(frame_time));
140 }
141
142 SECTION("has appropriately sized pixel data") {
143 auto* pixel_elem = ds.get(core::tags::pixel_data);
144 REQUIRE(pixel_elem != nullptr);
145
146 // 512x512 * 2 bytes * 15 frames = 7,864,320 bytes
147 size_t expected_size = 512 * 512 * 2 * num_frames;
148 CHECK(pixel_elem->length() == expected_size);
149 }
150}
151
152TEST_CASE("test_data_generator::us_cine generates valid multi-frame US dataset", "[data_generator][us][multiframe]") {
153 constexpr uint32_t num_frames = 30;
154 auto ds = test_data_generator::us_cine(num_frames);
155
156 SECTION("has US Multi-frame SOP Class") {
157 CHECK(ds.get_string(core::tags::sop_class_uid) ==
159 }
160
161 SECTION("has Number of Frames attribute") {
162 constexpr core::dicom_tag number_of_frames{0x0028, 0x0008};
163 REQUIRE(ds.contains(number_of_frames));
164 CHECK(ds.get_string(number_of_frames) == std::to_string(num_frames));
165 }
166
167 SECTION("has 8-bit pixel data with multiple frames") {
168 auto* pixel_elem = ds.get(core::tags::pixel_data);
169 REQUIRE(pixel_elem != nullptr);
170
171 // 640x480 * 1 byte * 30 frames = 9,216,000 bytes
172 size_t expected_size = 640 * 480 * 1 * num_frames;
173 CHECK(pixel_elem->length() == expected_size);
174 }
175}
176
177TEST_CASE("test_data_generator::enhanced_ct generates valid Enhanced CT dataset", "[data_generator][ct][enhanced]") {
178 constexpr uint32_t num_frames = 50;
179 auto ds = test_data_generator::enhanced_ct(num_frames);
180
181 SECTION("has Enhanced CT SOP Class") {
182 CHECK(ds.get_string(core::tags::sop_class_uid) == "1.2.840.10008.5.1.4.1.1.2.1");
183 }
184
185 SECTION("has Image Type attribute") {
186 REQUIRE(ds.contains(core::tags::image_type));
187 }
188
189 SECTION("has Number of Frames") {
190 constexpr core::dicom_tag number_of_frames{0x0028, 0x0008};
191 CHECK(ds.get_string(number_of_frames) == std::to_string(num_frames));
192 }
193}
194
195TEST_CASE("test_data_generator::enhanced_mr generates valid Enhanced MR dataset", "[data_generator][mr][enhanced]") {
196 constexpr uint32_t num_frames = 25;
197 auto ds = test_data_generator::enhanced_mr(num_frames);
198
199 SECTION("has Enhanced MR SOP Class") {
200 CHECK(ds.get_string(core::tags::sop_class_uid) == "1.2.840.10008.5.1.4.1.1.4.1");
201 }
202
203 SECTION("has Number of Frames") {
204 constexpr core::dicom_tag number_of_frames{0x0028, 0x0008};
205 CHECK(ds.get_string(number_of_frames) == std::to_string(num_frames));
206 }
207}
208
209// =============================================================================
210// Clinical Workflow Tests
211// =============================================================================
212
213TEST_CASE("test_data_generator::patient_journey creates multi-modal study", "[data_generator][workflow]") {
214 auto study = test_data_generator::patient_journey("PATIENT001", {"CT", "MR", "XA"});
215
216 SECTION("has consistent patient information") {
217 CHECK(study.patient_id == "PATIENT001");
218 CHECK_FALSE(study.study_uid.empty());
219
220 for (const auto& ds : study.datasets) {
221 CHECK(ds.get_string(core::tags::patient_id) == "PATIENT001");
222 CHECK(ds.get_string(core::tags::study_instance_uid) == study.study_uid);
223 }
224 }
225
226 SECTION("contains all requested modalities") {
227 CHECK(study.datasets.size() == 3);
228
229 auto ct_datasets = study.get_by_modality("CT");
230 auto mr_datasets = study.get_by_modality("MR");
231 auto xa_datasets = study.get_by_modality("XA");
232
233 CHECK(ct_datasets.size() == 1);
234 CHECK(mr_datasets.size() == 1);
235 CHECK(xa_datasets.size() == 1);
236 }
237
238 SECTION("each modality has unique series UID") {
239 CHECK(study.series_count() == 3);
240 }
241}
242
243TEST_CASE("test_data_generator::worklist generates valid worklist item", "[data_generator][worklist]") {
244 auto ds = test_data_generator::worklist("WL001", "MR");
245
246 SECTION("has patient attributes") {
247 REQUIRE(ds.contains(core::tags::patient_name));
248 CHECK(ds.get_string(core::tags::patient_id) == "WL001");
249 }
250
251 SECTION("has scheduled procedure step attributes") {
253 REQUIRE(ds.contains(core::tags::scheduled_station_ae_title));
254 CHECK(ds.get_string(core::tags::modality) == "MR");
255 }
256
257 SECTION("has requested procedure attributes") {
258 REQUIRE(ds.contains(core::tags::requested_procedure_id));
259 REQUIRE(ds.contains(core::tags::accession_number));
260 REQUIRE(ds.contains(core::tags::study_instance_uid));
261 }
262}
263
264// =============================================================================
265// Edge Case Generator Tests
266// =============================================================================
267
268TEST_CASE("test_data_generator::large creates appropriately sized dataset", "[data_generator][edge_case]") {
269 constexpr size_t target_mb = 2;
270 auto ds = test_data_generator::large(target_mb);
271
272 auto* pixel_elem = ds.get(core::tags::pixel_data);
273 REQUIRE(pixel_elem != nullptr);
274
275 // Check that pixel data size is approximately correct
276 // (may not be exact due to square dimension rounding)
277 size_t target_bytes = target_mb * 1024 * 1024;
278 size_t actual_size = pixel_elem->length();
279
280 // Allow for some variance due to dimension rounding
281 CHECK(actual_size >= target_bytes / 2);
282 CHECK(actual_size <= target_bytes * 2);
283}
284
285TEST_CASE("test_data_generator::unicode creates dataset with Unicode characters", "[data_generator][edge_case][unicode]") {
287
288 SECTION("has specific character set") {
289 REQUIRE(ds.contains(core::tags::specific_character_set));
290 }
291
292 SECTION("has patient name with Korean characters") {
293 REQUIRE(ds.contains(core::tags::patient_name));
294 // The patient name contains Korean characters
295 auto patient_name = ds.get_string(core::tags::patient_name);
296 CHECK_FALSE(patient_name.empty());
297 }
298}
299
300TEST_CASE("test_data_generator::with_private_tags includes private tags", "[data_generator][edge_case][private]") {
301 auto ds = test_data_generator::with_private_tags("MY_PRIVATE_CREATOR");
302
303 SECTION("has private creator tag") {
304 constexpr core::dicom_tag private_creator_tag{0x0011, 0x0010};
305 REQUIRE(ds.contains(private_creator_tag));
306 CHECK(ds.get_string(private_creator_tag) == "MY_PRIVATE_CREATOR");
307 }
308
309 SECTION("has private data tags") {
310 constexpr core::dicom_tag private_data_1{0x0011, 0x1001};
311 constexpr core::dicom_tag private_data_2{0x0011, 0x1002};
312
313 REQUIRE(ds.contains(private_data_1));
314 REQUIRE(ds.contains(private_data_2));
315 }
316}
317
318TEST_CASE("test_data_generator::invalid creates datasets with specific errors", "[data_generator][edge_case][invalid]") {
319 SECTION("missing_sop_class_uid") {
321 CHECK_FALSE(ds.contains(core::tags::sop_class_uid));
322 }
323
324 SECTION("missing_sop_instance_uid") {
326 CHECK_FALSE(ds.contains(core::tags::sop_instance_uid));
327 }
328
329 SECTION("missing_patient_id") {
331 CHECK_FALSE(ds.contains(core::tags::patient_id));
332 }
333
334 SECTION("missing_study_instance_uid") {
336 CHECK_FALSE(ds.contains(core::tags::study_instance_uid));
337 }
338
339 SECTION("corrupted_pixel_data") {
341 auto* pixel_elem = ds.get(core::tags::pixel_data);
342 REQUIRE(pixel_elem != nullptr);
343 // Pixel data should be much smaller than expected
344 CHECK(pixel_elem->length() < 1000);
345 }
346}
347
348// =============================================================================
349// Utility Function Tests
350// =============================================================================
351
352TEST_CASE("test_data_generator::generate_uid creates unique UIDs", "[data_generator][utility]") {
353 std::string uid1 = test_data_generator::generate_uid();
354 std::string uid2 = test_data_generator::generate_uid();
355 std::string uid3 = test_data_generator::generate_uid();
356
357 CHECK(uid1 != uid2);
358 CHECK(uid2 != uid3);
359 CHECK(uid1 != uid3);
360
361 // All should start with the default root
362 CHECK(uid1.find("1.2.826.0.1.3680043.9.9999") == 0);
363}
364
365TEST_CASE("test_data_generator::current_date returns valid DICOM date", "[data_generator][utility]") {
366 std::string date = test_data_generator::current_date();
367
368 // DICOM DA format: YYYYMMDD (8 characters)
369 CHECK(date.length() == 8);
370
371 // Should be all digits
372 CHECK(std::all_of(date.begin(), date.end(), ::isdigit));
373}
374
375TEST_CASE("test_data_generator::current_time returns valid DICOM time", "[data_generator][utility]") {
376 std::string time = test_data_generator::current_time();
377
378 // DICOM TM format: HHMMSS (6 characters minimum)
379 CHECK(time.length() >= 6);
380
381 // Should be all digits
382 CHECK(std::all_of(time.begin(), time.end(), ::isdigit));
383}
384
385} // namespace kcenon::pacs::integration_test
static core::dicom_dataset us_cine(uint32_t frames=60, const std::string &study_uid="")
Generate a multi-frame US cine dataset.
static core::dicom_dataset invalid(invalid_dataset_type type)
Generate an intentionally invalid dataset.
static std::string generate_uid(const std::string &root="1.2.826.0.1.3680043.9.9999")
Generate a unique UID for testing.
static core::dicom_dataset large(size_t target_size_mb)
Generate a large dataset for stress testing.
static core::dicom_dataset worklist(const std::string &patient_id="", const std::string &modality="CT")
Generate a worklist item dataset.
static core::dicom_dataset mr(const std::string &study_uid="")
Generate an MR Image dataset.
static core::dicom_dataset enhanced_ct(uint32_t frames=100, const std::string &study_uid="")
Generate an Enhanced CT multi-frame dataset.
static multi_modal_study patient_journey(const std::string &patient_id, const std::vector< std::string > &modalities={"CT", "MR", "XA"})
Generate a complete multi-modal patient study.
static core::dicom_dataset unicode()
Generate a dataset with Unicode patient names.
static core::dicom_dataset ct(const std::string &study_uid="")
Generate a CT Image dataset.
static core::dicom_dataset enhanced_mr(uint32_t frames=50, const std::string &study_uid="")
Generate an Enhanced MR multi-frame dataset.
static core::dicom_dataset us(const std::string &study_uid="")
Generate a single-frame US Image dataset.
static core::dicom_dataset xa(const std::string &study_uid="")
Generate a single-frame XA Image dataset.
static core::dicom_dataset with_private_tags(const std::string &creator_id="TEST PRIVATE CREATOR")
Generate a dataset with private tags.
static std::string current_time()
Get current time in DICOM TM format (HHMMSS)
static std::string current_date()
Get current date in DICOM DA format (YYYYMMDD)
static core::dicom_dataset xa_cine(uint32_t frames=30, const std::string &study_uid="")
Generate a multi-frame XA cine dataset.
constexpr dicom_tag patient_id
Patient ID.
constexpr dicom_tag bits_allocated
Bits Allocated.
constexpr dicom_tag rows
Rows.
constexpr dicom_tag specific_character_set
Specific Character Set.
constexpr dicom_tag columns
Columns.
constexpr dicom_tag scheduled_procedure_step_start_date
Scheduled Procedure Step Start Date.
constexpr dicom_tag sop_instance_uid
SOP Instance UID.
constexpr dicom_tag image_type
Image Type.
constexpr dicom_tag pixel_data
Pixel Data.
constexpr dicom_tag accession_number
Accession Number.
constexpr dicom_tag modality
Modality.
constexpr dicom_tag scheduled_station_ae_title
Scheduled Station AE Title.
constexpr dicom_tag study_instance_uid
Study Instance UID.
constexpr dicom_tag sop_class_uid
SOP Class UID.
constexpr dicom_tag requested_procedure_id
Requested Procedure ID.
constexpr dicom_tag patient_name
Patient's Name.
constexpr dicom_tag study_date
Study Date.
constexpr dicom_tag series_instance_uid
Series Instance UID.
@ missing_study_instance_uid
Missing Study Instance UID.
TEST_CASE("test_data_generator::ct generates valid CT dataset", "[data_generator][ct]")
constexpr std::string_view us_image_storage_uid
US Image Storage SOP Class UID (single-frame)
Definition us_storage.h:39
constexpr std::string_view xa_image_storage_uid
XA Image Storage SOP Class UID (single/multi-frame)
Definition xa_storage.h:47
constexpr std::string_view us_multiframe_image_storage_uid
US Multi-frame Image Storage SOP Class UID (cine loops)
Definition us_storage.h:43
Comprehensive DICOM test data generators for integration testing.
constexpr dicom_tag number_of_frames