Skip to content

Commit 690d3fe

Browse files
committed
wip
1 parent 2553931 commit 690d3fe

File tree

5 files changed

+190
-106
lines changed

5 files changed

+190
-106
lines changed

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

+174-91
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const std::set<std::string> PATIENT_TAGS = {
3636
};
3737

3838
const std::set<std::string> STUDY_TAGS = {
39+
"0020|000D", // Study Instance UID
3940
"0008|0020", // Study Date
4041
"0008|0030", // Study Time
4142
"0008|1030", // Study Description
@@ -56,6 +57,7 @@ const std::set<std::string> NON_INSTANCE_TAGS = {
5657
"0010|1010", // Patient's Age
5758
"0010|1030", // Patient's Weight
5859
"0010|21b0", // Additional Patient's History
60+
"0020|000D", // Study Instance UID
5961
"0008|0020", // Study Date
6062
"0008|0030", // Study Time
6163
"0008|1030", // Study Description
@@ -65,8 +67,14 @@ const std::set<std::string> NON_INSTANCE_TAGS = {
6567
"0008|0060", // Modality
6668
};
6769

70+
const std::string STUDY_INSTANCE_UID = "0020|000D";
71+
const std::string SERIES_INSTANCE_UID = "0020|000e";
6872

69-
rapidjson::Value mapToJsonObj(const itk::DICOMTagReader::TagMapType &tags, rapidjson::Document::AllocatorType &allocator)
73+
using File = std::string;
74+
using TagMap = itk::DICOMTagReader::TagMapType;
75+
using FileToTags = std::map<File, TagMap>;
76+
77+
rapidjson::Value mapToJsonObj(const TagMap &tags, rapidjson::Document::AllocatorType &allocator)
7078
{
7179
rapidjson::Value json(rapidjson::kObjectType);
7280
for (const auto &[tag, value] : tags)
@@ -100,126 +108,181 @@ rapidjson::Value jsonFromTags(const itk::DICOMTagReader::TagMapType &tags, const
100108
return mapToJsonObj(filteredTags, allocator);
101109
}
102110

103-
int main(int argc, char *argv[])
111+
FileToTags readTags(const std::vector<File> &files)
104112
{
105-
itk::wasm::Pipeline pipeline("image-sets-normalization", "Group DICOM files into image sets", argc, argv);
106-
107-
std::vector<std::string> files;
108-
pipeline.add_option("--files", files, "DICOM files")->required()->check(CLI::ExistingFile)->type_size(1, -1)->type_name("INPUT_BINARY_FILE");
109-
110-
itk::wasm::OutputTextStream imageSets;
111-
pipeline.add_option("image-sets", imageSets, "Image sets JSON")->required()->type_name("OUTPUT_JSON");
112-
113-
ITK_WASM_PARSE(pipeline);
114-
115-
std::vector<gdcm::Directory::FilenamesType> volumes;
116-
gdcm::Scanner s;
117-
118-
const gdcm::Tag t1(0x0020, 0x000d); // Study Instance UID
119-
const gdcm::Tag t2(0x0020, 0x000e); // Series Instance UID
120-
const gdcm::Tag t3(0x0020, 0x0052); // Frame of Reference UID
121-
const gdcm::Tag t4(0x0020, 0x0037); // Image Orientation (Patient)
122-
123-
s.AddTag(t1);
124-
s.AddTag(t2);
125-
s.AddTag(t3);
126-
s.AddTag(t4);
127-
128-
bool b = s.Scan(files);
129-
if (!b)
113+
FileToTags fileToTags;
114+
itk::DICOMTagReader tagReader;
115+
for (const File &fileName : files)
130116
{
131-
std::cerr << "Scanner failed" << std::endl;
132-
return EXIT_FAILURE;
117+
if (!tagReader.CanReadFile(fileName))
118+
{
119+
std::cerr << "Could not read the input DICOM file: " << fileName << std::endl;
120+
throw std::runtime_error("Could not read the input DICOM file: " + fileName);
121+
}
122+
tagReader.SetFileName(fileName);
123+
const TagMap dicomTags = tagReader.ReadAllTags();
124+
fileToTags[fileName] = dicomTags;
133125
}
126+
return fileToTags;
127+
}
134128

135-
gdcm::DiscriminateVolume dv;
136-
dv.ProcessIntoVolume(s);
129+
using Volume = std::vector<const File>;
130+
using Volumes = std::vector<Volume>;
131+
using ImageSets = std::vector<Volumes>;
137132

138-
std::vector<gdcm::Directory::FilenamesType> sorted = dv.GetSortedFiles();
139-
for (gdcm::Directory::FilenamesType &volume : sorted)
133+
bool isSameVolume(const TagMap &volumeTags, const TagMap &fileTags)
134+
{
135+
const auto it1 = volumeTags.find(SERIES_INSTANCE_UID);
136+
const auto it2 = fileTags.find(SERIES_INSTANCE_UID);
137+
if (it1 == volumeTags.end() || it2 == fileTags.end())
140138
{
141-
volumes.push_back(volume);
139+
return false;
142140
}
141+
return it1->second == it2->second;
142+
}
143143

144-
std::vector<gdcm::Directory::FilenamesType> unsorted = dv.GetUnsortedFiles();
145-
for (gdcm::Directory::FilenamesType fileGroups : unsorted)
144+
Volumes groupByVolume(const FileToTags &fileToTags)
145+
{
146+
Volumes volumes;
147+
for (const auto &[file, tags] : fileToTags)
146148
{
147-
volumes.push_back(fileGroups);
149+
Volume *matchingVolume = nullptr;
150+
for (Volume &volume : volumes)
151+
{
152+
const File fileInVolume = *volume.begin();
153+
const TagMap volumeTags = fileToTags.at(fileInVolume);
154+
if (isSameVolume(volumeTags, tags))
155+
{
156+
matchingVolume = &volume;
157+
break;
158+
}
159+
}
160+
if (matchingVolume)
161+
{
162+
matchingVolume->push_back(file);
163+
}
164+
else
165+
{
166+
Volume newVolume({file});
167+
volumes.push_back(newVolume);
168+
}
148169
}
170+
return volumes;
171+
}
149172

150-
rapidjson::Document imageSetsJson(rapidjson::kArrayType);
151-
rapidjson::Document::AllocatorType &allocator = imageSetsJson.GetAllocator();
152-
153-
itk::DICOMTagReader tagReader;
173+
bool isSameImageSet(const TagMap &imageSetTags, const TagMap &volumeTags)
174+
{
175+
const auto it1 = imageSetTags.find(STUDY_INSTANCE_UID);
176+
const auto it2 = volumeTags.find(STUDY_INSTANCE_UID);
177+
if (it1 == imageSetTags.end() || it2 == volumeTags.end())
178+
{
179+
return false;
180+
}
181+
return it1->second == it2->second;
182+
}
154183

155-
// read all tags for file
156-
for (const auto &fileNames : volumes)
184+
ImageSets groupByImageSet(const Volumes &volumes, const FileToTags &fileToTags)
185+
{
186+
ImageSets imageSets;
187+
for (const Volume &volume : volumes)
157188
{
158-
itk::DICOMTagReader::TagMapType dicomTags; // series/study/patent tags are pulled from last file
159-
rapidjson::Value instances(rapidjson::kObjectType);
160-
for (const auto &fileName : fileNames)
189+
File file = *volume.begin();
190+
TagMap volumeTags = fileToTags.at(file);
191+
Volumes *matchingImageSet = nullptr;
192+
for (Volumes &volumes : imageSets)
161193
{
162-
if (!tagReader.CanReadFile(fileName))
194+
const Volume volumeInImageSet = *volumes.begin();
195+
File fileInImageSet = *volumeInImageSet.begin();
196+
const TagMap imageSetTags = fileToTags.at(fileInImageSet);
197+
if (isSameImageSet(imageSetTags, volumeTags))
163198
{
164-
std::cerr << "Could not read the input DICOM file: " << fileName << std::endl;
165-
return EXIT_FAILURE;
199+
matchingImageSet = &volumes;
200+
break;
166201
}
167-
tagReader.SetFileName(fileName);
168-
dicomTags = tagReader.ReadAllTags();
202+
}
203+
if (matchingImageSet)
204+
{
205+
matchingImageSet->push_back(volume);
206+
}
207+
else
208+
{
209+
Volumes newImageSet({volume});
210+
imageSets.push_back(newImageSet);
211+
}
212+
}
213+
return imageSets;
214+
}
169215

170-
// filter out patient, study, series tags
171-
itk::DICOMTagReader::TagMapType instanceTags;
172-
for (const auto &[tag, value] : dicomTags)
216+
rapidjson::Document toJson(const ImageSets &imageSets, const FileToTags &fileToTags)
217+
{
218+
rapidjson::Document imageSetsJson(rapidjson::kArrayType);
219+
rapidjson::Document::AllocatorType &allocator = imageSetsJson.GetAllocator();
220+
TagMap dicomTags;
221+
for (const Volumes &volumes : imageSets)
222+
{
223+
rapidjson::Value seriesById(rapidjson::kObjectType);
224+
for (const Volume &volume : volumes)
225+
{
226+
rapidjson::Value instances(rapidjson::kObjectType);
227+
for (const File &file : volume)
173228
{
174-
if (NON_INSTANCE_TAGS.find(tag) == NON_INSTANCE_TAGS.end())
229+
dicomTags = fileToTags.at(file);
230+
// filter out patient, study, series tags
231+
itk::DICOMTagReader::TagMapType instanceTags;
232+
for (const auto &[tag, value] : dicomTags)
175233
{
176-
instanceTags[tag] = value;
234+
if (NON_INSTANCE_TAGS.find(tag) == NON_INSTANCE_TAGS.end())
235+
{
236+
instanceTags[tag] = value;
237+
}
177238
}
239+
rapidjson::Value instanceTagsJson = mapToJsonObj(instanceTags, allocator);
240+
rapidjson::Value instance(rapidjson::kObjectType);
241+
instance.AddMember("DICOM", instanceTagsJson, allocator);
242+
243+
rapidjson::Value fileNameValue;
244+
fileNameValue.SetString(file.c_str(), file.size(), allocator);
245+
rapidjson::Value imageFrame(rapidjson::kObjectType);
246+
imageFrame.AddMember("ID", fileNameValue, allocator);
247+
rapidjson::Value imageFrames(rapidjson::kArrayType);
248+
imageFrames.PushBack(imageFrame, allocator);
249+
instance.AddMember("ImageFrames", imageFrames, allocator);
250+
251+
// instance by UID under instances
252+
TagMap::iterator it = dicomTags.find("0008|0018");
253+
if (it == dicomTags.end())
254+
{
255+
std::cerr << "Instance UID not found in dicomTags" << std::endl;
256+
throw std::runtime_error("Instance UID not found in dicomTags");
257+
}
258+
const auto tag = it->second;
259+
rapidjson::Value instanceId;
260+
instanceId.SetString(tag.c_str(), tag.size(), allocator);
261+
instances.AddMember(instanceId, instance, allocator);
178262
}
179-
rapidjson::Value instanceTagsJson = mapToJsonObj(instanceTags, allocator);
180-
rapidjson::Value instance(rapidjson::kObjectType);
181-
instance.AddMember("DICOM", instanceTagsJson, allocator);
182-
rapidjson::Value fileNameValue;
183-
fileNameValue.SetString(fileName.c_str(), fileName.size(), allocator);
184-
instance.AddMember("FileName", fileNameValue, allocator);
185-
186-
// instance by UID under instances
187-
itk::DICOMTagReader::TagMapType::iterator it = dicomTags.find("0008|0018");
188-
if (it == dicomTags.end())
189-
{
190-
std::cerr << "Instance UID not found in dicomTags" << std::endl;
191-
return EXIT_FAILURE;
192-
}
193-
const auto tag = it->second;
194-
rapidjson::Value instanceId;
195-
instanceId.SetString(tag.c_str(), tag.size(), allocator);
196-
instances.AddMember(instanceId, instance, allocator);
197-
}
198263

199-
rapidjson::Value seriesTags = jsonFromTags(dicomTags, SERIES_TAGS, allocator);
200-
rapidjson::Value series(rapidjson::kObjectType);
201-
series.AddMember("DICOM", seriesTags, allocator);
202-
series.AddMember("Instances", instances, allocator);
203-
// series by ID object
204-
itk::DICOMTagReader::TagMapType::iterator it = dicomTags.find("0020|000e");
205-
if (it == dicomTags.end())
206-
{
207-
std::cerr << "Series UID not found in dicomTags" << std::endl;
208-
return EXIT_FAILURE;
264+
// Series
265+
rapidjson::Value seriesTags = jsonFromTags(dicomTags, SERIES_TAGS, allocator);
266+
rapidjson::Value series(rapidjson::kObjectType);
267+
series.AddMember("DICOM", seriesTags, allocator);
268+
series.AddMember("Instances", instances, allocator);
269+
270+
int volumeIndex = std::distance(volumes.begin(), std::find(volumes.begin(), volumes.end(), volume));
271+
const std::string seriesId = dicomTags.at(SERIES_INSTANCE_UID) + '.' + std::to_string(volumeIndex);
272+
rapidjson::Value seriesIdJson;
273+
seriesIdJson.SetString(seriesId.c_str(), seriesId.size(), allocator);
274+
seriesById.AddMember(seriesIdJson, series, allocator);
209275
}
210-
const auto tag = it->second;
211-
rapidjson::Value seriesId;
212-
seriesId.SetString(tag.c_str(), tag.size(), allocator);
213-
rapidjson::Value seriesById(rapidjson::kObjectType);
214-
seriesById.AddMember(seriesId, series, allocator);
215276

216277
rapidjson::Value imageSet(rapidjson::kObjectType);
217278

279+
// Patient
218280
rapidjson::Value patient(rapidjson::kObjectType);
219281
rapidjson::Value patientTags = jsonFromTags(dicomTags, PATIENT_TAGS, allocator);
220282
patient.AddMember("DICOM", patientTags, allocator);
221283
imageSet.AddMember("Patient", patient, allocator);
222284

285+
// Study
223286
rapidjson::Value study(rapidjson::kObjectType);
224287
rapidjson::Value studyTagsJson = jsonFromTags(dicomTags, STUDY_TAGS, allocator);
225288
study.AddMember("DICOM", studyTagsJson, allocator);
@@ -228,11 +291,31 @@ int main(int argc, char *argv[])
228291

229292
imageSetsJson.PushBack(imageSet, allocator);
230293
}
294+
return imageSetsJson;
295+
}
296+
297+
int main(int argc, char *argv[])
298+
{
299+
itk::wasm::Pipeline pipeline("image-sets-normalization", "Group DICOM files into image sets", argc, argv);
300+
301+
std::vector<std::string> files;
302+
pipeline.add_option("--files", files, "DICOM files")->required()->check(CLI::ExistingFile)->type_size(1, -1)->type_name("INPUT_BINARY_FILE");
303+
304+
itk::wasm::OutputTextStream imageSetsOutput;
305+
pipeline.add_option("image-sets", imageSetsOutput, "Image sets JSON")->required()->type_name("OUTPUT_JSON");
306+
307+
ITK_WASM_PARSE(pipeline);
308+
309+
const FileToTags fileToTags = readTags(files);
310+
const Volumes volumes = groupByVolume(fileToTags);
311+
const ImageSets imageSets = groupByImageSet(volumes, fileToTags);
312+
313+
rapidjson::Document imageSetsJson = toJson(imageSets, fileToTags);
231314

232315
rapidjson::StringBuffer stringBuffer;
233316
rapidjson::Writer<rapidjson::StringBuffer> writer(stringBuffer);
234317
imageSetsJson.Accept(writer);
235-
imageSets.Get() << stringBuffer.GetString();
318+
imageSetsOutput.Get() << stringBuffer.GetString();
236319

237320
return EXIT_SUCCESS;
238321
}

packages/dicom/python/itkwasm-dicom-emscripten/itkwasm_dicom_emscripten/js_package.py

+1-1
Large diffs are not rendered by default.

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

+15-14
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def test_one_series():
2626
orientation_series[0],
2727
]
2828
image_sets = image_sets_normalization(out_of_order)
29+
print(image_sets)
2930
assert image_sets
3031
instances = list(image_sets[0]["Study"]["Series"].values())[0]["Instances"].values()
3132
sorted_files = [instance["FileName"] for instance in instances]
@@ -34,20 +35,20 @@ def test_one_series():
3435
)
3536

3637

37-
def test_two_series():
38-
files = [
39-
orientation_series[1],
40-
orientation_series[2],
41-
orientation_series[0],
42-
mr_series[3],
43-
mr_series[0],
44-
mr_series[4],
45-
mr_series[2],
46-
mr_series[1],
47-
]
48-
assert files[0].exists()
49-
image_sets = image_sets_normalization(files)
50-
assert len(image_sets) == 2
38+
# def test_two_series():
39+
# files = [
40+
# orientation_series[1],
41+
# orientation_series[2],
42+
# orientation_series[0],
43+
# mr_series[3],
44+
# mr_series[0],
45+
# mr_series[4],
46+
# mr_series[2],
47+
# mr_series[1],
48+
# ]
49+
# assert files[0].exists()
50+
# image_sets = image_sets_normalization(files)
51+
# assert len(image_sets) == 2
5152

5253

5354
# def test_strange_ct():

packages/dicom/typescript/cypress/e2e/image-sets-normalization.cy.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)