names the class to be instantiated, currently this can be one of
- DICOMITKSeriesGDCMReader
- ThreeDnTDICOMSeriesReader
Both classes bring simple configuration flags with them and a description of how images are sorted prior to loading.
Flag for DICOMITKSeriesGDCMReader:
fixTiltByShearing="true|false"
Determines whether a potential gantry tilt should be "fixed" by shearing the output image.
Flag for ThreeDnTDICOMSeriesReader:
group3DnT="true|false"
Determines whether images at the same spatial position should be interpreted as 3D+t images.
The tags and describe the basic loading strategy of both
reader mentioned above: first images are divided into incompatible groups (),
and afterwards the images within each group are sorted by means of DICOMSortCriterion, which
most commonly mentions a tag.
- Tag element and group are interpreted as the hexadecimal numbers
+ Tag element and group are interpreted as the exadecimal numbers
found all around the DICOM standard. The numbers can be prepended by a "0x" if this is preferred
by the programmer (but they are taken as hexadecimal in all cases).
+
+ \section DICOMReaderConfigurator_AboutTheFuture About the future evolution of this class
+
+ This first version is hard coded for the current state of the implementation.
+
+ If things should evolve in a way that needs us to splitt off readers for "old" versions,
+ time should be taken to refactor this class.
+
+ Basically, a serializer class should accompany each of the configurable classes. Such
+ serializer classes should be registered and discovered via micro-services (to support extensions).
+ A serializer should offer both methods to serialize a class and to desirialize it again.
+
+ A "version" attribute at the top-level tag should be used to distinguish versions.
+
+ Usually it should be enough to keep DE-serializers for all versions. Writers for the most
+ recent version should be enough.
*/
class DICOMReader_EXPORT DICOMReaderConfigurator : public itk::LightObject
{
public:
mitkClassMacro( DICOMReaderConfigurator, itk::LightObject )
itkNewMacro( DICOMReaderConfigurator )
- DICOMFileReader::Pointer CreateFromConfigFile(const std::string& filename);
- DICOMFileReader::Pointer CreateFromUTF8ConfigString(const std::string& xmlContents);
+ DICOMFileReader::Pointer CreateFromConfigFile(const std::string& filename) const;
+ DICOMFileReader::Pointer CreateFromUTF8ConfigString(const std::string& xmlContents) const;
+
+ std::string CreateConfigStringFromReader(DICOMFileReader::ConstPointer reader) const;
protected:
DICOMReaderConfigurator();
virtual ~DICOMReaderConfigurator();
private:
- DICOMFileReader::Pointer CreateFromTiXmlDocument(TiXmlDocument& doc);
- DICOMTag tagFromXMLElement(TiXmlElement*);
- std::string requiredStringAttribute(TiXmlElement* xmlElement, const std::string& key);
- unsigned int hexStringToUInt(const std::string& s);
+ DICOMFileReader::Pointer CreateFromTiXmlDocument(TiXmlDocument& doc) const;
+ DICOMTag tagFromXMLElement(TiXmlElement*) const;
+ std::string requiredStringAttribute(TiXmlElement* xmlElement, const std::string& key) const;
+ unsigned int hexStringToUInt(const std::string& s) const;
+
+ ThreeDnTDICOMSeriesReader::Pointer ConfigureThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement*) const;
+ DICOMITKSeriesGDCMReader::Pointer ConfigureDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement*) const;
+
+ DICOMSortCriterion::Pointer CreateDICOMSortByTag(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion) const;
+ DICOMSortCriterion::Pointer CreateSortByImagePositionPatient(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion) const;
+
+ mitk::DICOMTagBasedSorter::Pointer CreateDICOMTagBasedSorter(TiXmlElement* element) const;
+
+ TiXmlElement* CreateConfigStringFromReader(const DICOMITKSeriesGDCMReader* reader) const;
+ TiXmlElement* CreateConfigStringFromReader(const ThreeDnTDICOMSeriesReader* reader) const;
+ TiXmlElement* CreateConfigStringFromReader(const ClassicDICOMSeriesReader* reader) const;
+
+ TiXmlElement* CreateConfigStringFromDICOMDatasetSorter(const DICOMTagBasedSorter* sorter) const;
- ThreeDnTDICOMSeriesReader::Pointer ConfigureThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement*);
- DICOMITKSeriesGDCMReader::Pointer ConfigureDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement*);
+ TiXmlElement* CreateConfigStringFromDICOMTag(const DICOMTag& tag) const;
- DICOMSortCriterion::Pointer CreateDICOMSortByTag(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion);
- DICOMSortCriterion::Pointer CreateSortByImagePositionPatient(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion);
+ TiXmlElement* CreateDICOMFileReaderTag(const DICOMFileReader* reader) const;
+ const char* toString(bool) const;
+ std::string toHexString(unsigned int i) const;
};
} // namespace
#endif // mitkDICOMReaderConfigurator_h
diff --git a/Modules/DICOMReader/mitkDICOMSortByTag.cpp b/Modules/DICOMReader/mitkDICOMSortByTag.cpp
index 61b84349c1..7c2e8e8e9a 100644
--- a/Modules/DICOMReader/mitkDICOMSortByTag.cpp
+++ b/Modules/DICOMReader/mitkDICOMSortByTag.cpp
@@ -1,126 +1,146 @@
/*===================================================================
The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
#include "mitkDICOMSortByTag.h"
mitk::DICOMSortByTag
::DICOMSortByTag(const DICOMTag& tag, DICOMSortCriterion::Pointer secondaryCriterion)
:DICOMSortCriterion(secondaryCriterion)
,m_Tag(tag)
{
}
mitk::DICOMSortByTag
::~DICOMSortByTag()
{
}
mitk::DICOMSortByTag
::DICOMSortByTag(const DICOMSortByTag& other )
:DICOMSortCriterion(other)
,m_Tag(other.m_Tag)
{
}
mitk::DICOMSortByTag&
mitk::DICOMSortByTag
::operator=(const DICOMSortByTag& other)
{
if (this != &other)
{
DICOMSortCriterion::operator=(other);
m_Tag = other.m_Tag;
}
return *this;
}
+
+bool
+mitk::DICOMSortByTag
+::operator==(const DICOMSortCriterion& other) const
+{
+ if (const DICOMSortByTag* otherSelf = dynamic_cast(&other))
+ {
+ if (!(this->m_Tag == otherSelf->m_Tag)) return false;
+
+ if (this->m_SecondaryCriterion.IsNull() && otherSelf->m_SecondaryCriterion.IsNull()) return true;
+
+ if (this->m_SecondaryCriterion.IsNull() || otherSelf->m_SecondaryCriterion.IsNull()) return false;
+
+ return *(this->m_SecondaryCriterion) == *(otherSelf->m_SecondaryCriterion);
+ }
+ else
+ {
+ return false;
+ }
+}
void
mitk::DICOMSortByTag
::Print(std::ostream& os) const
{
m_Tag.Print(os);
}
mitk::DICOMTagList
mitk::DICOMSortByTag
::GetTagsOfInterest() const
{
DICOMTagList list;
list.push_back(m_Tag);
return list;
}
bool
mitk::DICOMSortByTag
::IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const
{
return this->NumericCompare(left, right, m_Tag);
}
bool
mitk::DICOMSortByTag
::StringCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right, const DICOMTag& tag) const
{
assert(left);
assert(right);
std::string leftString = left->GetTagValueAsString(tag);
std::string rightString = right->GetTagValueAsString(tag);
if (leftString != rightString)
{
return leftString.compare(rightString) < 0;
}
else
{
return this->NextLevelIsLeftBeforeRight(left, right);
}
}
bool
mitk::DICOMSortByTag
::NumericCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right, const DICOMTag& tag) const
{
assert(left);
assert(right);
std::string leftString = left->GetTagValueAsString(tag);
std::string rightString = right->GetTagValueAsString(tag);
std::istringstream lefti(leftString);
std::istringstream righti(rightString);
double leftDouble(0);
double rightDouble(0);
if ( (lefti >> leftDouble) && (righti >> rightDouble)
&& lefti.eof() && righti.eof() )
{
if (leftDouble != rightDouble) // can we decide?
{
return leftDouble < rightDouble;
}
else // ask secondary criterion
{
return this->NextLevelIsLeftBeforeRight(left, right);
}
}
else // no numerical conversion..
{
return this->StringCompare(left,right, tag); // fallback to string compare
}
}
diff --git a/Modules/DICOMReader/mitkDICOMSortByTag.h b/Modules/DICOMReader/mitkDICOMSortByTag.h
index 33a228fa2e..984a115a4a 100644
--- a/Modules/DICOMReader/mitkDICOMSortByTag.h
+++ b/Modules/DICOMReader/mitkDICOMSortByTag.h
@@ -1,68 +1,70 @@
/*===================================================================
The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
#ifndef mitkDICOMSortByTag_h
#define mitkDICOMSortByTag_h
#include "mitkDICOMSortCriterion.h"
namespace mitk
{
/**
\ingroup DICOMReaderModule
\brief Compare two datasets by the value of a single tag (for use in DICOMTagBasedSorter).
The class will compare the tag values by
1. numerical value if possible (i.e. both datasets have a value that is numerical)
2. alphabetical order otherwise
If the comparison results in equalness, it is refered to the secondary criterion, see
DICOMSortByTag::NextLevelIsLeftBeforeRight().
*/
class DICOMReader_EXPORT DICOMSortByTag : public DICOMSortCriterion
{
public:
mitkClassMacro( DICOMSortByTag, DICOMSortCriterion );
mitkNewMacro1Param( DICOMSortByTag, const DICOMTag& );
mitkNewMacro2Param( DICOMSortByTag, const DICOMTag&, DICOMSortCriterion::Pointer );
virtual DICOMTagList GetTagsOfInterest() const;
virtual bool IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const;
virtual void Print(std::ostream& os) const;
+
+ virtual bool operator==(const DICOMSortCriterion& other) const;
protected:
DICOMSortByTag( const DICOMTag& tag, DICOMSortCriterion::Pointer secondaryCriterion = NULL );
virtual ~DICOMSortByTag();
DICOMSortByTag(const DICOMSortByTag& other);
DICOMSortByTag& operator=(const DICOMSortByTag& other);
bool StringCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right, const DICOMTag& tag) const;
bool NumericCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right, const DICOMTag& tag) const;
private:
DICOMTag m_Tag;
};
}
#endif
diff --git a/Modules/DICOMReader/mitkDICOMSortCriterion.h b/Modules/DICOMReader/mitkDICOMSortCriterion.h
index 8921bd9290..079f7be8f0 100644
--- a/Modules/DICOMReader/mitkDICOMSortCriterion.h
+++ b/Modules/DICOMReader/mitkDICOMSortCriterion.h
@@ -1,76 +1,78 @@
/*===================================================================
The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
#ifndef mitkDICOMSortCriterion_h
#define mitkDICOMSortCriterion_h
#include "itkObjectFactory.h"
#include "mitkCommon.h"
#include "mitkDICOMDatasetAccess.h"
namespace mitk
{
/**
\ingroup DICOMReaderModule
\brief A tag based sorting criterion for use in DICOMTagBasedSorter.
This class is used within std::sort (see DICOMTagBasedSorter::Sort())
and has to answer a simple question by implementing IsLeftBeforeRight().
Each time IsLeftBeforeRight() is called, the method should return whether
the left dataset should be sorted before the right dataset.
Because there are identical tags values quite oftenly, a DICOMSortCriterion
will always hold a secondary DICOMSortCriterion. In cases of equal tag
values, the decision is refered to the secondary criterion.
*/
class DICOMReader_EXPORT DICOMSortCriterion : public itk::LightObject
{
public:
mitkClassMacro( DICOMSortCriterion, itk::LightObject );
/// \brief Tags used for comparison (includes seconary criteria).
DICOMTagList GetAllTagsOfInterest() const;
/// \brief Tags used for comparison.
virtual DICOMTagList GetTagsOfInterest() const = 0;
/// \brief Answer the sorting question.
virtual bool IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const = 0;
/// \brief The fallback criterion.
DICOMSortCriterion::ConstPointer GetSecondaryCriterion() const;
/// brief describe this class in given stream.
virtual void Print(std::ostream& os) const = 0;
+ virtual bool operator==(const DICOMSortCriterion& other) const = 0;
+
protected:
DICOMSortCriterion( DICOMSortCriterion::Pointer secondaryCriterion );
virtual ~DICOMSortCriterion();
bool NextLevelIsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const;
DICOMSortCriterion(const DICOMSortCriterion& other);
DICOMSortCriterion& operator=(const DICOMSortCriterion& other);
DICOMSortCriterion::Pointer m_SecondaryCriterion;
};
}
#endif
diff --git a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp
index b5668d09b9..3243907c0f 100644
--- a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp
+++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp
@@ -1,287 +1,337 @@
/*===================================================================
The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
#include "mitkDICOMTagBasedSorter.h"
#include
mitk::DICOMTagBasedSorter::CutDecimalPlaces
::CutDecimalPlaces(unsigned int precision)
:m_Precision(precision)
{
}
std::string
mitk::DICOMTagBasedSorter::CutDecimalPlaces
::operator()(const std::string& input) const
{
// 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)
// iterate all fields, convert each to a number, cut this number as configured, then return a concatenated string with all cut-off numbers
static std::ostringstream resultString;
resultString.str(std::string());
resultString.clear();
resultString.setf(std::ios::fixed, std::ios::floatfield);
resultString.precision(m_Precision);
static std::stringstream ss(input);
ss.str(input);
ss.clear();
static std::string item;
while (std::getline(ss, item, '\\'))
{
static std::istringstream converter(item);
converter.str(item);
converter.clear();
static double number(0);
if (converter >> number && converter.eof())
{
// converted to double
resultString << number;
}
else
{
// did not convert to double
resultString << item; // just paste the unmodified string
}
if (!ss.eof())
{
resultString << "\\";
}
}
return resultString.str();
}
+unsigned int
+mitk::DICOMTagBasedSorter::CutDecimalPlaces
+::GetPrecision() const
+{
+ return m_Precision;
+}
+
mitk::DICOMTagBasedSorter
::DICOMTagBasedSorter()
:DICOMDatasetSorter()
{
}
mitk::DICOMTagBasedSorter
::~DICOMTagBasedSorter()
{
for(TagValueProcessorMap::iterator ti = m_TagValueProcessor.begin();
ti != m_TagValueProcessor.end();
++ti)
{
delete ti->second;
}
}
mitk::DICOMTagBasedSorter
::DICOMTagBasedSorter(const DICOMTagBasedSorter& other )
:DICOMDatasetSorter(other)
{
}
mitk::DICOMTagBasedSorter&
mitk::DICOMTagBasedSorter
::operator=(const DICOMTagBasedSorter& other)
{
if (this != &other)
{
DICOMDatasetSorter::operator=(other);
}
return *this;
}
+bool
+mitk::DICOMTagBasedSorter
+::operator==(const DICOMDatasetSorter& other) const
+{
+ if (const DICOMTagBasedSorter* otherSelf = dynamic_cast(&other))
+ {
+ return true; // TODO
+ }
+ else
+ {
+ return false;
+ }
+}
+
void
mitk::DICOMTagBasedSorter
::PrintConfiguration(std::ostream& os, const std::string& indent) const
{
os << indent << "Tag based sorting:" << std::endl;
for (DICOMTagList::const_iterator tagIter = m_DistinguishingTags.begin();
tagIter != m_DistinguishingTags.end();
++tagIter)
{
os << indent << " Split on ";
tagIter->Print(os);
os << std::endl;
}
DICOMSortCriterion::ConstPointer crit = m_SortCriterion.GetPointer();
while (crit.IsNotNull())
{
os << indent << " Sort by ";
crit->Print(os);
os << std::endl;
crit = crit->GetSecondaryCriterion();
}
}
mitk::DICOMTagList
mitk::DICOMTagBasedSorter
::GetTagsOfInterest()
{
DICOMTagList allTags = m_DistinguishingTags;
DICOMTagList sortingRelevantTags = m_SortCriterion->GetAllTagsOfInterest();
allTags.insert( allTags.end(), sortingRelevantTags.begin(), sortingRelevantTags.end() ); // append
return allTags;
}
+mitk::DICOMTagList
+mitk::DICOMTagBasedSorter
+::GetDistinguishingTags() const
+{
+ return m_DistinguishingTags;
+}
+
+const mitk::DICOMTagBasedSorter::TagValueProcessor*
+mitk::DICOMTagBasedSorter
+::GetTagValueProcessorForDistinguishingTag(const DICOMTag& tag) const
+{
+ TagValueProcessorMap::const_iterator loc = m_TagValueProcessor.find(tag);
+ if (loc != m_TagValueProcessor.end())
+ {
+ return loc->second;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
void
mitk::DICOMTagBasedSorter
::AddDistinguishingTag( const DICOMTag& tag, TagValueProcessor* tagValueProcessor )
{
m_DistinguishingTags.push_back(tag);
m_TagValueProcessor[tag] = tagValueProcessor;
}
void
mitk::DICOMTagBasedSorter
::SetSortCriterion( DICOMSortCriterion::ConstPointer criterion )
{
m_SortCriterion = criterion;
}
+mitk::DICOMSortCriterion::ConstPointer
+mitk::DICOMTagBasedSorter
+::GetSortCriterion() const
+{
+ return m_SortCriterion;
+}
+
void
mitk::DICOMTagBasedSorter
::Sort()
{
// 1. split
// 2. sort each group
GroupIDToListType groups = this->SplitInputGroups();
GroupIDToListType& sortedGroups = this->SortGroups( groups );
// 3. define output
this->SetNumberOfOutputs(sortedGroups.size());
unsigned int outputIndex(0);
for (GroupIDToListType::iterator groupIter = sortedGroups.begin();
groupIter != sortedGroups.end();
++outputIndex, ++groupIter)
{
this->SetOutput(outputIndex, groupIter->second);
}
}
std::string
mitk::DICOMTagBasedSorter
::BuildGroupID( DICOMDatasetAccess* dataset )
{
// just concatenate all tag values
assert(dataset);
std::stringstream groupID;
groupID << "g";
for (DICOMTagList::iterator tagIter = m_DistinguishingTags.begin();
tagIter != m_DistinguishingTags.end();
++tagIter)
{
groupID << tagIter->GetGroup() << tagIter->GetElement(); // make group/element part of the id to cover empty tags
std::string rawTagValue = dataset->GetTagValueAsString(*tagIter);
std::string processedTagValue;
if ( m_TagValueProcessor[*tagIter] != NULL )
{
processedTagValue = (*m_TagValueProcessor[*tagIter])(rawTagValue);
}
else
{
processedTagValue = rawTagValue;
}
groupID << processedTagValue;
}
// shorten ID?
return groupID.str();
}
mitk::DICOMTagBasedSorter::GroupIDToListType
mitk::DICOMTagBasedSorter
::SplitInputGroups()
{
DICOMDatasetList input = GetInput(); // copy
GroupIDToListType listForGroupID;
for (DICOMDatasetList::iterator dsIter = input.begin();
dsIter != input.end();
++dsIter)
{
DICOMDatasetAccess* dataset = *dsIter;
assert(dataset);
std::string groupID = this->BuildGroupID( dataset );
MITK_DEBUG << "Group ID for for " << dataset->GetFilenameIfAvailable() << ": " << groupID;
listForGroupID[groupID].push_back(dataset);
}
MITK_DEBUG << "After tag based splitting: " << listForGroupID.size() << " groups";
return listForGroupID;
}
mitk::DICOMTagBasedSorter::GroupIDToListType&
mitk::DICOMTagBasedSorter
::SortGroups(GroupIDToListType& groups)
{
if (m_SortCriterion.IsNotNull())
{
// for each output
// sort by all configured tags, use secondary tags when equal or empty
// make configurable:
// - sorting order (ascending, descending)
// - sort numerically
// - ... ?
unsigned int groupIndex(0);
for (GroupIDToListType::iterator gIter = groups.begin();
gIter != groups.end();
++groupIndex, ++gIter)
{
DICOMDatasetList& dsList = gIter->second;
MITK_DEBUG << " --------------------------------------------------------------------------------";
MITK_DEBUG << " DICOMTagBasedSorter before sorting group : " << groupIndex;
for (DICOMDatasetList::iterator oi = dsList.begin();
oi != dsList.end();
++oi)
{
MITK_DEBUG << " INPUT : " << (*oi)->GetFilenameIfAvailable();
}
std::sort( dsList.begin(), dsList.end(), ParameterizedDatasetSort( m_SortCriterion ) );
MITK_DEBUG << " --------------------------------------------------------------------------------";
MITK_DEBUG << " DICOMTagBasedSorter after sorting group : " << groupIndex;
for (DICOMDatasetList::iterator oi = dsList.begin();
oi != dsList.end();
++oi)
{
MITK_DEBUG << " OUTPUT : " << (*oi)->GetFilenameIfAvailable();
}
MITK_DEBUG << " --------------------------------------------------------------------------------";
}
}
return groups;
}
mitk::DICOMTagBasedSorter::ParameterizedDatasetSort
::ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer criterion)
:m_SortCriterion(criterion)
{
}
bool
mitk::DICOMTagBasedSorter::ParameterizedDatasetSort
::operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right)
{
assert(left);
assert(right);
assert(m_SortCriterion.IsNotNull());
return m_SortCriterion->IsLeftBeforeRight(left, right);
}
diff --git a/Modules/DICOMReader/mitkDICOMTagBasedSorter.h b/Modules/DICOMReader/mitkDICOMTagBasedSorter.h
index f054843ab6..176e0d49c0 100644
--- a/Modules/DICOMReader/mitkDICOMTagBasedSorter.h
+++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.h
@@ -1,148 +1,154 @@
/*===================================================================
The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
#ifndef mitkDICOMTagBasedSorter_h
#define mitkDICOMTagBasedSorter_h
#include "mitkDICOMDatasetSorter.h"
#include "mitkDICOMSortCriterion.h"
namespace mitk
{
/**
\ingroup DICOMReaderModule
\brief Sort DICOM datasets based on configurable tags.
This class implements sorting of input DICOM datasets into multiple outputs
as described in \ref DICOMITKSeriesGDCMReader_LoadingStrategy.
The logic of sorting and splitting is most simple and most generic:
1. Datasets will be put into different groups, if they differ in their value of specific tags (defined by AddDistinguishingTag())
- there might be multiple distinguishing tags defined
- tag values might be processed before comparison by means of TagValueProcessor (e.g. round to a number of decimal places)
2. Each of the groups will be sorted by comparing their tag values using multiple DICOMSortCriterion
- DICOMSortCriterion might evaluate a single tag (e.g. Instance Number) or multiple values (as in SortByImagePositionPatient)
- only a single DICOMSortCriterion is defined for DICOMTagBasedSorter, because each DICOMSortCriterion holds a "secondary sort criterion", i.e. an application can define multiple tags for sorting by chaining \link DICOMSortCriterion DICOMSortCriteria \endlink
- applications should make sure that sorting is always defined (to avoid problems with standard containers), e.g. by adding a comparison of filenames or instance UIDs as a last sorting fallback.
*/
class DICOMReader_EXPORT DICOMTagBasedSorter : public DICOMDatasetSorter
{
public:
/**
\brief Processes tag values before they are compared.
These classes could do some kind of normalization such as rounding, lower case formatting, etc.
*/
class DICOMReader_EXPORT TagValueProcessor
{
public:
/// \brief Implements the "processing".
virtual std::string operator()(const std::string&) const = 0;
};
/**
\brief Cuts a number after configured number of decimal places.
An instance of this class can be used to avoid errors when comparing minimally different image orientations.
*/
class DICOMReader_EXPORT CutDecimalPlaces : public TagValueProcessor
{
public:
CutDecimalPlaces(unsigned int precision);
+ unsigned int GetPrecision() const;
virtual std::string operator()(const std::string&) const;
private:
unsigned int m_Precision;
};
mitkClassMacro( DICOMTagBasedSorter, DICOMDatasetSorter )
itkNewMacro( DICOMTagBasedSorter )
/**
\brief Datasets that differ in given tag's value will be sorted into separate outputs.
*/
void AddDistinguishingTag( const DICOMTag&, TagValueProcessor* tagValueProcessor = NULL );
+ DICOMTagList GetDistinguishingTags() const;
+ const TagValueProcessor* GetTagValueProcessorForDistinguishingTag(const DICOMTag&) const;
/**
\brief Define the sorting criterion (which holds seconardy criteria)
*/
void SetSortCriterion( DICOMSortCriterion::ConstPointer criterion );
+ DICOMSortCriterion::ConstPointer GetSortCriterion() const;
/**
\brief A list of all the tags needed for processing (facilitates scanning).
*/
virtual DICOMTagList GetTagsOfInterest();
/**
\brief Actually sort as described in the Detailed Description.
*/
virtual void Sort();
/**
\brief Print configuration details into given stream.
*/
virtual void PrintConfiguration(std::ostream& os, const std::string& indent = "") const;
+
+ virtual bool operator==(const DICOMDatasetSorter& other) const;
protected:
/**
\brief Helper struct to feed into std::sort, configured via DICOMSortCriterion.
*/
struct ParameterizedDatasetSort
{
ParameterizedDatasetSort(DICOMSortCriterion::ConstPointer);
bool operator() (const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right);
DICOMSortCriterion::ConstPointer m_SortCriterion;
};
DICOMTagBasedSorter();
virtual ~DICOMTagBasedSorter();
DICOMTagBasedSorter(const DICOMTagBasedSorter& other);
DICOMTagBasedSorter& operator=(const DICOMTagBasedSorter& other);
/**
\brief Helper for SplitInputGroups().
*/
std::string BuildGroupID( DICOMDatasetAccess* dataset );
typedef std::map GroupIDToListType;
/**
\brief Implements the "distiguishing tags".
To sort datasets into different groups, a long string will be built for each dataset. The string concatenates all tags and their respective values.
Datasets that match in all values will end up with the same string.
*/
GroupIDToListType SplitInputGroups();
/**
\brief Implements the sorting step.
Relatively simple implementation thanks to std::sort and a parameterization via DICOMSortCriterion.
*/
GroupIDToListType& SortGroups(GroupIDToListType& groups);
DICOMTagList m_DistinguishingTags;
typedef std::map TagValueProcessorMap;
TagValueProcessorMap m_TagValueProcessor;
DICOMSortCriterion::ConstPointer m_SortCriterion;
};
}
#endif
diff --git a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp
index 100c4cb946..5c7846997b 100644
--- a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp
+++ b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp
@@ -1,537 +1,572 @@
/*=================================================================== The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
//#define MBILOG_ENABLE_DEBUG
#include "mitkEquiDistantBlocksSorter.h"
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
::SliceGroupingAnalysisResult()
{
}
mitk::DICOMDatasetList
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
::GetBlockDatasets()
{
return m_GroupedFiles;
}
mitk::DICOMDatasetList
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
::GetUnsortedDatasets()
{
return m_UnsortedFiles;
}
bool
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
::ContainsGantryTilt()
{
return m_TiltInfo.IsRegularGantryTilt();
}
void
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
::AddFileToSortedBlock(DICOMDatasetAccess* dataset)
{
m_GroupedFiles.push_back( dataset );
}
void
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
::AddFileToUnsortedBlock(DICOMDatasetAccess* dataset)
{
m_UnsortedFiles.push_back( dataset );
}
void
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
::AddFilesToUnsortedBlock(const DICOMDatasetList& datasets)
{
m_UnsortedFiles.insert( m_UnsortedFiles.end(), datasets.begin(), datasets.end() );
}
void
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
::SetFirstFilenameOfBlock(const std::string& filename)
{
m_FirstFilenameOfBlock = filename;
}
std::string
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
::GetFirstFilenameOfBlock() const
{
return m_FirstFilenameOfBlock;
}
void
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
::SetLastFilenameOfBlock(const std::string& filename)
{
m_LastFilenameOfBlock = filename;
}
std::string
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
::GetLastFilenameOfBlock() const
{
return m_LastFilenameOfBlock;
}
void
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
::FlagGantryTilt(const GantryTiltInformation& tiltInfo)
{
m_TiltInfo = tiltInfo;
}
const mitk::GantryTiltInformation&
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
::GetTiltInfo() const
{
return m_TiltInfo;
}
void
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
::UndoPrematureGrouping()
{
assert( !m_GroupedFiles.empty() );
m_UnsortedFiles.insert( m_UnsortedFiles.begin(), m_GroupedFiles.back() );
m_GroupedFiles.pop_back();
m_TiltInfo = GantryTiltInformation();
}
// ------------------------ end helper class
mitk::EquiDistantBlocksSorter
::EquiDistantBlocksSorter()
:DICOMDatasetSorter()
,m_AcceptTilt(false)
-,m_ToleratedOriginOffset(-1.0) // convention: negative values mean "adaptive"
+,m_ToleratedOriginOffset(0.3)
+,m_ToleratedOriginOffsetIsAbsolute(false)
{
}
mitk::EquiDistantBlocksSorter
::EquiDistantBlocksSorter(const EquiDistantBlocksSorter& other )
:DICOMDatasetSorter(other)
,m_AcceptTilt(other.m_AcceptTilt)
,m_ToleratedOriginOffset(other.m_ToleratedOriginOffset)
+,m_ToleratedOriginOffsetIsAbsolute(other.m_ToleratedOriginOffsetIsAbsolute)
{
}
mitk::EquiDistantBlocksSorter
::~EquiDistantBlocksSorter()
{
}
+bool
+mitk::EquiDistantBlocksSorter
+::operator==(const DICOMDatasetSorter& other) const
+{
+ if (const EquiDistantBlocksSorter* otherSelf = dynamic_cast(&other))
+ {
+ return this->m_AcceptTilt == otherSelf->m_AcceptTilt
+ && this->m_ToleratedOriginOffsetIsAbsolute == otherSelf->m_ToleratedOriginOffsetIsAbsolute
+ && (fabs(this->m_ToleratedOriginOffset - otherSelf->m_ToleratedOriginOffset) < eps);
+ }
+ else
+ {
+ return false;
+ }
+}
+
void
mitk::EquiDistantBlocksSorter
::PrintConfiguration(std::ostream& os, const std::string& indent) const
{
std::stringstream ts;
- if (m_ToleratedOriginOffset < 0.0)
+ if (!m_ToleratedOriginOffsetIsAbsolute)
{
ts << "adaptive";
}
else
{
ts << m_ToleratedOriginOffset << "mm";
}
os << indent << "Sort into blocks of equidistant, well-aligned (tolerance "
<< ts.str() << ") slices "
<< (m_AcceptTilt ? "(accepting a gantry tilt)" : "")
<< std::endl;
}
void
mitk::EquiDistantBlocksSorter
::SetAcceptTilt(bool accept)
{
m_AcceptTilt = accept;
}
mitk::EquiDistantBlocksSorter&
mitk::EquiDistantBlocksSorter
::operator=(const EquiDistantBlocksSorter& other)
{
if (this != &other)
{
DICOMDatasetSorter::operator=(other);
m_AcceptTilt = other.m_AcceptTilt;
m_ToleratedOriginOffset = other.m_ToleratedOriginOffset;
+ m_ToleratedOriginOffsetIsAbsolute = other.m_ToleratedOriginOffsetIsAbsolute;
}
return *this;
}
mitk::DICOMTagList
mitk::EquiDistantBlocksSorter
::GetTagsOfInterest()
{
DICOMTagList tags;
tags.push_back( DICOMTag(0x0020, 0x0032) ); // ImagePositionPatient
tags.push_back( DICOMTag(0x0020, 0x0037) ); // ImageOrientationPatient
tags.push_back( DICOMTag(0x0018, 0x1120) ); // GantryTilt
return tags;
}
void
mitk::EquiDistantBlocksSorter
::Sort()
{
DICOMDatasetList remainingInput = GetInput(); // copy
typedef std::list OutputListType;
OutputListType outputs;
m_SliceGroupingResults.clear();
while (!remainingInput.empty()) // repeat until all files are grouped somehow
{
SliceGroupingAnalysisResult regularBlock = this->AnalyzeFileForITKImageSeriesReaderSpacingAssumption( remainingInput, m_AcceptTilt );
DICOMDatasetList inBlock = regularBlock.GetBlockDatasets();
DICOMDatasetList laterBlock = regularBlock.GetUnsortedDatasets();
MITK_DEBUG << "Result: sorted 3D group with " << inBlock.size() << " files";
for (DICOMDatasetList::const_iterator diter = inBlock.begin(); diter != inBlock.end(); ++diter)
MITK_DEBUG << " IN " << (*diter)->GetFilenameIfAvailable();
for (DICOMDatasetList::const_iterator diter = laterBlock.begin(); diter != laterBlock.end(); ++diter)
MITK_DEBUG << " OUT " << (*diter)->GetFilenameIfAvailable();
outputs.push_back( regularBlock.GetBlockDatasets() );
m_SliceGroupingResults.push_back( regularBlock );
remainingInput = regularBlock.GetUnsortedDatasets();
}
unsigned int numberOfOutputs = outputs.size();
this->SetNumberOfOutputs(numberOfOutputs);
unsigned int outputIndex(0);
for (OutputListType::iterator oIter = outputs.begin();
oIter != outputs.end();
++outputIndex, ++oIter)
{
this->SetOutput(outputIndex, *oIter);
}
}
void
mitk::EquiDistantBlocksSorter
::SetToleratedOriginOffsetToAdaptive(double fractionOfInterSliceDistance)
{
- m_ToleratedOriginOffset = -fractionOfInterSliceDistance; // convention: negative values mean "adaptive"
- if (m_ToleratedOriginOffset > 0.0)
+ m_ToleratedOriginOffset = fractionOfInterSliceDistance;
+ m_ToleratedOriginOffsetIsAbsolute = false;
+
+ if (m_ToleratedOriginOffset < 0.0)
{
MITK_WARN << "Call SetToleratedOriginOffsetToAdaptive() only with positive numbers between 0.0 and 1.0, read documentation!";
}
- if (m_ToleratedOriginOffset < -0.5)
+ if (m_ToleratedOriginOffset > 0.5)
{
MITK_WARN << "EquiDistantBlocksSorter is now accepting large errors, take care of measurements, they could appear at unprecise locations!";
}
}
void
mitk::EquiDistantBlocksSorter
::SetToleratedOriginOffset(double millimeters)
{
m_ToleratedOriginOffset = millimeters;
+ m_ToleratedOriginOffsetIsAbsolute = true;
if (m_ToleratedOriginOffset < 0.0)
{
- MITK_WARN << "Call SetToleratedOriginOffsetToAdaptive() instead of passing negative numbers to SetToleratedOriginOffsetToAdaptive()!";
+ MITK_WARN << "Negative tolerance set to SetToleratedOriginOffset()!";
}
}
+double
+mitk::EquiDistantBlocksSorter
+::GetToleratedOriginOffset() const
+{
+ return m_ToleratedOriginOffset;
+}
+
+bool
+mitk::EquiDistantBlocksSorter
+::IsToleratedOriginOffsetAbsolute() const
+{
+ return m_ToleratedOriginOffsetIsAbsolute;
+}
+
std::string
mitk::EquiDistantBlocksSorter
::ConstCharStarToString(const char* s)
{
return s ? std::string(s) : std::string();
}
mitk::EquiDistantBlocksSorter::SliceGroupingAnalysisResult
mitk::EquiDistantBlocksSorter
::AnalyzeFileForITKImageSeriesReaderSpacingAssumption(
const DICOMDatasetList& datasets,
bool groupImagesWithGantryTilt)
{
// result.first = files that fit ITK's assumption
// result.second = files that do not fit, should be run through AnalyzeFileForITKImageSeriesReaderSpacingAssumption() again
SliceGroupingAnalysisResult result;
// we const_cast here, because I could not use a map.at(), which would make the code much more readable
const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient)
const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation
const DICOMTag tagGantryTilt = DICOMTag(0x0018, 0x1120); // gantry tilt
Vector3D fromFirstToSecondOrigin; fromFirstToSecondOrigin.Fill(0.0);
bool fromFirstToSecondOriginInitialized(false);
Point3D thisOrigin;
thisOrigin.Fill(0.0f);
Point3D lastOrigin;
lastOrigin.Fill(0.0f);
Point3D lastDifferentOrigin;
lastDifferentOrigin.Fill(0.0f);
bool lastOriginInitialized(false);
MITK_DEBUG << "--------------------------------------------------------------------------------";
MITK_DEBUG << "Analyzing " << datasets.size() << " files for z-spacing assumption of ITK's ImageSeriesReader (group tilted: " << groupImagesWithGantryTilt << ")";
unsigned int fileIndex(0);
- bool adaptiveErrorTolerance = m_ToleratedOriginOffset < 0.0; // convention: negative values: adaptive/percentage of slice distance ; positive: absolute in millimeters
double toleratedOriginError(0.005); // default: max. 1/10mm error when measurement crosses 20 slices in z direction (too strict? we don't know better)
for (DICOMDatasetList::const_iterator dsIter = datasets.begin();
dsIter != datasets.end();
++dsIter, ++fileIndex)
{
bool fileFitsIntoPattern(false);
std::string thisOriginString;
// Read tag value into point3D. PLEASE replace this by appropriate GDCM code if you figure out how to do that
thisOriginString = (*dsIter)->GetTagValueAsString( tagImagePositionPatient );
if (thisOriginString.empty())
{
// don't let such files be in a common group. Everything without position information will be loaded as a single slice:
// with standard DICOM files this can happen to: CR, DX, SC
MITK_DEBUG << " ==> Sort away " << *dsIter << " for later analysis (no position information)"; // we already have one occupying this position
if ( result.GetBlockDatasets().empty() ) // nothing WITH position information yet
{
// ==> this is a group of its own, stop processing, come back later
result.AddFileToSortedBlock( *dsIter );
DICOMDatasetList remainingFiles;
remainingFiles.insert( remainingFiles.end(), dsIter+1, datasets.end() );
result.AddFilesToUnsortedBlock( remainingFiles );
fileFitsIntoPattern = false;
break; // no files anymore
}
else
{
// ==> this does not match, consider later
result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis
fileFitsIntoPattern = false;
continue; // next file
}
}
bool ignoredConversionError(-42); // hard to get here, no graceful way to react
thisOrigin = DICOMStringToPoint3D( thisOriginString, ignoredConversionError );
MITK_DEBUG << " " << fileIndex << " " << (*dsIter)->GetFilenameIfAvailable()
<< " at "
/* << thisOriginString */ << "(" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")";
if ( lastOriginInitialized && (thisOrigin == lastOrigin) )
{
MITK_DEBUG << " ==> Sort away " << *dsIter << " for separate time step"; // we already have one occupying this position
result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis
fileFitsIntoPattern = false;
}
else
{
if (!fromFirstToSecondOriginInitialized && lastOriginInitialized) // calculate vector as soon as possible when we get a new position
{
fromFirstToSecondOrigin = thisOrigin - lastDifferentOrigin;
fromFirstToSecondOriginInitialized = true;
// classic mode without tolerance!
- if (adaptiveErrorTolerance)
+ if (!m_ToleratedOriginOffsetIsAbsolute)
{
MITK_DEBUG << "Distance of two slices: " << fromFirstToSecondOrigin.GetNorm() << "mm";
toleratedOriginError =
fromFirstToSecondOrigin.GetNorm() * 0.3; // a third of the slice distance
// (less than half, which would mean that a slice is displayed where another slice should actually be)
}
else
{
toleratedOriginError = m_ToleratedOriginOffset;
}
MITK_DEBUG << "Accepting errors in actual versus expected origin up to " << toleratedOriginError << "mm";
// Here we calculate if this slice and the previous one are well aligned,
// i.e. we test if the previous origin is on a line through the current
// origin, directed into the normal direction of the current slice.
// If this is NOT the case, then we have a data set with a TILTED GANTRY geometry,
// which cannot be simply loaded into a single mitk::Image at the moment.
// For this case, we flag this finding in the result and DicomSeriesReader
// can correct for that later.
Vector3D right; right.Fill(0.0);
Vector3D up; right.Fill(0.0); // might be down as well, but it is just a name at this point
std::string orientationValue = (*dsIter)->GetTagValueAsString( tagImageOrientation );
DICOMStringToOrientationVectors( orientationValue, right, up, ignoredConversionError );
GantryTiltInformation tiltInfo( lastDifferentOrigin, thisOrigin, right, up, 1 );
if ( tiltInfo.IsSheared() )
{
/* optimistic approach, accepting gantry tilt: save file for later, check all further files */
// at this point we have TWO slices analyzed! if they are the only two files, we still split, because there is no third to verify our tilting assumption.
// later with a third being available, we must check if the initial tilting vector is still valid. if yes, continue.
// if NO, we need to split the already sorted part (result.first) and the currently analyzed file (*dsIter)
// tell apart gantry tilt from overall skewedness
// sort out irregularly sheared slices, that IS NOT tilting
if ( groupImagesWithGantryTilt && tiltInfo.IsRegularGantryTilt() )
{
// check if this is at least roughly the same angle as recorded in DICOM tags
double angle = 0.0;
std::string tiltStr = (*dsIter)->GetTagValueAsString( tagGantryTilt );
std::istringstream i(tiltStr);
if (i >> angle)
{
MITK_DEBUG << "Comparing recorded tilt angle " << angle << " against calculated value " << tiltInfo.GetTiltAngleInDegrees();
// TODO we probably want the signs correct, too (that depends: this is just a rough check, nothing serious)
if ( fabs(angle) - tiltInfo.GetTiltAngleInDegrees() > 0.25)
{
result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis
fileFitsIntoPattern = false;
}
else // tilt angle from header is less than 0.25 degrees different from what we calculated, assume this is fine
{
assert(!datasets.empty());
result.FlagGantryTilt(tiltInfo);
result.AddFileToSortedBlock( *dsIter ); // this file is good for current block
result.SetFirstFilenameOfBlock( datasets.front()->GetFilenameIfAvailable() );
result.SetLastFilenameOfBlock( datasets.back()->GetFilenameIfAvailable() );
fileFitsIntoPattern = true;
}
}
else // we cannot check the calculated tilt angle against the one from the dicom header (so we assume we are right)
{
assert(!datasets.empty());
result.FlagGantryTilt(tiltInfo);
result.AddFileToSortedBlock( *dsIter ); // this file is good for current block
result.SetFirstFilenameOfBlock( datasets.front()->GetFilenameIfAvailable() );
result.SetLastFilenameOfBlock( datasets.back()->GetFilenameIfAvailable() );
fileFitsIntoPattern = true;
}
}
else // caller does not want tilt compensation OR shearing is more complicated than tilt
{
result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis
fileFitsIntoPattern = false;
}
}
else // not sheared
{
result.AddFileToSortedBlock( *dsIter ); // this file is good for current block
fileFitsIntoPattern = true;
}
}
else if (fromFirstToSecondOriginInitialized) // we already know the offset between slices
{
Point3D assumedOrigin = lastDifferentOrigin + fromFirstToSecondOrigin;
Vector3D originError = assumedOrigin - thisOrigin;
double norm = originError.GetNorm();
if (norm > toleratedOriginError)
{
MITK_DEBUG << " File does not fit into the inter-slice distance pattern (diff = "
<< norm << ", allowed "
<< toleratedOriginError << ").";
MITK_DEBUG << " Expected position (" << assumedOrigin[0] << ","
<< assumedOrigin[1] << ","
<< assumedOrigin[2] << "), got position ("
<< thisOrigin[0] << ","
<< thisOrigin[1] << ","
<< thisOrigin[2] << ")";
MITK_DEBUG << " ==> Sort away " << *dsIter << " for later analysis";
// At this point we know we deviated from the expectation of ITK's ImageSeriesReader
// We split the input file list at this point, i.e. all files up to this one (excluding it)
// are returned as group 1, the remaining files (including the faulty one) are group 2
/* Optimistic approach: check if any of the remaining slices fits in */
result.AddFileToUnsortedBlock( *dsIter ); // sort away for further analysis
fileFitsIntoPattern = false;
}
else
{
result.AddFileToSortedBlock( *dsIter ); // this file is good for current block
fileFitsIntoPattern = true;
}
}
else // this should be the very first slice
{
result.AddFileToSortedBlock( *dsIter ); // this file is good for current block
fileFitsIntoPattern = true;
}
}
// record current origin for reference in later iterations
if ( !lastOriginInitialized || ( fileFitsIntoPattern && (thisOrigin != lastOrigin) ) )
{
lastDifferentOrigin = thisOrigin;
}
lastOrigin = thisOrigin;
lastOriginInitialized = true;
}
if ( result.ContainsGantryTilt() )
{
// check here how many files were grouped.
// IF it was only two files AND we assume tiltedness (e.g. save "distance")
// THEN we would want to also split the two previous files (simple) because
// we don't have any reason to assume they belong together
if ( result.GetBlockDatasets().size() == 2 )
{
result.UndoPrematureGrouping();
}
}
// update tilt info to get maximum precision
// earlier, tilt was only calculated from first and second slice.
// now that we know the whole range, we can re-calculate using the very first and last slice
if ( result.ContainsGantryTilt() && result.GetBlockDatasets().size() > 1 )
{
try
{
DICOMDatasetList datasets = result.GetBlockDatasets();
DICOMDatasetAccess* firstDataset = datasets.front();
DICOMDatasetAccess* lastDataset = datasets.back();
unsigned int numberOfSlicesApart = datasets.size() - 1;
std::string orientationString = firstDataset->GetTagValueAsString( tagImageOrientation );
std::string firstOriginString = firstDataset->GetTagValueAsString( tagImagePositionPatient );
std::string lastOriginString = lastDataset->GetTagValueAsString( tagImagePositionPatient );
result.FlagGantryTilt( GantryTiltInformation::MakeFromTagValues( firstOriginString, lastOriginString, orientationString, numberOfSlicesApart ));
}
catch (...)
{
// just do not flag anything, we are ok
}
}
return result;
}
diff --git a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h
index 66d056f64b..92a4790ae0 100644
--- a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h
+++ b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h
@@ -1,204 +1,210 @@
/*===================================================================
The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
#ifndef mitkEquiDistantBlocksSorter_h
#define mitkEquiDistantBlocksSorter_h
#include "mitkDICOMDatasetSorter.h"
#include "mitkDICOMSortCriterion.h"
#include "mitkGantryTiltInformation.h"
#include "mitkVector.h"
#include
namespace mitk
{
/**
\ingroup DICOMReaderModule
\brief Split inputs into blocks of equidistant slices (for use in DICOMITKSeriesGDCMReader).
Since inter-slice distance is not recorded in DICOM tags, we must ensure that blocks are made up of
slices that have equal distances between neighboring slices. This is especially necessary because itk::ImageSeriesReader
is later used for the actual loading, and this class expects (and does nocht verify) equal inter-slice distance (see \ref DICOMITKSeriesGDCMReader_ForcedConfiguration).
To achieve such grouping, the inter-slice distance is calculated from the first two different slice positions of a block.
Following slices are added to a block as long as they can be added by adding the calculated inter-slice distance to the
last slice of the block. Slices that do not fit into the expected distance pattern, are set aside for further analysis.
This grouping is done until each file has been assigned to a group.
Slices that share a position in space are also sorted into separate blocks during this step.
So the result of this step is a set of blocks that contain only slices with equal z spacing
and uniqe slices at each position.
During sorting, the origins (documented in tag image position patient) are compared
against expected origins (from former origin plus moving direction). As there will
be minor differences in numbers (from both calculations and unprecise tag values),
we must be a bit tolerant here. The default behavior is to expect that an origin is
not further away from the expected position than 30% of the inter-slice distance.
To support a legacy behavior of a former loader (DicomSeriesReader), this default can
be restricted to a constant number of millimeters by calling SetToleratedOriginOffset(mm).
Detailed implementation in AnalyzeFileForITKImageSeriesReaderSpacingAssumption().
*/
class DICOMReader_EXPORT EquiDistantBlocksSorter : public DICOMDatasetSorter
{
public:
mitkClassMacro( EquiDistantBlocksSorter, DICOMDatasetSorter )
itkNewMacro( EquiDistantBlocksSorter )
virtual DICOMTagList GetTagsOfInterest();
/**
\brief Delegates work to AnalyzeFileForITKImageSeriesReaderSpacingAssumption().
AnalyzeFileForITKImageSeriesReaderSpacingAssumption() is called until it does not
create multiple blocks anymore.
*/
virtual void Sort();
/**
\brief Whether or not to accept images from a tilted acquisition in a single output group.
*/
void SetAcceptTilt(bool accept);
/**
\brief See class description and SetToleratedOriginOffset().
*/
void SetToleratedOriginOffsetToAdaptive(double fractionOfInterSliceDistanct = 0.3);
/**
\brief See class description and SetToleratedOriginOffsetToAdaptive().
Default value of 0.005 is calculated so that we get a maximum of 1/10mm
error when having a measurement crosses 20 slices in z direction (too strict? we don't know better..).
*/
void SetToleratedOriginOffset(double millimeters = 0.005);
+ double GetToleratedOriginOffset() const;
+ bool IsToleratedOriginOffsetAbsolute() const;
+
virtual void PrintConfiguration(std::ostream& os, const std::string& indent = "") const;
+ virtual bool operator==(const DICOMDatasetSorter& other) const;
+
protected:
/**
\brief Return type of AnalyzeFileForITKImageSeriesReaderSpacingAssumption().
Class contains the grouping result of method AnalyzeFileForITKImageSeriesReaderSpacingAssumption(),
which takes as input a number of images, which are all equally oriented and spatially sorted along their normal direction.
The result contains of two blocks: a first one is the grouping result, all of those images can be loaded
into one image block because they have an equal origin-to-origin distance without any gaps in-between.
*/
class SliceGroupingAnalysisResult
{
public:
SliceGroupingAnalysisResult();
/**
\brief Grouping result, all same origin-to-origin distance w/o gaps.
*/
DICOMDatasetList GetBlockDatasets();
void SetFirstFilenameOfBlock(const std::string& filename);
std::string GetFirstFilenameOfBlock() const;
void SetLastFilenameOfBlock(const std::string& filename);
std::string GetLastFilenameOfBlock() const;
/**
\brief Remaining files, which could not be grouped.
*/
DICOMDatasetList GetUnsortedDatasets();
/**
\brief Wheter or not the grouped result contain a gantry tilt.
*/
bool ContainsGantryTilt();
/**
\brief Detailed description of gantry tilt.
*/
const GantryTiltInformation& GetTiltInfo() const;
/**
\brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only.
*/
void AddFileToSortedBlock(DICOMDatasetAccess* dataset);
/**
\brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only.
*/
void AddFileToUnsortedBlock(DICOMDatasetAccess* dataset);
void AddFilesToUnsortedBlock(const DICOMDatasetList& datasets);
/**
\brief Meant for internal use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption only.
\todo Could make sense to enhance this with an instance of GantryTiltInformation to store the whole result!
*/
void FlagGantryTilt(const GantryTiltInformation& tiltInfo);
/**
\brief Only meaningful for use by AnalyzeFileForITKImageSeriesReaderSpacingAssumption.
*/
void UndoPrematureGrouping();
protected:
DICOMDatasetList m_GroupedFiles;
DICOMDatasetList m_UnsortedFiles;
GantryTiltInformation m_TiltInfo;
std::string m_FirstFilenameOfBlock;
std::string m_LastFilenameOfBlock;
};
/**
\brief Ensure an equal z-spacing for a group of files.
Takes as input a number of images, which are all equally oriented and spatially sorted along their normal direction.
Internally used by GetSeries. Returns two lists: the first one contins slices of equal inter-slice spacing.
The second list contains remaining files, which need to be run through AnalyzeFileForITKImageSeriesReaderSpacingAssumption again.
Relevant code that is matched here is in
itkImageSeriesReader.txx (ImageSeriesReader::GenerateOutputInformation(void)), lines 176 to 245 (as of ITK 3.20)
*/
SliceGroupingAnalysisResult
AnalyzeFileForITKImageSeriesReaderSpacingAssumption(const DICOMDatasetList& files, bool groupsOfSimilarImages);
/**
\brief Safely convert const char* to std::string.
*/
std::string
ConstCharStarToString(const char* s);
EquiDistantBlocksSorter();
virtual ~EquiDistantBlocksSorter();
EquiDistantBlocksSorter(const EquiDistantBlocksSorter& other);
EquiDistantBlocksSorter& operator=(const EquiDistantBlocksSorter& other);
bool m_AcceptTilt;
typedef std::vector ResultsList;
ResultsList m_SliceGroupingResults;
double m_ToleratedOriginOffset;
+ bool m_ToleratedOriginOffsetIsAbsolute;
};
}
#endif
diff --git a/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.cpp b/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.cpp
index 40666d8a96..d152d03a6b 100644
--- a/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.cpp
+++ b/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.cpp
@@ -1,165 +1,173 @@
/*=================================================================== The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
//#define MBILOG_ENABLE_DEBUG
#include "mitkNormalDirectionConsistencySorter.h"
#include
mitk::NormalDirectionConsistencySorter
::NormalDirectionConsistencySorter()
:DICOMDatasetSorter()
{
}
mitk::NormalDirectionConsistencySorter
::NormalDirectionConsistencySorter(const NormalDirectionConsistencySorter& other )
:DICOMDatasetSorter(other)
{
}
mitk::NormalDirectionConsistencySorter
::~NormalDirectionConsistencySorter()
{
}
void
mitk::NormalDirectionConsistencySorter
::PrintConfiguration(std::ostream& os, const std::string& indent) const
{
os << indent << "NormalDirectionConsistencySorter" << std::endl;
}
mitk::NormalDirectionConsistencySorter&
mitk::NormalDirectionConsistencySorter
::operator=(const NormalDirectionConsistencySorter& other)
{
if (this != &other)
{
DICOMDatasetSorter::operator=(other);
}
return *this;
}
+bool
+mitk::NormalDirectionConsistencySorter
+::operator==(const DICOMDatasetSorter& other) const
+{
+ return dynamic_cast(&other) != NULL;
+}
+
+
mitk::DICOMTagList
mitk::NormalDirectionConsistencySorter
::GetTagsOfInterest()
{
DICOMTagList tags;
tags.push_back( DICOMTag(0x0020, 0x0032) ); // ImagePositionPatient
tags.push_back( DICOMTag(0x0020, 0x0037) ); // ImageOrientationPatient
return tags;
}
void
mitk::NormalDirectionConsistencySorter
::Sort()
{
DICOMDatasetList datasets = GetInput();
if (datasets.size() > 1)
{
// at some point in the code, there is the expectation that
// the direction of the slice normals is the same as the direction between
// first and last slice origin. We need to make this sure here, because
// we want to feed the files into itk::ImageSeriesReader with the consistent
// setting of ReverseOrderOff.
static const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient)
static const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation
DICOMDatasetAccess* firstDS = datasets.front();
DICOMDatasetAccess* lastDS = datasets.back();
// make sure here that the direction from slice to slice is the direction of
// image normals...
std::string imageOrientationString = firstDS->GetTagValueAsString( tagImageOrientation );
std::string imagePositionPatientFirst = firstDS->GetTagValueAsString( tagImagePositionPatient );
std::string imagePositionPatientLast = lastDS->GetTagValueAsString( tagImagePositionPatient );
static Vector3D right; right.Fill(0.0);
static Vector3D up; up.Fill(0.0);
static bool hasOrientation(false);
DICOMStringToOrientationVectors( imageOrientationString,
right, up, hasOrientation );
static Point3D firstOrigin; firstOrigin.Fill(0.0f);
static bool firstHasOrigin(false);
firstOrigin = DICOMStringToPoint3D( imagePositionPatientFirst, firstHasOrigin );
static Point3D lastOrigin; lastOrigin.Fill(0.0f);
static bool lastHasOrigin(false);
lastOrigin = DICOMStringToPoint3D( imagePositionPatientLast, lastHasOrigin );
static Vector3D normal;
normal[0] = right[1] * up[2] - right[2] * up[1];
normal[1] = right[2] * up[0] - right[0] * up[2];
normal[2] = right[0] * up[1] - right[1] * up[0];
normal.Normalize();
static Vector3D directionOfSlices;
directionOfSlices = lastOrigin - firstOrigin;
directionOfSlices.Normalize();
static double projection = 0.0;
projection = 0.0;
projection = normal * directionOfSlices;
MITK_DEBUG << "Making sense of \norientation '" << imageOrientationString
<< "'\nfirst position '" << imagePositionPatientFirst
<< "'\nlast position '" << imagePositionPatientLast << "'";
MITK_DEBUG << "Normal: " << normal;
MITK_DEBUG << "Direction of slices: " << directionOfSlices;
MITK_DEBUG << "Projection of direction onto slice normal: " << projection;
if ( projection < 0.0 )
{
MITK_DEBUG << "Need to reverse filenames";
std::reverse( datasets.begin(), datasets.end() );
m_TiltInfo = GantryTiltInformation::MakeFromTagValues(
imagePositionPatientLast,
imagePositionPatientFirst,
imageOrientationString,
datasets.size() - 1
);
}
else
{
m_TiltInfo = GantryTiltInformation::MakeFromTagValues(
imagePositionPatientFirst,
imagePositionPatientLast,
imageOrientationString,
datasets.size() - 1
);
}
}
else // just ONE dataset, do not forget to reset tilt information
{
m_TiltInfo = GantryTiltInformation(); // empty info
}
this->SetNumberOfOutputs(1);
this->SetOutput(0, datasets);
}
mitk::GantryTiltInformation
mitk::NormalDirectionConsistencySorter
::GetTiltInformation() const
{
return m_TiltInfo;
}
diff --git a/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.h b/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.h
index b0dbfb478f..8fec03b966 100644
--- a/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.h
+++ b/Modules/DICOMReader/mitkNormalDirectionConsistencySorter.h
@@ -1,72 +1,74 @@
/*===================================================================
The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
#ifndef mitkNormalDirectionConsistencySorter_h
#define mitkNormalDirectionConsistencySorter_h
#include "mitkDICOMDatasetSorter.h"
#include "mitkGantryTiltInformation.h"
namespace mitk
{
/**
\ingroup DICOMReaderModule
\brief Makes sure that the order of files is along the image plane normals.
When loading with ImageSeriesReader and initializing an mitk::Image with the result
we need to make sure that the order of inputs for the ImageSeriesReader is along the
normal of the images. I.e. The direction of the normal needs to be the same direction
as the vector from the first to the last image origin.
Since this class is used as a last sorting step before loading, it will also
calculate (and return) an updated GantryTiltInformation object.
\note This class might be a workaround for another bug in MITK, but until this issue
is completely understood, the workaround fixes the problem of images that
appear upside-down.
*/
class DICOMReader_EXPORT NormalDirectionConsistencySorter : public DICOMDatasetSorter
{
public:
mitkClassMacro( NormalDirectionConsistencySorter, DICOMDatasetSorter )
itkNewMacro( NormalDirectionConsistencySorter )
virtual DICOMTagList GetTagsOfInterest();
/// See class description.
virtual void Sort();
/// See class description and DICOMITKSeriesGDCMReader.
GantryTiltInformation GetTiltInformation() const;
virtual void PrintConfiguration(std::ostream& os, const std::string& indent = "") const;
+ virtual bool operator==(const DICOMDatasetSorter& other) const;
+
protected:
NormalDirectionConsistencySorter();
virtual ~NormalDirectionConsistencySorter();
NormalDirectionConsistencySorter(const NormalDirectionConsistencySorter& other);
NormalDirectionConsistencySorter& operator=(const NormalDirectionConsistencySorter& other);
GantryTiltInformation m_TiltInfo;
};
}
#endif
diff --git a/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp b/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp
index bea8ff52cf..da30f6be08 100644
--- a/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp
+++ b/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp
@@ -1,135 +1,142 @@
/*===================================================================
The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
#include "mitkSortByImagePositionPatient.h"
#include "mitkDICOMTag.h"
mitk::SortByImagePositionPatient
::SortByImagePositionPatient(DICOMSortCriterion::Pointer secondaryCriterion)
:DICOMSortCriterion(secondaryCriterion)
{
}
mitk::SortByImagePositionPatient
::~SortByImagePositionPatient()
{
}
mitk::SortByImagePositionPatient
::SortByImagePositionPatient(const SortByImagePositionPatient& other )
:DICOMSortCriterion(other)
{
}
mitk::SortByImagePositionPatient&
mitk::SortByImagePositionPatient
::operator=(const SortByImagePositionPatient& other)
{
if (this != &other)
{
DICOMSortCriterion::operator=(other);
}
return *this;
}
+
+bool
+mitk::SortByImagePositionPatient
+::operator==(const DICOMSortCriterion& other) const
+{
+ return dynamic_cast(&other) != NULL; // same class
+}
void
mitk::SortByImagePositionPatient
::Print(std::ostream& os) const
{
os << "(0020,0032) Image Position (Patient) along normal of (0020,0037) Image Orientation (Patient)";
}
mitk::DICOMTagList
mitk::SortByImagePositionPatient
::GetTagsOfInterest() const
{
DICOMTagList tags;
tags.push_back( DICOMTag(0x0020, 0x0032) ); // ImagePositionPatient
tags.push_back( DICOMTag(0x0020, 0x0037) ); // ImageOrientationPatient
return tags;
}
bool
mitk::SortByImagePositionPatient
::IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const
{
// sort by distance to world origin, assuming (almost) equal orientation
static const DICOMTag tagImagePositionPatient = DICOMTag(0x0020,0x0032); // Image Position (Patient)
static const DICOMTag tagImageOrientation = DICOMTag(0x0020, 0x0037); // Image Orientation
static Vector3D leftRight; leftRight.Fill(0.0);
static Vector3D leftUp; leftUp.Fill(0.0);
static bool leftHasOrientation(false);
DICOMStringToOrientationVectors( left->GetTagValueAsString( tagImageOrientation ),
leftRight, leftUp, leftHasOrientation );
static Vector3D rightRight; rightRight.Fill(0.0);
static Vector3D rightUp; rightUp.Fill(0.0);
static bool rightHasOrientation(false);
DICOMStringToOrientationVectors( right->GetTagValueAsString( tagImageOrientation ),
rightRight, rightUp, rightHasOrientation );
static Point3D leftOrigin; leftOrigin.Fill(0.0f);
static bool leftHasOrigin(false);
leftOrigin = DICOMStringToPoint3D( left->GetTagValueAsString( tagImagePositionPatient ), leftHasOrigin );
static Point3D rightOrigin; rightOrigin.Fill(0.0f);
static bool rightHasOrigin(false);
rightOrigin = DICOMStringToPoint3D( right->GetTagValueAsString( tagImagePositionPatient ), rightHasOrigin );
// we tolerate very small differences in image orientation, since we got to know about
// acquisitions where these values change across a single series (7th decimal digit)
// (http://bugs.mitk.org/show_bug.cgi?id=12263)
// still, we want to check if our assumption of 'almost equal' orientations is valid
for (unsigned int dim = 0; dim < 3; ++dim)
{
if ( fabs(leftRight[dim] - rightRight[dim]) > 0.0001
|| fabs(leftUp[dim] - rightUp[dim]) > 0.0001)
{
MITK_ERROR << "Dicom images have different orientations.";
throw std::logic_error("Dicom images have different orientations. Call GetSeries() first to separate images.");
}
}
static Vector3D normal;
normal[0] = leftRight[1] * leftUp[2] - leftRight[2] * leftUp[1];
normal[1] = leftRight[2] * leftUp[0] - leftRight[0] * leftUp[2];
normal[2] = leftRight[0] * leftUp[1] - leftRight[1] * leftUp[0];
static double leftDistance = 0.0;
static double rightDistance = 0.0;
leftDistance = 0.0;
rightDistance = 0.0;
// this computes the distance from world origin (0,0,0) ALONG THE NORMAL of the image planes
for (unsigned int dim = 0; dim < 3; ++dim)
{
leftDistance += normal[dim] * leftOrigin[dim];
rightDistance += normal[dim] * rightOrigin[dim];
}
// if we can sort by just comparing the distance, we do exactly that
if ( fabs(leftDistance - rightDistance) >= mitk::eps)
{
// default: compare position
return leftDistance < rightDistance;
}
else
{
return this->NextLevelIsLeftBeforeRight(left, right);
}
}
diff --git a/Modules/DICOMReader/mitkSortByImagePositionPatient.h b/Modules/DICOMReader/mitkSortByImagePositionPatient.h
index 950196a184..bc9fdad546 100644
--- a/Modules/DICOMReader/mitkSortByImagePositionPatient.h
+++ b/Modules/DICOMReader/mitkSortByImagePositionPatient.h
@@ -1,62 +1,64 @@
/*===================================================================
The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
#ifndef mitkSortByImagePositionPatient_h
#define mitkSortByImagePositionPatient_h
#include "mitkDICOMSortCriterion.h"
#include "mitkVector.h"
namespace mitk
{
/**
\ingroup DICOMReaderModule
\brief Sort by distance of image origin along image normal (for use in DICOMTagBasedSorter).
To compare two datasets, their distance to the world origin is calculated.
This distance is calculated along the image normals because we do not know
the image orientation in advance, to any of the three coordinates could be identical for all datasets.
\note This class assumes that the datasets have identical orientations!
*/
class DICOMReader_EXPORT SortByImagePositionPatient : public DICOMSortCriterion
{
public:
mitkClassMacro( SortByImagePositionPatient, DICOMSortCriterion );
mitkNewMacro1Param( SortByImagePositionPatient, DICOMSortCriterion::Pointer );
virtual DICOMTagList GetTagsOfInterest() const;
virtual bool IsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const;
virtual void Print(std::ostream& os) const;
+
+ virtual bool operator==(const DICOMSortCriterion& other) const;
protected:
SortByImagePositionPatient( DICOMSortCriterion::Pointer secondaryCriterion = NULL );
virtual ~SortByImagePositionPatient();
SortByImagePositionPatient(const SortByImagePositionPatient& other);
SortByImagePositionPatient& operator=(const SortByImagePositionPatient& other);
private:
};
}
#endif
diff --git a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp
index 119e07f8ee..c57f552a97 100644
--- a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp
+++ b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp
@@ -1,242 +1,265 @@
/*===================================================================
The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
#include "mitkThreeDnTDICOMSeriesReader.h"
#include "mitkITKDICOMSeriesReaderHelper.h"
mitk::ThreeDnTDICOMSeriesReader
::ThreeDnTDICOMSeriesReader(unsigned int decimalPlacesForOrientation)
:DICOMITKSeriesGDCMReader(decimalPlacesForOrientation)
,m_Group3DandT(true)
{
}
mitk::ThreeDnTDICOMSeriesReader
::ThreeDnTDICOMSeriesReader(const ThreeDnTDICOMSeriesReader& other )
:itk::Object()
,DICOMITKSeriesGDCMReader(other)
,m_Group3DandT(true)
{
}
mitk::ThreeDnTDICOMSeriesReader
::~ThreeDnTDICOMSeriesReader()
{
}
mitk::ThreeDnTDICOMSeriesReader&
mitk::ThreeDnTDICOMSeriesReader
::operator=(const ThreeDnTDICOMSeriesReader& other)
{
if (this != &other)
{
DICOMITKSeriesGDCMReader::operator=(other);
this->m_Group3DandT = other.m_Group3DandT;
}
return *this;
}
+bool
+mitk::ThreeDnTDICOMSeriesReader
+::operator==(const DICOMFileReader& other) const
+{
+ if (const Self* otherSelf = dynamic_cast(&other))
+ {
+ return
+ DICOMITKSeriesGDCMReader::operator==(other)
+ && this->m_Group3DandT == otherSelf->m_Group3DandT;
+ }
+ else
+ {
+ return false;
+ }
+}
+
void
mitk::ThreeDnTDICOMSeriesReader
::SetGroup3DandT(bool on)
{
m_Group3DandT = on;
}
+bool
+mitk::ThreeDnTDICOMSeriesReader
+::GetGroup3DandT() const
+{
+ return m_Group3DandT;
+}
+
mitk::DICOMITKSeriesGDCMReader::SortingBlockList
mitk::ThreeDnTDICOMSeriesReader
::Condense3DBlocks(SortingBlockList& resultOf3DGrouping)
{
if (!m_Group3DandT)
{
return resultOf3DGrouping; // don't work if nobody asks us to
}
SortingBlockList remainingBlocks = resultOf3DGrouping;
SortingBlockList non3DnTBlocks;
SortingBlockList true3DnTBlocks;
std::vector true3DnTBlocksTimeStepCount;
// we should describe our need for this tag as needed via a function
// (however, we currently know that the superclass will always need this tag)
const DICOMTag tagImagePositionPatient(0x0020, 0x0032);
while (!remainingBlocks.empty())
{
// new block to fill up
DICOMGDCMImageFrameList& firstBlock = remainingBlocks.front();
DICOMGDCMImageFrameList current3DnTBlock = firstBlock;
int current3DnTBlockNumberOfTimeSteps = 1;
// get block characteristics of first block
unsigned int currentBlockNumberOfSlices = firstBlock.size();
std::string currentBlockFirstOrigin = firstBlock.front()->GetTagValueAsString( tagImagePositionPatient );
std::string currentBlockLastOrigin = firstBlock.back()->GetTagValueAsString( tagImagePositionPatient );
remainingBlocks.pop_front();
// compare all other blocks against the first one
for (SortingBlockList::iterator otherBlockIter = remainingBlocks.begin();
otherBlockIter != remainingBlocks.end();
/*++otherBlockIter*/) // <-- inside loop
{
// get block characteristics from first block
DICOMGDCMImageFrameList& otherBlock = *otherBlockIter;
unsigned int otherBlockNumberOfSlices = otherBlock.size();
std::string otherBlockFirstOrigin = otherBlock.front()->GetTagValueAsString( tagImagePositionPatient );
std::string otherBlockLastOrigin = otherBlock.back()->GetTagValueAsString( tagImagePositionPatient );
// add matching blocks to current3DnTBlock
// keep other blocks for later
if ( otherBlockNumberOfSlices == currentBlockNumberOfSlices
&& otherBlockFirstOrigin == currentBlockFirstOrigin
&& otherBlockLastOrigin == currentBlockLastOrigin
)
{ // matching block
++current3DnTBlockNumberOfTimeSteps;
current3DnTBlock.insert( current3DnTBlock.end(), otherBlock.begin(), otherBlock.end() ); // append
// remove this block from remainingBlocks
otherBlockIter = remainingBlocks.erase(otherBlockIter); // make sure iterator otherBlockIter is valid afterwards
}
else
{
++otherBlockIter;
}
}
// in any case, we now now all about the first block of our list ...
// ... and we wither call it 3D o 3D+t
if (current3DnTBlockNumberOfTimeSteps > 1)
{
true3DnTBlocks.push_back(current3DnTBlock);
true3DnTBlocksTimeStepCount.push_back(current3DnTBlockNumberOfTimeSteps);
}
else
{
non3DnTBlocks.push_back(current3DnTBlock);
}
}
// create output for real 3D+t blocks (other outputs will be created by superclass)
// set 3D+t flag on output block
this->SetNumberOfOutputs( true3DnTBlocks.size() );
unsigned int o = 0;
for (SortingBlockList::iterator blockIter = true3DnTBlocks.begin();
blockIter != true3DnTBlocks.end();
++o, ++blockIter)
{
// bad copy&paste code from DICOMITKSeriesGDCMReader, should be handled in a better way
DICOMGDCMImageFrameList& gdcmFrameInfoList = *blockIter;
assert(!gdcmFrameInfoList.empty());
// reverse frames if necessary
// update tilt information from absolute last sorting
DICOMDatasetList datasetList = ToDICOMDatasetList( gdcmFrameInfoList );
m_NormalDirectionConsistencySorter->SetInput( datasetList );
m_NormalDirectionConsistencySorter->Sort();
DICOMGDCMImageFrameList sortedGdcmInfoFrameList = FromDICOMDatasetList( m_NormalDirectionConsistencySorter->GetOutput(0) );
const GantryTiltInformation& tiltInfo = m_NormalDirectionConsistencySorter->GetTiltInformation();
// set frame list for current block
DICOMImageFrameList frameList = ToDICOMImageFrameList( sortedGdcmInfoFrameList );
assert(!frameList.empty());
DICOMImageBlockDescriptor block;
block.SetTagCache( this ); // important: this must be before SetImageFrameList(), because SetImageFrameList will trigger reading of lots of interesting tags!
block.SetImageFrameList( frameList );
block.SetTiltInformation( tiltInfo );
block.SetFlag("3D+t", true);
block.SetIntProperty("timesteps", true3DnTBlocksTimeStepCount[o]);
MITK_DEBUG << "Found " << true3DnTBlocksTimeStepCount[o] << " timesteps";
this->SetOutput( o, block );
}
return non3DnTBlocks;
}
bool
mitk::ThreeDnTDICOMSeriesReader
::LoadImages()
{
bool success = true;
unsigned int numberOfOutputs = this->GetNumberOfOutputs();
for (unsigned int o = 0; o < numberOfOutputs; ++o)
{
DICOMImageBlockDescriptor& block = this->InternalGetOutput(o);
if (block.GetFlag("3D+t", false))
{
success &= this->LoadMitkImageForOutput(o);
}
else
{
success &= DICOMITKSeriesGDCMReader::LoadMitkImageForOutput(o); // let superclass handle non-3D+t
}
}
return success;
}
bool
mitk::ThreeDnTDICOMSeriesReader
::LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const
{
PushLocale();
const DICOMImageFrameList& frames = block.GetImageFrameList();
const GantryTiltInformation tiltInfo = block.GetTiltInformation();
bool hasTilt = tiltInfo.IsRegularGantryTilt();
int numberOfTimesteps = block.GetIntProperty("timesteps", 1);
if (numberOfTimesteps == 1)
{
return DICOMITKSeriesGDCMReader::LoadMitkImageForImageBlockDescriptor(block);
}
int numberOfFramesPerTimestep = frames.size() / numberOfTimesteps;
assert( int(double((double)frames.size() / (double)numberOfTimesteps ))
== numberOfFramesPerTimestep ); // this should hold
ITKDICOMSeriesReaderHelper::StringContainerList filenamesPerTimestep;
for (int timeStep = 0; timeStepFilename );
}
filenamesPerTimestep.push_back( filenamesOfThisTimeStep );
}
mitk::ITKDICOMSeriesReaderHelper helper;
mitk::Image::Pointer mitkImage = helper.Load3DnT( filenamesPerTimestep, m_FixTiltByShearing && hasTilt, tiltInfo );
block.SetMitkImage( mitkImage );
PopLocale();
return true;
}
diff --git a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.h b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.h
index d8cab3b715..d977c9814a 100644
--- a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.h
+++ b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.h
@@ -1,84 +1,86 @@
/*===================================================================
The Medical Imaging Interaction Toolkit (MITK)
Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.
See LICENSE.txt or http://www.mitk.org for details.
===================================================================*/
#ifndef mitkThreeDnTDICOMSeriesReader_h
#define mitkThreeDnTDICOMSeriesReader_h
#include "mitkDICOMITKSeriesGDCMReader.h"
#include "DICOMReaderExports.h"
namespace mitk
{
/**
\ingroup DICOMReader
\brief Extends DICOMITKSeriesGDCMReader by sorting/grouping into 3D+t image blocks.
This class reuses the DICOMITKSeriesGDCMReader class and adds the option of
grouping 3D blocks at the same spatial position into a single block, which
is loaded as a 3D+t mitk::Image (compare to \ref DICOMITKSeriesGDCMReader_Condensing).
To group two output blocks into a single 3D+t block, this class tests a number
of requirements that the two blocks must fulfill:
- the origin of the first slice must be identical
- the origin of the last slice must be identical
- the number of slices must be identical
The output blocks described by DICOMImageBlockDescriptor will contains the following properties:
- \b "3D+t": true if the image is 3D+t
- \b "timesteps": number of timesteps of an image (only defined if "3D+t" is true)
*/
class DICOMReader_EXPORT ThreeDnTDICOMSeriesReader : public DICOMITKSeriesGDCMReader
{
public:
mitkClassMacro( ThreeDnTDICOMSeriesReader, DICOMITKSeriesGDCMReader );
mitkCloneMacro( ThreeDnTDICOMSeriesReader );
itkNewMacro( ThreeDnTDICOMSeriesReader );
mitkNewMacro1Param( ThreeDnTDICOMSeriesReader, unsigned int );
/// \brief Control whether 3D+t grouping shall actually be attempted.
void SetGroup3DandT(bool on);
+ bool GetGroup3DandT() const;
// void AllocateOutputImages();
/// \brief Load via multiple calls to itk::ImageSeriesReader.
virtual bool LoadImages();
+ virtual bool operator==(const DICOMFileReader& other) const;
protected:
ThreeDnTDICOMSeriesReader(unsigned int decimalPlacesForOrientation = 5);
virtual ~ThreeDnTDICOMSeriesReader();
ThreeDnTDICOMSeriesReader(const ThreeDnTDICOMSeriesReader& other);
ThreeDnTDICOMSeriesReader& operator=(const ThreeDnTDICOMSeriesReader& other);
/**
\brief Analyze the groups produced by DICOMITKSeriesGDCMReader for 3D+t properties.
This method tests whether some blocks are at the same spatial position and groups
them into single blocks.
*/
virtual SortingBlockList Condense3DBlocks(SortingBlockList&);
bool LoadMitkImageForImageBlockDescriptor(DICOMImageBlockDescriptor& block) const;
bool m_Group3DandT;
};
}
#endif