|
1 | 1 | /*
|
2 |
| - Copyright (C) 2018, 2023, University College London |
| 2 | + Copyright (C) 2018, 2023-2024, University College London |
3 | 3 | Copyright (C) 2019-2023, National Physical Laboratory
|
4 | 4 | This file is part of STIR.
|
5 | 5 |
|
@@ -60,8 +60,7 @@ class SPECTDICOMData
|
60 | 60 | SPECTDICOMData(const std::string& DICOM_filename) { dicom_filename = DICOM_filename; };
|
61 | 61 | stir::Succeeded get_interfile_header(std::string& output_header, const std::string& data_filename, const int dataset_num) const;
|
62 | 62 | stir::Succeeded get_proj_data(const std::string& output_file) const;
|
63 |
| - stir::Succeeded open_dicom_file(bool is_planar); |
64 |
| - bool is_planar; |
| 63 | + stir::Succeeded open_dicom_file(); |
65 | 64 | int num_energy_windows = 1;
|
66 | 65 | int num_frames = 0;
|
67 | 66 | int num_of_projections = 0;
|
@@ -129,6 +128,18 @@ GetDICOMTagInfo(const gdcm::File& file, const gdcm::Tag tag, std::string& dst, c
|
129 | 128 | {
|
130 | 129 | const gdcm::DataElement& de = file.GetDataSet().GetDataElement(t);
|
131 | 130 | const gdcm::SequenceOfItems* sqi = de.GetValueAsSQ();
|
| 131 | + if (!sqi) |
| 132 | + { |
| 133 | + // try next sequence |
| 134 | + continue; |
| 135 | + } |
| 136 | + const auto sqi_length = sqi->GetNumberOfItems(); |
| 137 | + if (static_cast<unsigned>(sequence_idx) > sqi_length) |
| 138 | + { |
| 139 | + // stir::warning("sequence_id larger than length"); |
| 140 | + // try next sequence |
| 141 | + continue; |
| 142 | + } |
132 | 143 | const gdcm::Item& item = sqi->GetItem(sequence_idx);
|
133 | 144 |
|
134 | 145 | element = item.GetDataElement(tag);
|
@@ -263,7 +274,7 @@ GetRadionuclideInfo(const gdcm::File& file, const RadionuclideInfo request, std:
|
263 | 274 | }
|
264 | 275 |
|
265 | 276 | stir::Succeeded
|
266 |
| -SPECTDICOMData::open_dicom_file(bool is_planar) |
| 277 | +SPECTDICOMData::open_dicom_file() |
267 | 278 | {
|
268 | 279 |
|
269 | 280 | stir::info(boost::format("SPECTDICOMData: opening file %1%") % dicom_filename);
|
@@ -317,73 +328,29 @@ SPECTDICOMData::open_dicom_file(bool is_planar)
|
317 | 328 | }
|
318 | 329 |
|
319 | 330 | this->num_of_projections = 1;
|
320 |
| - if (!is_planar) |
| 331 | + this->calibration_factor = -1; |
| 332 | + if (GetRadionuclideInfo(file, RadionuclideInfo::CodeMeaning, isotope_name) == stir::Succeeded::yes) |
321 | 333 | {
|
322 |
| - if (GetDICOMTagInfo(file, gdcm::Tag(0x0054, 0x0053), no_of_proj_as_str) == stir::Succeeded::yes) |
323 |
| - { |
324 |
| - num_of_projections = std::stoi(no_of_proj_as_str); |
325 |
| - std::cout << "Number of projections: " << num_of_projections << std::endl; |
326 |
| - } |
327 |
| - |
328 |
| - if (GetDICOMTagInfo(file, gdcm::Tag(0x0018, 0x1140), direction_of_rotation) == stir::Succeeded::yes) |
329 |
| - { |
330 |
| - |
331 |
| - if (direction_of_rotation == "CC") |
332 |
| - direction_of_rotation = "CCW"; |
333 |
| - |
334 |
| - std::cout << "Direction of rotation: " << direction_of_rotation << std::endl; |
335 |
| - } |
336 |
| - |
337 |
| - if (GetDICOMTagInfo(file, gdcm::Tag(0x0054, 0x0200), start_angle_as_string) == stir::Succeeded::yes) |
338 |
| - { |
339 |
| - start_angle = std::stof(start_angle_as_string); |
340 |
| - std::cout << "Starting angle: " << std::fixed << std::setprecision(6) << start_angle << std::endl; |
341 |
| - } |
342 |
| - |
343 |
| - if (GetDICOMTagInfo(file, gdcm::Tag(0x0054, 0x1322), calib_factor_as_string) == stir::Succeeded::yes) |
344 |
| - { |
345 |
| - calibration_factor = std::stof(calib_factor_as_string); |
346 |
| - std::cout << "calibration factor: " << std::fixed << std::setprecision(6) << calibration_factor << std::endl; |
347 |
| - } |
348 |
| - else |
349 |
| - calibration_factor = -1; |
350 |
| - |
351 |
| - if (GetRadionuclideInfo(file, RadionuclideInfo::CodeMeaning, isotope_name) == stir::Succeeded::yes) |
352 |
| - { |
353 |
| - std::cout << "Isotope name: " << isotope_name << std::endl; |
354 |
| - } |
355 |
| - |
356 |
| - if (GetDICOMTagInfo(file, gdcm::Tag(0x0018, 0x1144), angular_step_as_string) == stir::Succeeded::yes) |
357 |
| - { |
358 |
| - angular_step = std::stof(angular_step_as_string); |
359 |
| - std::cout << "Angular step: " << std::fixed << std::setprecision(6) << angular_step << std::endl; |
360 |
| - } |
361 |
| - |
362 |
| - if (GetDICOMTagInfo(file, gdcm::Tag(0x0018, 0x1143), extent_of_rotation_as_string) == stir::Succeeded::yes) |
363 |
| - { |
364 |
| - extent_of_rotation = std::stoi(extent_of_rotation_as_string); |
365 |
| - std::cout << "Rotation extent: " << extent_of_rotation << std::endl; |
366 |
| - } |
367 |
| - |
368 |
| - if (GetDICOMTagInfo(file, gdcm::Tag(0x0018, 0x1142), radius_as_string) == stir::Succeeded::yes) |
369 |
| - { |
370 |
| - rotation_radius = (radius_as_string); |
371 |
| - char slash = '\\'; |
372 |
| - char comma = ','; |
373 |
| - std::cout << "Radius: " << radius_as_string << " " << slash << std::endl; |
374 |
| - std::replace(rotation_radius.begin(), rotation_radius.end(), slash, comma); |
375 |
| - } |
| 334 | + std::cout << "Isotope name: " << isotope_name << std::endl; |
| 335 | + } |
376 | 336 |
|
377 |
| - if (GetDICOMTagInfo(file, gdcm::Tag(0x0018, 0x1242), actual_frame_duration_as_string) == stir::Succeeded::yes) |
378 |
| - { |
379 |
| - actual_frame_duration = std::stoi(actual_frame_duration_as_string); |
380 |
| - } |
| 337 | + if (GetDICOMTagInfo(file, gdcm::Tag(0x0018, 0x1242), actual_frame_duration_as_string) == stir::Succeeded::yes) |
| 338 | + { |
| 339 | + actual_frame_duration = std::stoi(actual_frame_duration_as_string); |
| 340 | + } |
381 | 341 |
|
382 |
| - { |
383 |
| - std::string str; |
384 |
| - if (GetDICOMTagInfo(file, gdcm::Tag(0x0054, 0x0011), str) == stir::Succeeded::yes) |
385 |
| - num_energy_windows = std::stoi(str); |
386 |
| - } |
| 342 | + { |
| 343 | + this->num_energy_windows = 0; |
| 344 | + std::string str; |
| 345 | + if (GetDICOMTagInfo(file, gdcm::Tag(0x0054, 0x0011), str) == stir::Succeeded::yes) |
| 346 | + this->num_energy_windows = std::stoi(str); |
| 347 | + } |
| 348 | + if (this->num_energy_windows == 0) |
| 349 | + { |
| 350 | + std::cout << "No energy window information found\n"; |
| 351 | + } |
| 352 | + else |
| 353 | + { |
387 | 354 | energy_window_name.resize(num_energy_windows);
|
388 | 355 | lower_en_window_thres.resize(num_energy_windows);
|
389 | 356 | upper_en_window_thres.resize(num_energy_windows);
|
@@ -412,6 +379,58 @@ SPECTDICOMData::open_dicom_file(bool is_planar)
|
412 | 379 | }
|
413 | 380 | }
|
414 | 381 |
|
| 382 | + if (GetDICOMTagInfo(file, gdcm::Tag(0x0054, 0x0053), no_of_proj_as_str) == stir::Succeeded::yes) |
| 383 | + { |
| 384 | + num_of_projections = std::stoi(no_of_proj_as_str); |
| 385 | + std::cout << "Number of projections: " << num_of_projections << std::endl; |
| 386 | + } |
| 387 | + |
| 388 | + if (GetDICOMTagInfo(file, gdcm::Tag(0x0018, 0x1140), direction_of_rotation) == stir::Succeeded::yes) |
| 389 | + { |
| 390 | + if (direction_of_rotation == "CC") |
| 391 | + direction_of_rotation = "CCW"; |
| 392 | + |
| 393 | + std::cout << "Direction of rotation: " << direction_of_rotation << std::endl; |
| 394 | + } |
| 395 | + |
| 396 | + if (GetDICOMTagInfo(file, gdcm::Tag(0x0054, 0x0200), start_angle_as_string) == stir::Succeeded::yes) |
| 397 | + { |
| 398 | + start_angle = std::stof(start_angle_as_string); |
| 399 | + std::cout << "Starting angle: " << std::fixed << std::setprecision(6) << start_angle << std::endl; |
| 400 | + } |
| 401 | + |
| 402 | + if (GetDICOMTagInfo(file, gdcm::Tag(0x0054, 0x1322), calib_factor_as_string) == stir::Succeeded::yes) |
| 403 | + { |
| 404 | + calibration_factor = std::stof(calib_factor_as_string); |
| 405 | + std::cout << "calibration factor: " << std::fixed << std::setprecision(6) << calibration_factor << std::endl; |
| 406 | + } |
| 407 | + |
| 408 | + if (GetDICOMTagInfo(file, gdcm::Tag(0x0018, 0x1144), angular_step_as_string) == stir::Succeeded::yes) |
| 409 | + { |
| 410 | + angular_step = std::stof(angular_step_as_string); |
| 411 | + std::cout << "Angular step: " << std::fixed << std::setprecision(6) << angular_step << std::endl; |
| 412 | + } |
| 413 | + |
| 414 | + if (GetDICOMTagInfo(file, gdcm::Tag(0x0018, 0x1143), extent_of_rotation_as_string) == stir::Succeeded::yes) |
| 415 | + { |
| 416 | + extent_of_rotation = std::stoi(extent_of_rotation_as_string); |
| 417 | + std::cout << "Rotation extent: " << extent_of_rotation << std::endl; |
| 418 | + } |
| 419 | + else |
| 420 | + { |
| 421 | + extent_of_rotation = 360; |
| 422 | + stir::warning("Rotation was not present in DICOM file. Setting it to 360"); |
| 423 | + } |
| 424 | + |
| 425 | + if (GetDICOMTagInfo(file, gdcm::Tag(0x0018, 0x1142), radius_as_string) == stir::Succeeded::yes) |
| 426 | + { |
| 427 | + rotation_radius = (radius_as_string); |
| 428 | + char slash = '\\'; |
| 429 | + char comma = ','; |
| 430 | + std::cout << "Radius: " << radius_as_string << " " << slash << std::endl; |
| 431 | + std::replace(rotation_radius.begin(), rotation_radius.end(), slash, comma); |
| 432 | + } |
| 433 | + |
415 | 434 | num_dimensions = 2;
|
416 | 435 |
|
417 | 436 | matrix_labels.push_back("axial coordinate");
|
@@ -473,6 +492,7 @@ SPECTDICOMData::get_interfile_header(std::string& output_header, const std::stri
|
473 | 492 | ss << std::endl;
|
474 | 493 |
|
475 | 494 | ss << "!GENERAL IMAGE DATA :=" << std::endl;
|
| 495 | + // We will write planar data as SPECT (i.e. "tomographic") anyway |
476 | 496 | ss << "!type of data := Tomographic" << std::endl;
|
477 | 497 | ss << "imagedata byte order := LITTLEENDIAN" << std::endl;
|
478 | 498 | ss << "!number format := float" << std::endl;
|
@@ -502,28 +522,23 @@ SPECTDICOMData::get_interfile_header(std::string& output_header, const std::stri
|
502 | 522 | ss << std::endl;
|
503 | 523 |
|
504 | 524 | ss << "!SPECT STUDY (acquired data) :=" << std::endl;
|
505 |
| - ss << "!direction of rotation := " << this->direction_of_rotation << std::endl; |
| 525 | + if (!this->direction_of_rotation.empty()) |
| 526 | + ss << "!direction of rotation := " << this->direction_of_rotation << std::endl; |
506 | 527 | ss << "start angle := " << this->start_angle << std::endl;
|
507 | 528 |
|
508 |
| - if (is_planar) |
509 |
| - { |
510 |
| - ss << "orbit := circular" << std::endl; |
511 |
| - ss << "radius := " << 0 << std::endl; |
512 |
| - } |
513 |
| - else |
514 |
| - { |
515 |
| - if (this->rotation_radius.find(",") != std::string::npos) |
516 |
| - { |
517 |
| - ss << "orbit := non-circular" << std::endl; |
518 |
| - ss << "radii := {" << this->rotation_radius << "}" << std::endl; |
519 |
| - std::cout << "orbit := non-circular" << std::endl; |
520 |
| - } |
521 |
| - else |
522 |
| - { |
523 |
| - ss << "orbit := circular" << std::endl; |
524 |
| - ss << "radius := " << this->rotation_radius << "" << std::endl; |
525 |
| - } |
526 |
| - } |
| 529 | + { |
| 530 | + if (this->rotation_radius.find(",") != std::string::npos) |
| 531 | + { |
| 532 | + ss << "orbit := non-circular" << std::endl; |
| 533 | + ss << "radii := {" << this->rotation_radius << "}" << std::endl; |
| 534 | + std::cout << "orbit := non-circular" << std::endl; |
| 535 | + } |
| 536 | + else |
| 537 | + { |
| 538 | + ss << "orbit := circular" << std::endl; |
| 539 | + ss << "radius := " << this->rotation_radius << "" << std::endl; |
| 540 | + } |
| 541 | + } |
527 | 542 | ss << std::endl;
|
528 | 543 |
|
529 | 544 | ss << "!END OF INTERFILE :=";
|
@@ -558,7 +573,11 @@ SPECTDICOMData::get_proj_data(const std::string& output_file) const
|
558 | 573 |
|
559 | 574 | const gdcm::DataElement& de = file.GetDataSet().GetDataElement(gdcm::Tag(0x7fe0, 0x0010));
|
560 | 575 | const gdcm::ByteValue* bv = de.GetByteValue();
|
561 |
| - |
| 576 | + if (!bv) |
| 577 | + { |
| 578 | + stir::warning("GDCM GetByteValue failed on x7fe0, 0x0010"); |
| 579 | + return stir::Succeeded::no; |
| 580 | + } |
562 | 581 | /*
|
563 | 582 | std::string tmpFile = "tmp.s";
|
564 | 583 | std::ofstream outfile(tmpFile.c_str(), std::ios::out | std::ios::binary);
|
@@ -614,20 +633,19 @@ int
|
614 | 633 | main(int argc, char* argv[])
|
615 | 634 | {
|
616 | 635 |
|
617 |
| - if (argc != 4) |
| 636 | + if (argc != 3 && argc != 4) |
618 | 637 | {
|
619 |
| - std::cerr << "Usage: " << argv[0] << " <output_interfile_prefix> sinogram(dcm)> is_planar?\n"; |
| 638 | + std::cerr << "Usage: " << argv[0] << " <output_interfile_prefix> sinogram(dcm)> [is_planar?]\n" |
| 639 | + << "Note: the is_planar value is ignored. All data will be written as SPECT.\n"; |
620 | 640 | exit(EXIT_FAILURE);
|
621 | 641 | }
|
622 | 642 |
|
623 | 643 | SPECTDICOMData spect(argv[2]);
|
624 | 644 | const std::string output_prefix(argv[1]);
|
625 | 645 |
|
626 |
| - spect.is_planar = atoi(argv[3]); |
627 |
| - |
628 | 646 | try
|
629 | 647 | {
|
630 |
| - if (spect.open_dicom_file(spect.is_planar) == stir::Succeeded::no) |
| 648 | + if (spect.open_dicom_file() == stir::Succeeded::no) |
631 | 649 | {
|
632 | 650 | std::cerr << "Failed to read!" << std::endl;
|
633 | 651 | return EXIT_FAILURE;
|
|
0 commit comments