Skip to content

Commit 0775d5b

Browse files
committed
Sort DICOMs with discriminate volume
1 parent 4dfc374 commit 0775d5b

File tree

4 files changed

+339
-8
lines changed

4 files changed

+339
-8
lines changed
+264
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
#include "gdcmScanner.h"
2+
#include "gdcmTesting.h"
3+
#include "gdcmIPPSorter.h"
4+
#include "gdcmDirectionCosines.h"
5+
#include <cmath>
6+
7+
/*
8+
* The following example is a basic sorted which should work in generic cases.
9+
* It sort files based on:
10+
* Study Instance UID
11+
* Series Instance UID
12+
* Frame of Reference UID
13+
* Image Orientation (Patient)
14+
* Image Position (Patient) (Sorting based on IPP + IOP)
15+
*/
16+
17+
namespace gdcm
18+
{
19+
const Tag t1(0x0020, 0x000d); // Study Instance UID
20+
const Tag t2(0x0020, 0x000e); // Series Instance UID
21+
const Tag t3(0x0020, 0x0052); // Frame of Reference UID
22+
const Tag t4(0x0020, 0x0037); // Image Orientation (Patient)
23+
24+
class DiscriminateVolume
25+
{
26+
private:
27+
std::vector<Directory::FilenamesType> SortedFiles;
28+
std::vector<Directory::FilenamesType> UnsortedFiles;
29+
30+
Directory::FilenamesType GetAllFilenamesFromTagToValue(
31+
Scanner const &s, Directory::FilenamesType const &filesubset, Tag const &t, const char *valueref)
32+
{
33+
Directory::FilenamesType theReturn;
34+
if (valueref)
35+
{
36+
size_t len = strlen(valueref);
37+
Directory::FilenamesType::const_iterator file = filesubset.begin();
38+
for (; file != filesubset.end(); ++file)
39+
{
40+
const char *filename = file->c_str();
41+
const char *value = s.GetValue(filename, t);
42+
if (value && strncmp(value, valueref, len) == 0)
43+
{
44+
theReturn.push_back(filename);
45+
}
46+
}
47+
}
48+
return theReturn;
49+
}
50+
51+
void ProcessAIOP(Scanner const &, Directory::FilenamesType const &subset, const char *iopval)
52+
{
53+
std::cout << "IOP: " << iopval << std::endl;
54+
IPPSorter ipp;
55+
ipp.SetComputeZSpacing(true);
56+
ipp.SetZSpacingTolerance(1e-3); // ??
57+
bool b = ipp.Sort(subset);
58+
if (!b)
59+
{
60+
// If you reach here this means you need one more parameter to discriminiat this
61+
// series. Eg. T1 / T2 intertwinted. Multiple Echo (0018,0081)
62+
std::cerr << "Failed to sort: " << subset.begin()->c_str() << std::endl;
63+
for (
64+
Directory::FilenamesType::const_iterator file = subset.begin();
65+
file != subset.end(); ++file)
66+
{
67+
std::cerr << *file << std::endl;
68+
}
69+
UnsortedFiles.push_back(subset);
70+
return;
71+
}
72+
ipp.Print(std::cout);
73+
SortedFiles.push_back(ipp.GetFilenames());
74+
}
75+
76+
void ProcessAFrameOfRef(Scanner const &s, Directory::FilenamesType const &subset, const char *frameuid)
77+
{
78+
// In this subset of files (belonging to same series), let's find those
79+
// belonging to the same Frame ref UID:
80+
Directory::FilenamesType files = GetAllFilenamesFromTagToValue(
81+
s, subset, t3, frameuid);
82+
83+
std::set<std::string> iopset;
84+
85+
for (
86+
Directory::FilenamesType::const_iterator file = files.begin();
87+
file != files.end(); ++file)
88+
{
89+
// std::cout << *file << std::endl;
90+
const char *value = s.GetValue(file->c_str(), gdcm::t4);
91+
assert(value);
92+
iopset.insert(value);
93+
}
94+
size_t n = iopset.size();
95+
if (n == 0)
96+
{
97+
assert(files.empty());
98+
return;
99+
}
100+
101+
std::cout << "Frame of Ref: " << frameuid << std::endl;
102+
if (n == 1)
103+
{
104+
ProcessAIOP(s, files, iopset.begin()->c_str());
105+
}
106+
else
107+
{
108+
const char *f = files.begin()->c_str();
109+
std::cerr << "More than one IOP: " << f << std::endl;
110+
// Make sure that there is actually 'n' different IOP
111+
gdcm::DirectionCosines ref;
112+
gdcm::DirectionCosines dc;
113+
for (
114+
std::set<std::string>::const_iterator it = iopset.begin();
115+
it != iopset.end(); ++it)
116+
{
117+
ref.SetFromString(it->c_str());
118+
for (
119+
Directory::FilenamesType::const_iterator file = files.begin();
120+
file != files.end(); ++file)
121+
{
122+
std::string value = s.GetValue(file->c_str(), gdcm::t4);
123+
if (value != it->c_str())
124+
{
125+
dc.SetFromString(value.c_str());
126+
const double crossdot = ref.CrossDot(dc);
127+
const double eps = std::fabs(1. - crossdot);
128+
if (eps < 1e-6)
129+
{
130+
std::cerr << "Problem with IOP discrimination: " << file->c_str()
131+
<< " " << it->c_str() << std::endl;
132+
return;
133+
}
134+
}
135+
}
136+
}
137+
// If we reach here this means there is actually 'n' different IOP
138+
for (
139+
std::set<std::string>::const_iterator it = iopset.begin();
140+
it != iopset.end(); ++it)
141+
{
142+
const char *iopvalue = it->c_str();
143+
Directory::FilenamesType iopfiles = GetAllFilenamesFromTagToValue(
144+
s, files, t4, iopvalue);
145+
ProcessAIOP(s, iopfiles, iopvalue);
146+
}
147+
}
148+
}
149+
150+
void ProcessASeries(Scanner const &s, const char *seriesuid)
151+
{
152+
std::cout << "Series: " << seriesuid << std::endl;
153+
// let's find all files belonging to this series:
154+
Directory::FilenamesType seriesfiles = GetAllFilenamesFromTagToValue(
155+
s, s.GetFilenames(), t2, seriesuid);
156+
157+
gdcm::Scanner::ValuesType vt3 = s.GetValues(t3);
158+
for (
159+
gdcm::Scanner::ValuesType::const_iterator it = vt3.begin(); it != vt3.end(); ++it)
160+
{
161+
ProcessAFrameOfRef(s, seriesfiles, it->c_str());
162+
}
163+
}
164+
165+
void ProcessAStudy(Scanner const &s, const char *studyuid)
166+
{
167+
std::cout << "Study: " << studyuid << std::endl;
168+
gdcm::Scanner::ValuesType vt2 = s.GetValues(t2);
169+
for (
170+
gdcm::Scanner::ValuesType::const_iterator it = vt2.begin(); it != vt2.end(); ++it)
171+
{
172+
ProcessASeries(s, it->c_str());
173+
}
174+
}
175+
176+
public:
177+
void Print(std::ostream &os)
178+
{
179+
os << "Sorted Files: " << std::endl;
180+
for (
181+
std::vector<Directory::FilenamesType>::const_iterator it = SortedFiles.begin();
182+
it != SortedFiles.end(); ++it)
183+
{
184+
os << "Group: " << std::endl;
185+
for (
186+
Directory::FilenamesType::const_iterator file = it->begin();
187+
file != it->end(); ++file)
188+
{
189+
os << *file << std::endl;
190+
}
191+
}
192+
os << "Unsorted Files: " << std::endl;
193+
for (
194+
std::vector<Directory::FilenamesType>::const_iterator it = UnsortedFiles.begin();
195+
it != UnsortedFiles.end(); ++it)
196+
{
197+
os << "Group: " << std::endl;
198+
for (
199+
Directory::FilenamesType::const_iterator file = it->begin();
200+
file != it->end(); ++file)
201+
{
202+
os << *file << std::endl;
203+
}
204+
}
205+
}
206+
207+
std::vector<Directory::FilenamesType> const &GetSortedFiles() const { return SortedFiles; }
208+
std::vector<Directory::FilenamesType> const &GetUnsortedFiles() const { return UnsortedFiles; }
209+
210+
void ProcessIntoVolume(Scanner const &s)
211+
{
212+
gdcm::Scanner::ValuesType vt1 = s.GetValues(gdcm::t1);
213+
for (
214+
gdcm::Scanner::ValuesType::const_iterator it = vt1.begin(); it != vt1.end(); ++it)
215+
{
216+
ProcessAStudy(s, it->c_str());
217+
}
218+
}
219+
};
220+
221+
} // namespace gdcm
222+
223+
// int main(int argc, char *argv[])
224+
// {
225+
// std::string dir1;
226+
// if (argc < 2)
227+
// {
228+
// const char *extradataroot = nullptr;
229+
// #ifdef GDCM_BUILD_TESTING
230+
// extradataroot = gdcm::Testing::GetDataExtraRoot();
231+
// #endif
232+
// if (!extradataroot)
233+
// {
234+
// return 1;
235+
// }
236+
// dir1 = extradataroot;
237+
// dir1 += "/gdcmSampleData/ForSeriesTesting/VariousIncidences/ST1";
238+
// }
239+
// else
240+
// {
241+
// dir1 = argv[1];
242+
// }
243+
244+
// gdcm::Directory d;
245+
// d.Load(dir1, true); // recursive !
246+
247+
// gdcm::Scanner s;
248+
// s.AddTag(gdcm::t1);
249+
// s.AddTag(gdcm::t2);
250+
// s.AddTag(gdcm::t3);
251+
// s.AddTag(gdcm::t4);
252+
// bool b = s.Scan(d.GetFilenames());
253+
// if (!b)
254+
// {
255+
// std::cerr << "Scanner failed" << std::endl;
256+
// return 1;
257+
// }
258+
259+
// gdcm::DiscriminateVolume dv;
260+
// dv.ProcessIntoVolume(s);
261+
// dv.Print(std::cout);
262+
263+
// return 0;
264+
// }

packages/dicom/gdcm/image-sets-normalization.cxx

+50-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include "rapidjson/stringbuffer.h"
2424
#include "rapidjson/writer.h"
2525

26+
#include "discriminate-volume.cxx"
27+
2628
int main(int argc, char *argv[])
2729
{
2830
itk::wasm::Pipeline pipeline("image-sets-normalization", "Group DICOM files into image sets", argc, argv);
@@ -35,13 +37,57 @@ int main(int argc, char *argv[])
3537

3638
ITK_WASM_PARSE(pipeline);
3739

40+
gdcm::Scanner s;
41+
42+
const gdcm::Tag t1(0x0020, 0x000d); // Study Instance UID
43+
const gdcm::Tag t2(0x0020, 0x000e); // Series Instance UID
44+
const gdcm::Tag t3(0x0020, 0x0052); // Frame of Reference UID
45+
const gdcm::Tag t4(0x0020, 0x0037); // Image Orientation (Patient)
46+
47+
s.AddTag(t1);
48+
s.AddTag(t2);
49+
s.AddTag(t3);
50+
s.AddTag(t4);
51+
52+
bool b = s.Scan(files);
53+
if (!b)
54+
{
55+
std::cerr << "Scanner failed" << std::endl;
56+
return 1;
57+
}
58+
59+
gdcm::DiscriminateVolume dv;
60+
dv.ProcessIntoVolume(s);
61+
62+
std::vector<gdcm::Directory::FilenamesType> sortedFiles = dv.GetSortedFiles();
63+
3864
rapidjson::Document imageSetsJson;
39-
imageSetsJson.SetObject();
4065
rapidjson::Document::AllocatorType &allocator = imageSetsJson.GetAllocator();
66+
imageSetsJson.SetObject();
67+
68+
int groupId = 0;
69+
for (
70+
std::vector<gdcm::Directory::FilenamesType>::const_iterator fileNames = sortedFiles.begin();
71+
fileNames != sortedFiles.end(); ++fileNames)
72+
{
73+
groupId++;
74+
rapidjson::Value sortedFileNameArray(rapidjson::kArrayType);
75+
76+
for (
77+
gdcm::Directory::FilenamesType::const_iterator fileName = fileNames->begin();
78+
fileName != fileNames->end(); ++fileName)
79+
{
80+
rapidjson::Value fileNameValue;
81+
fileNameValue.SetString(fileName->c_str(), fileName->size(), allocator);
82+
sortedFileNameArray.PushBack(fileNameValue, allocator);
83+
}
84+
85+
std::string fileName = fileNames->front();
86+
const char* studyInstanceUID = s.GetValue(fileName.c_str(), t1);
4187

42-
rapidjson::Value almostEqualValue;
43-
almostEqualValue.SetBool(false);
44-
imageSetsJson.AddMember("", almostEqualValue, allocator);
88+
rapidjson::Value groupIdKey(studyInstanceUID, allocator);
89+
imageSetsJson.AddMember(groupIdKey, sortedFileNameArray, allocator);
90+
}
4591

4692
rapidjson::StringBuffer stringBuffer;
4793
rapidjson::Writer<rapidjson::StringBuffer> writer(stringBuffer);

packages/dicom/python/itkwasm-dicom-wasi/tests/test_image_sets_normalization.py

+25-4
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,35 @@
33
from .common import test_input_path, test_output_path
44

55

6-
def test_image_sets_normalization():
6+
def test_one_series():
77
files = [
8+
test_input_path / "DicomImageOrientationTest" / "ImageOrientation.3.dcm",
89
test_input_path / "DicomImageOrientationTest" / "ImageOrientation.1.dcm",
910
test_input_path / "DicomImageOrientationTest" / "ImageOrientation.2.dcm",
10-
test_input_path / "DicomImageOrientationTest" / "ImageOrientation.3.dcm",
1111
]
1212

1313
assert files[0].exists()
1414

15-
output_text = image_sets_normalization(files)
16-
assert output_text
15+
image_sets = image_sets_normalization(files)
16+
print(image_sets)
17+
# assert image_sets
18+
19+
assert image_sets == [
20+
str(files[1]),
21+
str(files[2]),
22+
str(files[0]),
23+
]
24+
25+
# def test_two_series():
26+
# files = [
27+
# test_input_path / "DicomImageOrientationTest" / "ImageOrientation.3.dcm",
28+
# test_input_path / "DicomImageOrientationTest" / "ImageOrientation.1.dcm",
29+
# test_input_path / "DicomImageOrientationTest" / "ImageOrientation.2.dcm",
30+
# test_input_path / "dicom-images" / "CT" / "1-1.dcm",
31+
# test_input_path / "dicom-images" / "CT" / "1-2.dcm",
32+
# ]
33+
34+
# assert files[0].exists()
35+
36+
# image_sets = image_sets_normalization(files)
37+
# assert image_sets

0 commit comments

Comments
 (0)