PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
test_data_generator.cpp
Go to the documentation of this file.
1
7
12
13#include <chrono>
14#include <cmath>
15#include <iomanip>
16#include <sstream>
17
18namespace kcenon::pacs::samples {
19
20// ============================================================================
21// Static Member Definitions
22// ============================================================================
23
24std::atomic<uint64_t> test_data_generator::uid_counter_{0};
25
26// ============================================================================
27// SOP Class UIDs
28// ============================================================================
29
30namespace sop_class {
31constexpr std::string_view ct_image_storage = "1.2.840.10008.5.1.4.1.1.2";
32constexpr std::string_view mr_image_storage = "1.2.840.10008.5.1.4.1.1.4";
33constexpr std::string_view cr_image_storage = "1.2.840.10008.5.1.4.1.1.1";
34constexpr std::string_view dx_image_storage = "1.2.840.10008.5.1.4.1.1.1.1";
35} // namespace sop_class
36
37// ============================================================================
38// UID Generation
39// ============================================================================
40
41auto test_data_generator::generate_uid(std::string_view root) -> std::string {
42 auto now = std::chrono::system_clock::now();
43 auto time = std::chrono::duration_cast<std::chrono::microseconds>(
44 now.time_since_epoch()).count();
45
46 auto counter = uid_counter_.fetch_add(1, std::memory_order_relaxed);
47
48 std::ostringstream oss;
49 oss << root << "." << time << "." << counter;
50 return oss.str();
51}
52
53// ============================================================================
54// Date/Time Utilities
55// ============================================================================
56
57auto test_data_generator::current_date() -> std::string {
58 auto now = std::chrono::system_clock::now();
59 auto time_t = std::chrono::system_clock::to_time_t(now);
60 std::tm tm{};
61#ifdef _WIN32
62 localtime_s(&tm, &time_t);
63#else
64 localtime_r(&time_t, &tm);
65#endif
66
67 std::ostringstream oss;
68 oss << std::setfill('0')
69 << std::setw(4) << (tm.tm_year + 1900)
70 << std::setw(2) << (tm.tm_mon + 1)
71 << std::setw(2) << tm.tm_mday;
72 return oss.str();
73}
74
75auto test_data_generator::current_time() -> std::string {
76 auto now = std::chrono::system_clock::now();
77 auto time_t = std::chrono::system_clock::to_time_t(now);
78 auto ms = std::chrono::duration_cast<std::chrono::microseconds>(
79 now.time_since_epoch()).count() % 1000000;
80 std::tm tm{};
81#ifdef _WIN32
82 localtime_s(&tm, &time_t);
83#else
84 localtime_r(&time_t, &tm);
85#endif
86
87 std::ostringstream oss;
88 oss << std::setfill('0')
89 << std::setw(2) << tm.tm_hour
90 << std::setw(2) << tm.tm_min
91 << std::setw(2) << tm.tm_sec
92 << "." << std::setw(6) << ms;
93 return oss.str();
94}
95
96// ============================================================================
97// Base Dataset Creation
98// ============================================================================
99
101 const patient_info& patient,
102 const image_params& params) -> core::dicom_dataset {
103
104 using namespace core::tags;
105 using vr = encoding::vr_type;
106
108
109 // Patient Module
110 ds.set_string(patient_name, vr::PN, patient.patient_name);
111 ds.set_string(patient_id, vr::LO, patient.patient_id);
112 ds.set_string(patient_birth_date, vr::DA, patient.birth_date);
113 ds.set_string(patient_sex, vr::CS, patient.sex);
114
115 // General Study Module
116 auto study_uid = generate_uid();
117 ds.set_string(study_instance_uid, vr::UI, study_uid);
118 ds.set_string(study_date, vr::DA, current_date());
119 ds.set_string(study_time, vr::TM, current_time());
120 ds.set_string(accession_number, vr::SH, "ACC" + patient.patient_id);
121 ds.set_string(study_id, vr::SH, "1");
122 ds.set_string(study_description, vr::LO, "Test Study");
123 ds.set_string(referring_physician_name, vr::PN, "REFERRING^PHYSICIAN");
124
125 // General Series Module
126 auto series_uid = generate_uid();
127 ds.set_string(series_instance_uid, vr::UI, series_uid);
128 ds.set_string(modality, vr::CS, params.modality);
129 ds.set_numeric<int32_t>(series_number, vr::IS, 1);
130 ds.set_string(series_date, vr::DA, current_date());
131 ds.set_string(series_time, vr::TM, current_time());
132 ds.set_string(series_description, vr::LO, params.modality + " Series");
133
134 // Frame of Reference Module
135 ds.set_string(frame_of_reference_uid, vr::UI, generate_uid());
136
137 // General Equipment Module
138 ds.set_string(manufacturer, vr::LO, "PACS System");
139 ds.set_string(station_name, vr::SH, "SAMPLE_STATION");
140 ds.set_string(manufacturers_model_name, vr::LO, "Sample Generator");
141
142 // General Image Module
143 auto instance_uid = generate_uid();
144 ds.set_string(sop_instance_uid, vr::UI, instance_uid);
145 ds.set_numeric<int32_t>(instance_number, vr::IS, 1);
146 ds.set_string(content_date, vr::DA, current_date());
147 ds.set_string(content_time, vr::TM, current_time());
148 ds.set_string(image_type, vr::CS, "ORIGINAL\\PRIMARY\\AXIAL");
149
150 // Image Pixel Module
151 ds.set_numeric<uint16_t>(samples_per_pixel, vr::US, 1);
152 ds.set_string(photometric_interpretation, vr::CS, params.photometric);
153 ds.set_numeric<uint16_t>(rows, vr::US, params.rows);
154 ds.set_numeric<uint16_t>(columns, vr::US, params.columns);
155 ds.set_numeric<uint16_t>(bits_allocated, vr::US, params.bits_allocated);
156 ds.set_numeric<uint16_t>(bits_stored, vr::US, params.bits_stored);
157 ds.set_numeric<uint16_t>(high_bit, vr::US, params.high_bit);
158 ds.set_numeric<uint16_t>(pixel_representation, vr::US, params.pixel_representation);
159
160 // VOI LUT Module
161 ds.set_string(window_center, vr::DS, std::to_string(params.window_center));
162 ds.set_string(window_width, vr::DS, std::to_string(params.window_width));
163
164 return ds;
165}
166
167// ============================================================================
168// Pixel Data Generation
169// ============================================================================
170
172 uint16_t row_count,
173 uint16_t col_count,
174 uint16_t bits_alloc) -> std::vector<uint8_t> {
175
176 size_t bytes_per_pixel = bits_alloc / 8;
177 size_t total_pixels = static_cast<size_t>(row_count) * col_count;
178 std::vector<uint8_t> data(total_pixels * bytes_per_pixel);
179
180 // Generate a simple gradient pattern
181 uint16_t max_value = static_cast<uint16_t>((1 << (bits_alloc > 16 ? 16 : bits_alloc)) - 1);
182
183 for (uint16_t y = 0; y < row_count; ++y) {
184 for (uint16_t x = 0; x < col_count; ++x) {
185 size_t idx = (static_cast<size_t>(y) * col_count + x) * bytes_per_pixel;
186
187 // Create a circular gradient pattern
188 double cx = col_count / 2.0;
189 double cy = row_count / 2.0;
190 double dx = x - cx;
191 double dy = y - cy;
192 double dist = std::sqrt(dx * dx + dy * dy);
193 double max_dist = std::sqrt(cx * cx + cy * cy);
194 double normalized = 1.0 - (dist / max_dist);
195
196 uint16_t value = static_cast<uint16_t>(normalized * max_value * 0.8);
197
198 // Store as little-endian
199 data[idx] = static_cast<uint8_t>(value & 0xFF);
200 if (bytes_per_pixel > 1) {
201 data[idx + 1] = static_cast<uint8_t>((value >> 8) & 0xFF);
202 }
203 }
204 }
205
206 return data;
207}
208
209// ============================================================================
210// CT Dataset Creation
211// ============================================================================
212
214 const patient_info& patient,
215 const image_params& params) -> core::dicom_dataset {
216
217 using namespace core::tags;
218 using vr = encoding::vr_type;
219
220 image_params ct_params = params;
221 ct_params.modality = "CT";
222 ct_params.photometric = "MONOCHROME2";
223 ct_params.window_center = 40.0;
224 ct_params.window_width = 400.0;
225
226 auto ds = create_base_dataset(patient, ct_params);
227
228 // SOP Class
229 ds.set_string(sop_class_uid, vr::UI, std::string{sop_class::ct_image_storage});
230
231 // CT-specific attributes
232 ds.set_string(rescale_intercept, vr::DS, "-1024");
233 ds.set_string(rescale_slope, vr::DS, "1");
234 ds.set_string(rescale_type, vr::LO, "HU");
235 ds.set_string(pixel_spacing, vr::DS, "0.5\\0.5");
236 ds.set_string(slice_location, vr::DS, "0.0");
237 ds.set_string(image_position_patient, vr::DS, "0.0\\0.0\\0.0");
238 ds.set_string(image_orientation_patient, vr::DS, "1.0\\0.0\\0.0\\0.0\\1.0\\0.0");
239
240 // Add pixel data
241 auto pixel_bytes = generate_pixel_data(ct_params.rows, ct_params.columns,
242 ct_params.bits_allocated);
243 ds.insert(core::dicom_element{pixel_data, vr::OW, pixel_bytes});
244
245 return ds;
246}
247
248// ============================================================================
249// MR Dataset Creation
250// ============================================================================
251
253 const patient_info& patient,
255
256 using namespace core::tags;
257 using vr = encoding::vr_type;
258
259 params.modality = "MR";
260 params.photometric = "MONOCHROME2";
261 params.window_center = 500.0;
262 params.window_width = 1000.0;
263
264 auto ds = create_base_dataset(patient, params);
265
266 // SOP Class
267 ds.set_string(sop_class_uid, vr::UI, std::string{sop_class::mr_image_storage});
268
269 // MR-specific attributes
270 ds.set_string(pixel_spacing, vr::DS, "1.0\\1.0");
271 ds.set_string(slice_location, vr::DS, "0.0");
272 ds.set_string(image_position_patient, vr::DS, "0.0\\0.0\\0.0");
273 ds.set_string(image_orientation_patient, vr::DS, "1.0\\0.0\\0.0\\0.0\\1.0\\0.0");
274
275 // Add pixel data
276 auto pixel_bytes = generate_pixel_data(params.rows, params.columns,
277 params.bits_allocated);
278 ds.insert(core::dicom_element{pixel_data, vr::OW, pixel_bytes});
279
280 return ds;
281}
282
283// ============================================================================
284// CR Dataset Creation
285// ============================================================================
286
288 const patient_info& patient,
290
291 using namespace core::tags;
292 using vr = encoding::vr_type;
293
294 params.modality = "CR";
295 params.photometric = "MONOCHROME2";
296 params.rows = 2048;
297 params.columns = 2048;
298 params.bits_allocated = 16;
299 params.bits_stored = 14;
300 params.high_bit = 13;
301 params.window_center = 8192.0;
302 params.window_width = 16384.0;
303
304 auto ds = create_base_dataset(patient, params);
305
306 // SOP Class
307 ds.set_string(sop_class_uid, vr::UI, std::string{sop_class::cr_image_storage});
308
309 // Add pixel data
310 auto pixel_bytes = generate_pixel_data(params.rows, params.columns,
311 params.bits_allocated);
312 ds.insert(core::dicom_element{pixel_data, vr::OW, pixel_bytes});
313
314 return ds;
315}
316
317// ============================================================================
318// DX Dataset Creation
319// ============================================================================
320
322 const patient_info& patient,
324
325 using namespace core::tags;
326 using vr = encoding::vr_type;
327
328 params.modality = "DX";
329 params.photometric = "MONOCHROME2";
330 params.rows = 3000;
331 params.columns = 3000;
332 params.bits_allocated = 16;
333 params.bits_stored = 14;
334 params.high_bit = 13;
335 params.window_center = 8192.0;
336 params.window_width = 16384.0;
337
338 auto ds = create_base_dataset(patient, params);
339
340 // SOP Class
341 ds.set_string(sop_class_uid, vr::UI, std::string{sop_class::dx_image_storage});
342
343 // DX-specific attributes
344 ds.set_string(pixel_spacing, vr::DS, "0.15\\0.15");
345 ds.set_string(image_orientation_patient, vr::DS, "1.0\\0.0\\0.0\\0.0\\1.0\\0.0");
346
347 // Add pixel data
348 auto pixel_bytes = generate_pixel_data(params.rows, params.columns,
349 params.bits_allocated);
350 ds.insert(core::dicom_element{pixel_data, vr::OW, pixel_bytes});
351
352 return ds;
353}
354
355// ============================================================================
356// Study Generation
357// ============================================================================
358
360 const patient_info& patient,
361 const study_info& study,
362 int series_count,
363 int instances_per_series) -> std::vector<core::dicom_dataset> {
364
365 using namespace core::tags;
366 using vr = encoding::vr_type;
367
368 std::vector<core::dicom_dataset> datasets;
369 datasets.reserve(static_cast<size_t>(series_count * instances_per_series));
370
371 auto study_uid = study.study_uid.empty() ? generate_uid() : study.study_uid;
372 auto study_dt = study.study_date.empty() ? current_date() : study.study_date;
373 auto study_tm = study.study_time.empty() ? current_time() : study.study_time;
374
375 for (int series_idx = 0; series_idx < series_count; ++series_idx) {
376 auto series_uid = generate_uid();
377 auto frame_uid = generate_uid();
378
379 for (int instance_idx = 0; instance_idx < instances_per_series; ++instance_idx) {
380 auto ds = create_ct_dataset(patient);
381
382 // Override with study-level info
383 ds.set_string(study_instance_uid, vr::UI, study_uid);
384 ds.set_string(study_date, vr::DA, study_dt);
385 ds.set_string(study_time, vr::TM, study_tm);
386
387 if (!study.accession_number.empty()) {
388 ds.set_string(accession_number, vr::SH, study.accession_number);
389 }
390 if (!study.description.empty()) {
391 ds.set_string(study_description, vr::LO, study.description);
392 }
393 if (!study.referring_physician.empty()) {
394 ds.set_string(referring_physician_name, vr::PN, study.referring_physician);
395 }
396
397 // Series-level info
398 ds.set_string(series_instance_uid, vr::UI, series_uid);
399 ds.set_numeric<int32_t>(series_number, vr::IS, series_idx + 1);
400 ds.set_string(frame_of_reference_uid, vr::UI, frame_uid);
401
402 // Instance-level info
403 ds.set_string(sop_instance_uid, vr::UI, generate_uid());
404 ds.set_numeric<int32_t>(instance_number, vr::IS, instance_idx + 1);
405
406 // Slice location for 3D datasets
407 double slice_loc = static_cast<double>(instance_idx) * 5.0;
408 ds.set_string(slice_location, vr::DS, std::to_string(slice_loc));
409 ds.set_string(image_position_patient, vr::DS,
410 "0.0\\0.0\\" + std::to_string(slice_loc));
411
412 datasets.push_back(std::move(ds));
413 }
414 }
415
416 return datasets;
417}
418
419// ============================================================================
420// File Operations
421// ============================================================================
422
424 const std::vector<core::dicom_dataset>& datasets,
425 const std::filesystem::path& directory) {
426
427 using namespace core::tags;
428 namespace fs = std::filesystem;
429
430 fs::create_directories(directory);
431
432 for (const auto& ds : datasets) {
433 auto pat_id = ds.get_string(patient_id, "UNKNOWN");
434 auto study_uid_str = ds.get_string(study_instance_uid, "UNKNOWN");
435 auto series_uid_str = ds.get_string(series_instance_uid, "UNKNOWN");
436 auto instance_uid_str = ds.get_string(sop_instance_uid, "UNKNOWN");
437
438 // Create hierarchical path
439 auto path = directory / pat_id / study_uid_str / series_uid_str;
440 fs::create_directories(path);
441
442 auto file_path = path / (instance_uid_str + ".dcm");
443
444 // Create DICOM file and save
445 auto file = core::dicom_file::create(
447 auto result = file.save(file_path);
448
449 if (!result.is_ok()) {
450 throw std::runtime_error("Failed to save DICOM file: " +
451 file_path.string());
452 }
453 }
454}
455
456} // namespace kcenon::pacs::samples
Represents a DICOM Dataset (ordered collection of Data Elements)
void set_numeric(dicom_tag tag, encoding::vr_type vr, T value)
Set a numeric value for the given tag.
void set_string(dicom_tag tag, encoding::vr_type vr, std::string_view value)
Set a string value for the given tag.
Represents a DICOM Data Element (Tag, VR, Value)
static auto create(dicom_dataset dataset, const encoding::transfer_syntax &ts) -> dicom_file
Create a new DICOM file from a dataset.
static const transfer_syntax explicit_vr_little_endian
Explicit VR Little Endian (1.2.840.10008.1.2.1)
static void save_to_directory(const std::vector< core::dicom_dataset > &datasets, const std::filesystem::path &directory)
Save datasets to a directory structure.
static auto create_mr_dataset(const patient_info &patient={}, image_params params={}) -> core::dicom_dataset
Create an MR dataset with default or custom parameters.
static auto current_date() -> std::string
Get current date in DICOM format (YYYYMMDD)
static auto create_cr_dataset(const patient_info &patient={}, image_params params={}) -> core::dicom_dataset
Create a CR (Computed Radiography) dataset.
static auto generate_pixel_data(uint16_t rows, uint16_t columns, uint16_t bits_allocated) -> std::vector< uint8_t >
Generate test pixel data.
static std::atomic< uint64_t > uid_counter_
Atomic counter for UID generation.
static auto create_dx_dataset(const patient_info &patient={}, image_params params={}) -> core::dicom_dataset
Create a DX (Digital X-Ray) dataset.
static auto generate_uid(std::string_view root="1.2.410.200001.1.1") -> std::string
Generate a unique DICOM UID.
static auto current_time() -> std::string
Get current time in DICOM format (HHMMSS.FFFFFF)
static auto create_study(const patient_info &patient, const study_info &study, int series_count=1, int instances_per_series=5) -> std::vector< core::dicom_dataset >
Generate a complete study with multiple series and instances.
static auto create_base_dataset(const patient_info &patient, const image_params &params) -> core::dicom_dataset
Create a base dataset with common elements.
static auto create_ct_dataset(const patient_info &patient={}, const image_params &params={}) -> core::dicom_dataset
Create a CT dataset with default or custom parameters.
DICOM Part 10 file handling for reading/writing DICOM files.
Compile-time constants for commonly used DICOM tags.
vr_type
DICOM Value Representation (VR) types.
Definition vr_type.h:29
constexpr std::string_view mr_image_storage
constexpr std::string_view cr_image_storage
constexpr std::string_view dx_image_storage
constexpr std::string_view ct_image_storage
Image parameters for test data generation.
Patient information for test data generation.
Study information for test data generation.
Test data generation utilities for developer samples.
vr_encoding vr