Render a DICOM image to JPEG or PNG.
1334 {
1335
1336
1337 std::ifstream file(std::string(file_path), std::ios::binary);
1338 if (!file) {
1339 return rendered_result::error("Failed to open DICOM file");
1340 }
1341
1342 std::vector<uint8_t> file_data(
1343 (std::istreambuf_iterator<char>(file)),
1344 std::istreambuf_iterator<char>());
1345 file.close();
1346
1347 auto dicom_result = core::dicom_file::from_bytes(
1348 std::span<const uint8_t>(file_data.data(), file_data.size()));
1349
1350 if (dicom_result.is_err()) {
1351 return rendered_result::error("Failed to parse DICOM file");
1352 }
1353
1354 const auto& dataset = dicom_result.value().dataset();
1355
1356
1357 auto rows_elem = dataset.get(core::tags::rows);
1358 auto cols_elem = dataset.get(core::tags::columns);
1359 auto bits_stored_elem = dataset.get(core::tags::bits_stored);
1360 auto bits_allocated_elem = dataset.get(core::tags::bits_allocated);
1361 auto pixel_rep_elem = dataset.get(core::tags::pixel_representation);
1362 auto samples_elem = dataset.get(core::tags::samples_per_pixel);
1363 auto pixel_data_elem = dataset.get(core::tags::pixel_data);
1364
1365 if (!rows_elem || !cols_elem || !pixel_data_elem) {
1366 return rendered_result::error("Missing required image attributes");
1367 }
1368
1369 uint16_t rows = rows_elem->as_numeric<uint16_t>().unwrap_or(0);
1370 uint16_t cols = cols_elem->as_numeric<uint16_t>().unwrap_or(0);
1371 uint16_t bits_stored = bits_stored_elem ?
1372 bits_stored_elem->as_numeric<uint16_t>().unwrap_or(8) : 8;
1373 uint16_t bits_allocated = bits_allocated_elem ?
1374 bits_allocated_elem->as_numeric<uint16_t>().unwrap_or(8) : 8;
1375 uint16_t pixel_rep = pixel_rep_elem ?
1376 pixel_rep_elem->as_numeric<uint16_t>().unwrap_or(0) : 0;
1377 uint16_t samples_per_pixel = samples_elem ?
1378 samples_elem->as_numeric<uint16_t>().unwrap_or(1) : 1;
1379
1380 bool is_signed = (pixel_rep == 1);
1381
1382
1383 double window_center = 128.0;
1384 double window_width = 256.0;
1385
1388 } else if (auto wc = dataset.get(core::tags::window_center)) {
1389 try {
1390 auto values_result = wc->as_string_list();
1391 if (values_result.is_ok() && !values_result.value().empty()) {
1392 window_center = std::stod(values_result.value()[0]);
1393 }
1394 } catch (...) {}
1395 }
1396
1399 } else if (auto ww = dataset.get(core::tags::window_width)) {
1400 try {
1401 auto values_result = ww->as_string_list();
1402 if (values_result.is_ok() && !values_result.value().empty()) {
1404 }
1405 } catch (...) {}
1406 }
1407
1408
1411
1412 if (auto rs = dataset.get(core::tags::rescale_slope)) {
1413 try {
1415 } catch (...) {}
1416 }
1417 if (auto ri = dataset.get(core::tags::rescale_intercept)) {
1418 try {
1420 } catch (...) {}
1421 }
1422
1423
1424 auto pixel_data = pixel_data_elem->raw_data();
1425
1426
1427 size_t frame_size =
static_cast<size_t>(
rows) * cols * samples_per_pixel *
1428 ((bits_allocated + 7) / 8);
1429
1430
1431 std::span<const uint8_t> frame_data =
pixel_data;
1432 if (params.
frame > 1) {
1433 auto extracted = extract_frame(pixel_data, params.
frame, frame_size);
1434 if (extracted.empty()) {
1435 return rendered_result::error("Requested frame does not exist");
1436 }
1437
1438
1439 }
1440
1441
1442 std::vector<uint8_t> output_pixels;
1443 if (samples_per_pixel == 1) {
1444 output_pixels = apply_window_level(
1445 frame_data, cols, rows, bits_stored, is_signed,
1446 window_center, window_width, rescale_slope, rescale_intercept);
1447 } else {
1448
1449 output_pixels.resize(static_cast<size_t>(rows) * cols * samples_per_pixel);
1450 if (bits_allocated == 8) {
1451 std::copy(frame_data.begin(),
1452 frame_data.begin() + (std::min)(frame_data.size(),
1453 output_pixels.size()),
1454 output_pixels.begin());
1455 } else {
1456
1457 for (size_t i = 0; i < output_pixels.size() && i * 2 + 1 < frame_data.size(); ++i) {
1458 uint16_t val = static_cast<uint16_t>(
1459 frame_data[i * 2] | (frame_data[i * 2 + 1] << 8));
1460 output_pixels[i] =
static_cast<uint8_t
>(val >> (
bits_stored - 8));
1461 }
1462 }
1463 }
1464
1465
1466 encoding::compression::image_params img_params;
1467 img_params.width = cols;
1468 img_params.height =
rows;
1469 img_params.bits_allocated = 8;
1470 img_params.bits_stored = 8;
1471 img_params.high_bit = 7;
1473 img_params.photometric =
1475 encoding::compression::photometric_interpretation::monochrome2 :
1476 encoding::compression::photometric_interpretation::
rgb;
1477
1478 if (params.
format == rendered_format::jphc) {
1479 encoding::compression::htj2k_codec codec(
1480 false, false);
1481
1482 auto result = codec.encode(output_pixels, img_params);
1483 if (result.is_err()) {
1484 return rendered_result::error("HTJ2K encoding failed: " +
1485 result.error().message);
1486 }
1487
1488 return rendered_result::ok(std::move(result.value().data), media_type::jphc);
1489 }
else if (params.
format == rendered_format::jpeg) {
1490 encoding::compression::jpeg_baseline_codec codec;
1491
1492 encoding::compression::compression_options opts;
1493 opts.quality = params.
quality;
1494
1495 auto result = codec.encode(output_pixels, img_params, opts);
1496 if (result.is_err()) {
1497 return rendered_result::error("JPEG encoding failed: " +
1498 result.error().message);
1499 }
1500
1501 return rendered_result::ok(std::move(result.value().data), media_type::jpeg);
1502 } else {
1503
1504 return rendered_result::error("PNG encoding not yet implemented");
1505 }
1506}
@ rgb
Red, Green, Blue color model.