Medical Imaging Interaction Toolkit  2016.11.0
Medical Imaging Interaction Toolkit
mitkDICOMTagBasedSorter.cpp
Go to the documentation of this file.
1 /*===================================================================
2 The Medical Imaging Interaction Toolkit (MITK)
3 
4 Copyright (c) German Cancer Research Center,
5 Division of Medical and Biological Informatics.
6 All rights reserved.
7 
8 This software is distributed WITHOUT ANY WARRANTY; without
9 even the implied warranty of MERCHANTABILITY or FITNESS FOR
10 A PARTICULAR PURPOSE.
11 
12 See LICENSE.txt or http://www.mitk.org for details.
13 
14 ===================================================================*/
15 
17 
18 #include <algorithm>
19 #include <iomanip>
20 
22 ::CutDecimalPlaces(unsigned int precision)
23 :m_Precision(precision)
24 {
25 }
26 
29 :m_Precision(other.m_Precision)
30 {
31 }
32 
33 std::string
35 ::operator()(const std::string& input) const
36 {
37  // be a bit tolerant for tags such as image orientation orienatation, let only the first few digits matter (http://bugs.mitk.org/show_bug.cgi?id=12263)
38  // iterate all fields, convert each to a number, cut this number as configured, then return a concatenated string with all cut-off numbers
39  std::ostringstream resultString;
40  resultString.str(std::string());
41  resultString.clear();
42  resultString.setf(std::ios::fixed, std::ios::floatfield);
43  resultString.precision(m_Precision);
44 
45  std::stringstream ss(input);
46  ss.str(input);
47  ss.clear();
48  std::string item;
49  double number(0);
50  std::istringstream converter(item);
51  while (std::getline(ss, item, '\\'))
52  {
53  converter.str(item);
54  converter.clear();
55  if (converter >> number && converter.eof())
56  {
57  // converted to double
58  resultString << number;
59  }
60  else
61  {
62  // did not convert to double
63  resultString << item; // just paste the unmodified string
64  }
65 
66  if (!ss.eof())
67  {
68  resultString << "\\";
69  }
70  }
71 
72  return resultString.str();
73 }
74 
77 ::Clone() const
78 {
79  return new CutDecimalPlaces(*this);
80 }
81 
82 
83 unsigned int
86 {
87  return m_Precision;
88 }
89 
93 ,m_StrictSorting(false)
94 ,m_ExpectDistanceOne(false)
95 {
96 }
97 
100 {
101  for(auto ti = m_TagValueProcessor.cbegin();
102  ti != m_TagValueProcessor.cend();
103  ++ti)
104  {
105  delete ti->second;
106  }
107 }
108 
111 :DICOMDatasetSorter(other)
112 ,m_DistinguishingTags( other.m_DistinguishingTags )
113 ,m_SortCriterion( other.m_SortCriterion )
114 ,m_StrictSorting( other.m_StrictSorting )
115 ,m_ExpectDistanceOne( other.m_ExpectDistanceOne )
116 {
117  for(auto ti = other.m_TagValueProcessor.cbegin();
118  ti != other.m_TagValueProcessor.cend();
119  ++ti)
120  {
121  m_TagValueProcessor[ti->first] = ti->second->Clone();
122  }
123 }
124 
128 {
129  if (this != &other)
130  {
132  m_DistinguishingTags = other.m_DistinguishingTags;
133  m_SortCriterion = other.m_SortCriterion;
134  m_StrictSorting = other.m_StrictSorting;
135  m_ExpectDistanceOne = other.m_ExpectDistanceOne;
136 
137  for(auto ti = other.m_TagValueProcessor.cbegin();
138  ti != other.m_TagValueProcessor.cend();
139  ++ti)
140  {
141  m_TagValueProcessor[ti->first] = ti->second->Clone();
142  }
143  }
144  return *this;
145 }
146 
147 bool
149 ::operator==(const DICOMDatasetSorter& other) const
150 {
151  if (const DICOMTagBasedSorter* otherSelf = dynamic_cast<const DICOMTagBasedSorter*>(&other))
152  {
153  if (this->m_StrictSorting != otherSelf->m_StrictSorting) return false;
154  if (this->m_ExpectDistanceOne != otherSelf->m_ExpectDistanceOne) return false;
155 
156  bool allTagsPresentAndEqual(true);
157  if (this->m_DistinguishingTags.size() != otherSelf->m_DistinguishingTags.size())
158  return false;
159 
160  for (auto myTag = this->m_DistinguishingTags.cbegin();
161  myTag != this->m_DistinguishingTags.cend();
162  ++myTag)
163  {
164  allTagsPresentAndEqual &= (std::find( otherSelf->m_DistinguishingTags.cbegin(), otherSelf->m_DistinguishingTags.cend(), *myTag )
165  != otherSelf->m_DistinguishingTags.cend()); // other contains this tags
166  // since size is equal, we don't need to check the inverse
167  }
168 
169  if (!allTagsPresentAndEqual) return false;
170 
171  if (this->m_SortCriterion.IsNotNull() && otherSelf->m_SortCriterion.IsNotNull())
172  {
173  return *(this->m_SortCriterion) == *(otherSelf->m_SortCriterion);
174  }
175  else
176  {
177  return this->m_SortCriterion.IsNull() && otherSelf->m_SortCriterion.IsNull();
178  }
179  }
180  else
181  {
182  return false;
183  }
184 }
185 
186 void
188 ::PrintConfiguration(std::ostream& os, const std::string& indent) const
189 {
190  os << indent << "Tag based sorting "
191  << "(strict=" << (m_StrictSorting?"true":"false")
192  << ", expectDistanceOne=" << (m_ExpectDistanceOne?"true":"false") << "):"
193  << std::endl;
194 
195  for (auto tagIter = m_DistinguishingTags.begin();
196  tagIter != m_DistinguishingTags.end();
197  ++tagIter)
198  {
199  os << indent << " Split on ";
200  tagIter->Print(os);
201  os << std::endl;
202  }
203 
204  DICOMSortCriterion::ConstPointer crit = m_SortCriterion.GetPointer();
205  while (crit.IsNotNull())
206  {
207  os << indent << " Sort by ";
208  crit->Print(os);
209  os << std::endl;
210  crit = crit->GetSecondaryCriterion();
211  }
212 }
213 
214 void
216 ::SetStrictSorting(bool strict)
217 {
218  m_StrictSorting = strict;
219 }
220 
221 bool
224 {
225  return m_StrictSorting;
226 }
227 
228 void
231 {
232  m_ExpectDistanceOne = strict;
233 }
234 
235 bool
238 {
239  return m_ExpectDistanceOne;
240 }
241 
242 
246 {
247  DICOMTagList allTags = m_DistinguishingTags;
248 
249  const DICOMTagList sortingRelevantTags = m_SortCriterion->GetAllTagsOfInterest();
250  allTags.insert( allTags.end(), sortingRelevantTags.cbegin(), sortingRelevantTags.cend() ); // append
251 
252  return allTags;
253 }
254 
258 {
259  return m_DistinguishingTags;
260 }
261 
265 {
266  auto loc = m_TagValueProcessor.find(tag);
267  if (loc != m_TagValueProcessor.cend())
268  {
269  return loc->second;
270  }
271  else
272  {
273  return nullptr;
274  }
275 }
276 
277 void
279 ::AddDistinguishingTag( const DICOMTag& tag, TagValueProcessor* tagValueProcessor )
280 {
281  m_DistinguishingTags.push_back(tag);
282  m_TagValueProcessor[tag] = tagValueProcessor;
283 }
284 
285 void
288 {
289  m_SortCriterion = criterion;
290 }
291 
295 {
296  return m_SortCriterion;
297 }
298 
299 void
302 {
303  // 1. split
304  // 2. sort each group
305  GroupIDToListType groups = this->SplitInputGroups();
306  GroupIDToListType& sortedGroups = this->SortGroups( groups );
307 
308  // 3. define output
309  this->SetNumberOfOutputs(sortedGroups.size());
310  unsigned int outputIndex(0);
311  for (auto groupIter = sortedGroups.cbegin();
312  groupIter != sortedGroups.cend();
313  ++outputIndex, ++groupIter)
314  {
315  this->SetOutput(outputIndex, groupIter->second);
316  }
317 }
318 
319 std::string
322 {
323  // just concatenate all tag values
324  assert(dataset);
325  std::stringstream groupID;
326  groupID << "g";
327  for (auto tagIter = m_DistinguishingTags.cbegin();
328  tagIter != m_DistinguishingTags.cend();
329  ++tagIter)
330  {
331  groupID << tagIter->GetGroup() << tagIter->GetElement(); // make group/element part of the id to cover empty tags
332  DICOMDatasetFinding rawTagValue = dataset->GetTagValueAsString(*tagIter);
333  std::string processedTagValue;
334  if ( m_TagValueProcessor[*tagIter] != nullptr && rawTagValue.isValid)
335  {
336  processedTagValue = (*m_TagValueProcessor[*tagIter])(rawTagValue.value);
337  }
338  else
339  {
340  processedTagValue = rawTagValue.value;
341  }
342  groupID << processedTagValue;
343  }
344  // shorten ID?
345  return groupID.str();
346 }
347 
351 {
352  DICOMDatasetList input = GetInput(); // copy
353 
354  GroupIDToListType listForGroupID;
355 
356  for (auto dsIter = input.cbegin();
357  dsIter != input.cend();
358  ++dsIter)
359  {
360  DICOMDatasetAccess* dataset = *dsIter;
361  assert(dataset);
362 
363  std::string groupID = this->BuildGroupID( dataset );
364  MITK_DEBUG << "Group ID for for " << dataset->GetFilenameIfAvailable() << ": " << groupID;
365  listForGroupID[groupID].push_back(dataset);
366  }
367 
368  MITK_DEBUG << "After tag based splitting: " << listForGroupID.size() << " groups";
369 
370  return listForGroupID;
371 }
372 
376 {
377  if (m_SortCriterion.IsNotNull())
378  {
379  /*
380  Three steps here:
381  1. sort within each group
382  - this may result in orders such as 1 2 3 4 6 7 8 10 12 13 14
383  2. create new groups by enforcing consecutive order within each group
384  - resorts above example like 1 2 3 4 ; 6 7 8 ; 10 ; 12 13 14
385  3. sort all of the groups (not WITHIN each group) by their first frame
386  - if earlier "distinguish" steps created groups like 6 7 8 ; 1 2 3 4 ; 10,
387  then this step would sort them like 1 2 3 4 ; 6 7 8 ; 10
388  */
389 
390  // Step 1: sort within the groups
391  // for each output
392  // sort by all configured tags, use secondary tags when equal or empty
393  // make configurable:
394  // - sorting order (ascending, descending)
395  // - sort numerically
396  // - ... ?
397  unsigned int groupIndex(0);
398  for (auto gIter = groups.begin();
399  gIter != groups.end();
400  ++groupIndex, ++gIter)
401  {
402  DICOMDatasetList& dsList = gIter->second;
403 
404 #ifdef MBILOG_ENABLE_DEBUG
405  MITK_DEBUG << " --------------------------------------------------------------------------------";
406  MITK_DEBUG << " DICOMTagBasedSorter before sorting group : " << groupIndex;
407  for (auto oi = dsList.begin();
408  oi != dsList.cend();
409  ++oi)
410  {
411  MITK_DEBUG << " INPUT : " << (*oi)->GetFilenameIfAvailable();
412  }
413 #endif // #ifdef MBILOG_ENABLE_DEBUG
414 
415 
416  std::sort( dsList.begin(), dsList.end(), ParameterizedDatasetSort( m_SortCriterion ) );
417 
418 #ifdef MBILOG_ENABLE_DEBUG
419  MITK_DEBUG << " --------------------------------------------------------------------------------";
420  MITK_DEBUG << " DICOMTagBasedSorter after sorting group : " << groupIndex;
421  for (auto oi = dsList.cbegin();
422  oi != dsList.cend();
423  ++oi)
424  {
425  MITK_DEBUG << " OUTPUT : " << (*oi)->GetFilenameIfAvailable();
426  }
427  MITK_DEBUG << " --------------------------------------------------------------------------------";
428 
429 #endif // MBILOG_ENABLE_DEBUG
430 
431  }
432 
433  GroupIDToListType consecutiveGroups;
434  if (m_StrictSorting)
435  {
436  // Step 2: create new groups by enforcing consecutive order within each group
437  unsigned int groupIndex(0);
438  for (auto gIter = groups.begin();
439  gIter != groups.end();
440  ++gIter)
441  {
442  std::stringstream groupKey;
443  groupKey << std::setfill('0') << std::setw(6) << groupIndex++;
444 
445  DICOMDatasetList& dsList = gIter->second;
446  DICOMDatasetAccess* previousDS(nullptr);
447  unsigned int dsIndex(0);
448  double constantDistance(0.0);
449  bool constantDistanceInitialized(false);
450  for (auto dataset = dsList.cbegin();
451  dataset != dsList.cend();
452  ++dsIndex, ++dataset)
453  {
454  if (dsIndex >0) // ignore the first dataset, we cannot check any distances yet..
455  {
456  // for the second and every following dataset:
457  // let the sorting criterion calculate a "distance"
458  // if the distance is not 1, split off a new group!
459  const double currentDistance = m_SortCriterion->NumericDistance(previousDS, *dataset);
460  if (constantDistanceInitialized)
461  {
462  if (fabs(currentDistance - constantDistance) < fabs(constantDistance * 0.01)) // ok, deviation of up to 1% of distance is tolerated
463  {
464  // nothing to do, just ok
465  MITK_DEBUG << "Checking currentDistance==" << currentDistance << ": small enough";
466  }
467  //else if (currentDistance < mitk::eps) // close enough to 0
468  else
469  {
470  MITK_DEBUG << "Split consecutive group at index " << dsIndex << " (current distance " << currentDistance << ", constant distance " << constantDistance << ")";
471  // split! this is done by simply creating a new group (key)
472  groupKey.str(std::string());
473  groupKey.clear();
474  groupKey << std::setfill('0') << std::setw(6) << groupIndex++;
475  }
476  }
477  else
478  {
479  // second slice: learn about the expected distance!
480 
481  // heuristic: if distance is an integer, we check for a special case:
482  // if the distance is integer and not 1/-1, then we assume
483  // a missing slice right after the first slice
484  // ==> split off slices
485  // in all other cases: second dataset at this position, no need to split already, we are still learning about the images
486 
487  // addition to the above: when sorting by imagepositions, a distance other than 1 between the first two slices is
488  // not unusual, actually expected... then we should not split
489 
490  if (m_ExpectDistanceOne)
491  {
492  if ((currentDistance - (int)currentDistance == 0.0) && fabs(currentDistance) != 1.0)
493  // exact comparison. An integer should not be expressed as 1.000000000000000000000000001!
494  {
495  MITK_DEBUG << "Split consecutive group at index " << dsIndex << " (special case: expected distance 1 exactly)";
496  groupKey.str(std::string());
497  groupKey.clear();
498  groupKey << std::setfill('0') << std::setw(6) << groupIndex++;
499  }
500  }
501 
502  MITK_DEBUG << "Initialize strict distance to currentDistance=" << currentDistance;
503 
504  constantDistance = currentDistance;
505  constantDistanceInitialized = true;
506  }
507  }
508  consecutiveGroups[groupKey.str()].push_back(*dataset);
509  previousDS = *dataset;
510  }
511  }
512  }
513  else
514  {
515  consecutiveGroups = groups;
516  }
517 
518  // Step 3: sort all of the groups (not WITHIN each group) by their first frame
519  /*
520  build a list-1 of datasets with the first dataset one of each group
521  sort this list-1
522  build a new result list-2:
523  - iterate list-1, for each dataset
524  - find the group that contains this dataset
525  - add this group as the next element to list-2
526  return list-2 as the sorted output
527  */
528  DICOMDatasetList firstSlices;
529  for (auto gIter = consecutiveGroups.cbegin();
530  gIter != consecutiveGroups.cend();
531  ++gIter)
532  {
533  assert(!gIter->second.empty());
534  firstSlices.push_back(gIter->second.front());
535  }
536 
537  std::sort( firstSlices.begin(), firstSlices.end(), ParameterizedDatasetSort( m_SortCriterion ) );
538 
539  GroupIDToListType sortedResultBlocks;
540  unsigned int groupKeyValue(0);
541  for (auto firstSlice = firstSlices.cbegin();
542  firstSlice != firstSlices.cend();
543  ++firstSlice)
544  {
545  for (auto gIter = consecutiveGroups.cbegin();
546  gIter != consecutiveGroups.cend();
547  ++groupKeyValue, ++gIter)
548  {
549  if (gIter->second.front() == *firstSlice)
550  {
551  std::stringstream groupKey;
552  groupKey << std::setfill('0') << std::setw(6) << groupKeyValue; // try more than 999,999 groups and you are doomed (your application already is)
553  sortedResultBlocks[groupKey.str()] = gIter->second;
554  }
555  }
556  }
557 
558  groups = sortedResultBlocks;
559  }
560 
561 #ifdef MBILOG_ENABLE_DEBUG
562  unsigned int groupIndex( 0 );
563  for ( auto gIter = groups.begin(); gIter != groups.end(); ++groupIndex, ++gIter )
564  {
565  DICOMDatasetList& dsList = gIter->second;
566  MITK_DEBUG << " --------------------------------------------------------------------------------";
567  MITK_DEBUG << " DICOMTagBasedSorter after sorting group : " << groupIndex;
568  for ( auto oi = dsList.begin(); oi != dsList.end(); ++oi )
569  {
570  MITK_DEBUG << " OUTPUT : " << ( *oi )->GetFilenameIfAvailable();
571  }
572  MITK_DEBUG << " --------------------------------------------------------------------------------";
573  }
574 #endif // MBILOG_ENABLE_DEBUG
575 
576 
577 
578  return groups;
579 }
580 
583 :m_SortCriterion(criterion)
584 {
585 }
586 
587 bool
590 {
591  assert(left);
592  assert(right);
593  assert(m_SortCriterion.IsNotNull());
594 
595  return m_SortCriterion->IsLeftBeforeRight(left, right);
596 }
void SetStrictSorting(bool strict)
Whether or not groups should be checked for consecutive tag values.
DICOMTagList GetDistinguishingTags() const
virtual void PrintConfiguration(std::ostream &os, const std::string &indent="") const override
Print configuration details into given stream.
void SetExpectDistanceOne(bool strict)
Flag for a special case in "strict sorting". Please see documentation of SetStrictSorting().
The sorting/splitting building-block of DICOMITKSeriesGDCMReader.
void SetSortCriterion(DICOMSortCriterion::ConstPointer criterion)
Define the sorting criterion (which holds seconardy criteria)
std::vector< DICOMTag > DICOMTagList
Definition: mitkDICOMTag.h:64
GroupIDToListType & SortGroups(GroupIDToListType &groups)
Implements the sorting step. Relatively simple implementation thanks to std::sort and a parameterizat...
Representation of a DICOM tag.
Definition: mitkDICOMTag.h:37
#define MITK_DEBUG
Definition: mitkLogMacros.h:26
virtual bool operator==(const DICOMDatasetSorter &other) const override
std::map< std::string, DICOMDatasetList > GroupIDToListType
itk::SmartPointer< const Self > ConstPointer
Helper struct to feed into std::sort, configured via DICOMSortCriterion.
Interface to datasets that is presented to sorting classes such as DICOMDatasetSorter.
GroupIDToListType SplitInputGroups()
Implements the "distiguishing tags". To sort datasets into different groups, a long string will be bu...
void AddDistinguishingTag(const DICOMTag &, TagValueProcessor *tagValueProcessor=nullptr)
Datasets that differ in given tag's value will be sorted into separate outputs.
const TagValueProcessor * GetTagValueProcessorForDistinguishingTag(const DICOMTag &) const
virtual DICOMDatasetFinding GetTagValueAsString(const DICOMTag &tag) const =0
Return a DICOMDatasetFinding instance of the tag. The return containes (if valid) the raw value of th...
TagValueProcessorMap m_TagValueProcessor
virtual std::string operator()(const std::string &) const override
Implements the "processing".
DICOMTagBasedSorter & operator=(const DICOMTagBasedSorter &other)
DICOMSortCriterion::ConstPointer m_SortCriterion
DICOMSortCriterion::ConstPointer GetSortCriterion() const
virtual void Sort() override
Actually sort as described in the Detailed Description.
bool operator()(const mitk::DICOMDatasetAccess *left, const mitk::DICOMDatasetAccess *right)
virtual TagValueProcessor * Clone() const override
virtual DICOMTagList GetTagsOfInterest() override
A list of all the tags needed for processing (facilitates scanning).
Cuts a number after configured number of decimal places. An instance of this class can be used to avo...
virtual std::string GetFilenameIfAvailable() const =0
Return a filename if possible. If DICOM is not read from file but from somewhere else (network...
Sort DICOM datasets based on configurable tags.
Processes tag values before they are compared. These classes could do some kind of normalization such...
DICOMDatasetSorter & operator=(const DICOMDatasetSorter &other)
std::vector< DICOMDatasetAccess * > DICOMDatasetList
std::string BuildGroupID(DICOMDatasetAccess *dataset)
Helper for SplitInputGroups().