Medical Imaging Interaction Toolkit  2018.4.99-f51274ea
Medical Imaging Interaction Toolkit
mitkDICOMTagBasedSorter.cpp
Go to the documentation of this file.
1 /*============================================================================
2 
3 The Medical Imaging Interaction Toolkit (MITK)
4 
5 Copyright (c) German Cancer Research Center (DKFZ)
6 All rights reserved.
7 
8 Use of this source code is governed by a 3-clause BSD license that can be
9 found in the LICENSE file.
10 
11 ============================================================================*/
12 
14 
15 #include <algorithm>
16 #include <iomanip>
17 
19 ::CutDecimalPlaces(unsigned int precision)
20 :m_Precision(precision)
21 {
22 }
23 
26 :m_Precision(other.m_Precision)
27 {
28 }
29 
30 std::string
32 ::operator()(const std::string& input) const
33 {
34  // 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)
35  // iterate all fields, convert each to a number, cut this number as configured, then return a concatenated string with all cut-off numbers
36  std::ostringstream resultString;
37  resultString.str(std::string());
38  resultString.clear();
39  resultString.setf(std::ios::fixed, std::ios::floatfield);
40  resultString.precision(m_Precision);
41 
42  std::stringstream ss(input);
43  ss.str(input);
44  ss.clear();
45  std::string item;
46  double number(0);
47  std::istringstream converter(item);
48  while (std::getline(ss, item, '\\'))
49  {
50  converter.str(item);
51  converter.clear();
52  if (converter >> number && converter.eof())
53  {
54  // converted to double
55  resultString << number;
56  }
57  else
58  {
59  // did not convert to double
60  resultString << item; // just paste the unmodified string
61  }
62 
63  if (!ss.eof())
64  {
65  resultString << "\\";
66  }
67  }
68 
69  return resultString.str();
70 }
71 
74 ::Clone() const
75 {
76  return new CutDecimalPlaces(*this);
77 }
78 
79 
80 unsigned int
83 {
84  return m_Precision;
85 }
86 
92 {
93 }
94 
97 {
98  for(auto ti = m_TagValueProcessor.cbegin();
99  ti != m_TagValueProcessor.cend();
100  ++ti)
101  {
102  delete ti->second;
103  }
104 }
105 
108 :DICOMDatasetSorter(other)
113 {
114  for(auto ti = other.m_TagValueProcessor.cbegin();
115  ti != other.m_TagValueProcessor.cend();
116  ++ti)
117  {
118  m_TagValueProcessor[ti->first] = ti->second->Clone();
119  }
120 }
121 
125 {
126  if (this != &other)
127  {
133 
134  for(auto ti = other.m_TagValueProcessor.cbegin();
135  ti != other.m_TagValueProcessor.cend();
136  ++ti)
137  {
138  m_TagValueProcessor[ti->first] = ti->second->Clone();
139  }
140  }
141  return *this;
142 }
143 
144 bool
146 ::operator==(const DICOMDatasetSorter& other) const
147 {
148  if (const auto* otherSelf = dynamic_cast<const DICOMTagBasedSorter*>(&other))
149  {
150  if (this->m_StrictSorting != otherSelf->m_StrictSorting) return false;
151  if (this->m_ExpectDistanceOne != otherSelf->m_ExpectDistanceOne) return false;
152 
153  bool allTagsPresentAndEqual(true);
154  if (this->m_DistinguishingTags.size() != otherSelf->m_DistinguishingTags.size())
155  return false;
156 
157  for (auto myTag = this->m_DistinguishingTags.cbegin();
158  myTag != this->m_DistinguishingTags.cend();
159  ++myTag)
160  {
161  allTagsPresentAndEqual &= (std::find( otherSelf->m_DistinguishingTags.cbegin(), otherSelf->m_DistinguishingTags.cend(), *myTag )
162  != otherSelf->m_DistinguishingTags.cend()); // other contains this tags
163  // since size is equal, we don't need to check the inverse
164  }
165 
166  if (!allTagsPresentAndEqual) return false;
167 
168  if (this->m_SortCriterion.IsNotNull() && otherSelf->m_SortCriterion.IsNotNull())
169  {
170  return *(this->m_SortCriterion) == *(otherSelf->m_SortCriterion);
171  }
172  else
173  {
174  return this->m_SortCriterion.IsNull() && otherSelf->m_SortCriterion.IsNull();
175  }
176  }
177  else
178  {
179  return false;
180  }
181 }
182 
183 void
185 ::PrintConfiguration(std::ostream& os, const std::string& indent) const
186 {
187  os << indent << "Tag based sorting "
188  << "(strict=" << (m_StrictSorting?"true":"false")
189  << ", expectDistanceOne=" << (m_ExpectDistanceOne?"true":"false") << "):"
190  << std::endl;
191 
192  for (auto tagIter = m_DistinguishingTags.begin();
193  tagIter != m_DistinguishingTags.end();
194  ++tagIter)
195  {
196  os << indent << " Split on ";
197  tagIter->Print(os);
198  os << std::endl;
199  }
200 
201  DICOMSortCriterion::ConstPointer crit = m_SortCriterion.GetPointer();
202  while (crit.IsNotNull())
203  {
204  os << indent << " Sort by ";
205  crit->Print(os);
206  os << std::endl;
207  crit = crit->GetSecondaryCriterion();
208  }
209 }
210 
211 void
213 ::SetStrictSorting(bool strict)
214 {
215  m_StrictSorting = strict;
216 }
217 
218 bool
221 {
222  return m_StrictSorting;
223 }
224 
225 void
228 {
229  m_ExpectDistanceOne = strict;
230 }
231 
232 bool
235 {
236  return m_ExpectDistanceOne;
237 }
238 
239 
243 {
245 
246  if (m_SortCriterion.IsNotNull())
247  {
248  const DICOMTagList sortingRelevantTags = m_SortCriterion->GetAllTagsOfInterest();
249  allTags.insert( allTags.end(), sortingRelevantTags.cbegin(), sortingRelevantTags.cend() ); // append
250  }
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
287 ::SetSortCriterion( DICOMSortCriterion::ConstPointer criterion )
288 {
289  m_SortCriterion = criterion;
290 }
291 
292 mitk::DICOMSortCriterion::ConstPointer
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 
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 
582 ::ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer criterion)
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.
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.
const TagValueProcessor * GetTagValueProcessorForDistinguishingTag(const DICOMTag &) const
void SetSortCriterion(DICOMSortCriterion::ConstPointer criterion)
Define the sorting criterion (which holds seconardy criteria)
std::vector< DICOMTag > DICOMTagList
Definition: mitkDICOMTag.h:59
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:32
#define MITK_DEBUG
Definition: mitkLogMacros.h:22
bool operator==(const DICOMDatasetSorter &other) const override
std::map< std::string, DICOMDatasetList > GroupIDToListType
void SetNumberOfOutputs(unsigned int numberOfOutputs)
static const bool m_DefaultExpectDistanceOne
Helper struct to feed into std::sort, configured via DICOMSortCriterion.
const DICOMDatasetList & GetInput() const
Input for sorting.
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&#39;s value will be sorted into separate outputs.
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
std::string operator()(const std::string &) const override
Implements the "processing".
static const bool m_DefaultStrictSorting
DICOMTagList GetDistinguishingTags() const
DICOMSortCriterion::ConstPointer GetSortCriterion() const
DICOMTagBasedSorter & operator=(const DICOMTagBasedSorter &other)
DICOMSortCriterion::ConstPointer m_SortCriterion
void Sort() override
Actually sort as described in the Detailed Description.
bool operator()(const mitk::DICOMDatasetAccess *left, const mitk::DICOMDatasetAccess *right)
TagValueProcessor * Clone() const override
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.
void SetOutput(unsigned int index, const DICOMDatasetList &output)
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().