@@ -36,6 +36,7 @@ const std::set<std::string> PATIENT_TAGS = {
3636};
3737
3838const 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}
0 commit comments