@@ -36,6 +36,7 @@ const std::set<std::string> PATIENT_TAGS = {
36
36
};
37
37
38
38
const std::set<std::string> STUDY_TAGS = {
39
+ " 0020|000D" , // Study Instance UID
39
40
" 0008|0020" , // Study Date
40
41
" 0008|0030" , // Study Time
41
42
" 0008|1030" , // Study Description
@@ -56,6 +57,7 @@ const std::set<std::string> NON_INSTANCE_TAGS = {
56
57
" 0010|1010" , // Patient's Age
57
58
" 0010|1030" , // Patient's Weight
58
59
" 0010|21b0" , // Additional Patient's History
60
+ " 0020|000D" , // Study Instance UID
59
61
" 0008|0020" , // Study Date
60
62
" 0008|0030" , // Study Time
61
63
" 0008|1030" , // Study Description
@@ -65,8 +67,14 @@ const std::set<std::string> NON_INSTANCE_TAGS = {
65
67
" 0008|0060" , // Modality
66
68
};
67
69
70
+ const std::string STUDY_INSTANCE_UID = " 0020|000D" ;
71
+ const std::string SERIES_INSTANCE_UID = " 0020|000e" ;
68
72
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)
70
78
{
71
79
rapidjson::Value json (rapidjson::kObjectType );
72
80
for (const auto &[tag, value] : tags)
@@ -100,126 +108,181 @@ rapidjson::Value jsonFromTags(const itk::DICOMTagReader::TagMapType &tags, const
100
108
return mapToJsonObj (filteredTags, allocator);
101
109
}
102
110
103
- int main ( int argc, char *argv[] )
111
+ FileToTags readTags ( const std::vector<File> &files )
104
112
{
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)
130
116
{
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;
133
125
}
126
+ return fileToTags;
127
+ }
134
128
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>;
137
132
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 ())
140
138
{
141
- volumes. push_back (volume) ;
139
+ return false ;
142
140
}
141
+ return it1->second == it2->second ;
142
+ }
143
143
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)
146
148
{
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
+ }
148
169
}
170
+ return volumes;
171
+ }
149
172
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
+ }
154
183
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)
157
188
{
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)
161
193
{
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))
163
198
{
164
- std::cerr << " Could not read the input DICOM file: " << fileName << std::endl ;
165
- return EXIT_FAILURE ;
199
+ matchingImageSet = &volumes ;
200
+ break ;
166
201
}
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
+ }
169
215
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)
173
228
{
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)
175
233
{
176
- instanceTags[tag] = value;
234
+ if (NON_INSTANCE_TAGS.find (tag) == NON_INSTANCE_TAGS.end ())
235
+ {
236
+ instanceTags[tag] = value;
237
+ }
177
238
}
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);
178
262
}
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
- }
198
263
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);
209
275
}
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);
215
276
216
277
rapidjson::Value imageSet (rapidjson::kObjectType );
217
278
279
+ // Patient
218
280
rapidjson::Value patient (rapidjson::kObjectType );
219
281
rapidjson::Value patientTags = jsonFromTags (dicomTags, PATIENT_TAGS, allocator);
220
282
patient.AddMember (" DICOM" , patientTags, allocator);
221
283
imageSet.AddMember (" Patient" , patient, allocator);
222
284
285
+ // Study
223
286
rapidjson::Value study (rapidjson::kObjectType );
224
287
rapidjson::Value studyTagsJson = jsonFromTags (dicomTags, STUDY_TAGS, allocator);
225
288
study.AddMember (" DICOM" , studyTagsJson, allocator);
@@ -228,11 +291,31 @@ int main(int argc, char *argv[])
228
291
229
292
imageSetsJson.PushBack (imageSet, allocator);
230
293
}
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);
231
314
232
315
rapidjson::StringBuffer stringBuffer;
233
316
rapidjson::Writer<rapidjson::StringBuffer> writer (stringBuffer);
234
317
imageSetsJson.Accept (writer);
235
- imageSets .Get () << stringBuffer.GetString ();
318
+ imageSetsOutput .Get () << stringBuffer.GetString ();
236
319
237
320
return EXIT_SUCCESS;
238
321
}
0 commit comments