Skip to content

Commit 3f6c0c9

Browse files
Merge pull request #1473 from KrisThielemans/SPECTDICOM_GE
SPECT DICOM: safer reading and better handling of planar data
2 parents 01d5648 + 844a02a commit 3f6c0c9

File tree

2 files changed

+127
-94
lines changed

2 files changed

+127
-94
lines changed

documentation/release_6.2.htm

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,21 @@ <h3>Changed functionality</h3>
9191
<br>
9292
<a href=https://github.com/UCL/STIR/pull/1464>PR #1464</a>
9393
</li>
94+
<li>
95+
<tt>SPECT_dicom_to_interfile</tt> improvements:
96+
<ul>
97+
<li>remove requirement for the <tt>is_planar</tt> parameters.
98+
As STIR can only read SPECT sinograms, we now read/set all fields
99+
from a planar scan as well. There is therefore no need anymore for
100+
the boolean, and it is just ignored.
101+
Output of a conversion of planar data is now directly readable into STIR.
102+
</li>
103+
<li>
104+
do checks if sequences are present to avoid seg-faults
105+
</li>
106+
</ul>
107+
See <a href=https://github.com/UCL/STIR/pull/1473>PR #1473</a>
108+
</li>
94109
</ul>
95110

96111

src/utilities/SPECT_dicom_to_interfile.cxx

100644100755
Lines changed: 112 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright (C) 2018, 2023, University College London
2+
Copyright (C) 2018, 2023-2024, University College London
33
Copyright (C) 2019-2023, National Physical Laboratory
44
This file is part of STIR.
55
@@ -60,8 +60,7 @@ class SPECTDICOMData
6060
SPECTDICOMData(const std::string& DICOM_filename) { dicom_filename = DICOM_filename; };
6161
stir::Succeeded get_interfile_header(std::string& output_header, const std::string& data_filename, const int dataset_num) const;
6262
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();
6564
int num_energy_windows = 1;
6665
int num_frames = 0;
6766
int num_of_projections = 0;
@@ -129,6 +128,18 @@ GetDICOMTagInfo(const gdcm::File& file, const gdcm::Tag tag, std::string& dst, c
129128
{
130129
const gdcm::DataElement& de = file.GetDataSet().GetDataElement(t);
131130
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+
}
132143
const gdcm::Item& item = sqi->GetItem(sequence_idx);
133144

134145
element = item.GetDataElement(tag);
@@ -263,7 +274,7 @@ GetRadionuclideInfo(const gdcm::File& file, const RadionuclideInfo request, std:
263274
}
264275

265276
stir::Succeeded
266-
SPECTDICOMData::open_dicom_file(bool is_planar)
277+
SPECTDICOMData::open_dicom_file()
267278
{
268279

269280
stir::info(boost::format("SPECTDICOMData: opening file %1%") % dicom_filename);
@@ -317,73 +328,29 @@ SPECTDICOMData::open_dicom_file(bool is_planar)
317328
}
318329

319330
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)
321333
{
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+
}
376336

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+
}
381341

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+
{
387354
energy_window_name.resize(num_energy_windows);
388355
lower_en_window_thres.resize(num_energy_windows);
389356
upper_en_window_thres.resize(num_energy_windows);
@@ -412,6 +379,58 @@ SPECTDICOMData::open_dicom_file(bool is_planar)
412379
}
413380
}
414381

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+
415434
num_dimensions = 2;
416435

417436
matrix_labels.push_back("axial coordinate");
@@ -473,6 +492,7 @@ SPECTDICOMData::get_interfile_header(std::string& output_header, const std::stri
473492
ss << std::endl;
474493

475494
ss << "!GENERAL IMAGE DATA :=" << std::endl;
495+
// We will write planar data as SPECT (i.e. "tomographic") anyway
476496
ss << "!type of data := Tomographic" << std::endl;
477497
ss << "imagedata byte order := LITTLEENDIAN" << std::endl;
478498
ss << "!number format := float" << std::endl;
@@ -502,28 +522,23 @@ SPECTDICOMData::get_interfile_header(std::string& output_header, const std::stri
502522
ss << std::endl;
503523

504524
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;
506527
ss << "start angle := " << this->start_angle << std::endl;
507528

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+
}
527542
ss << std::endl;
528543

529544
ss << "!END OF INTERFILE :=";
@@ -558,7 +573,11 @@ SPECTDICOMData::get_proj_data(const std::string& output_file) const
558573

559574
const gdcm::DataElement& de = file.GetDataSet().GetDataElement(gdcm::Tag(0x7fe0, 0x0010));
560575
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+
}
562581
/*
563582
std::string tmpFile = "tmp.s";
564583
std::ofstream outfile(tmpFile.c_str(), std::ios::out | std::ios::binary);
@@ -614,20 +633,19 @@ int
614633
main(int argc, char* argv[])
615634
{
616635

617-
if (argc != 4)
636+
if (argc != 3 && argc != 4)
618637
{
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";
620640
exit(EXIT_FAILURE);
621641
}
622642

623643
SPECTDICOMData spect(argv[2]);
624644
const std::string output_prefix(argv[1]);
625645

626-
spect.is_planar = atoi(argv[3]);
627-
628646
try
629647
{
630-
if (spect.open_dicom_file(spect.is_planar) == stir::Succeeded::no)
648+
if (spect.open_dicom_file() == stir::Succeeded::no)
631649
{
632650
std::cerr << "Failed to read!" << std::endl;
633651
return EXIT_FAILURE;

0 commit comments

Comments
 (0)