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
+ 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).
+*/
+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);
+
+ 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);
+
+ ThreeDnTDICOMSeriesReader::Pointer ConfigureThreeDnTDICOMSeriesReader(ThreeDnTDICOMSeriesReader::Pointer reader, TiXmlElement*);
+ DICOMITKSeriesGDCMReader::Pointer ConfigureDICOMITKSeriesGDCMReader(DICOMITKSeriesGDCMReader::Pointer reader, TiXmlElement*);
+
+ DICOMSortCriterion::Pointer CreateDICOMSortByTag(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion);
+ DICOMSortCriterion::Pointer CreateSortByImagePositionPatient(TiXmlElement* xmlElement, DICOMSortCriterion::Pointer secondaryCriterion);
+ };
+
+} // namespace
+
+#endif // mitkDICOMReaderConfigurator_h
diff --git a/Modules/DICOMReader/mitkDICOMSortByTag.cpp b/Modules/DICOMReader/mitkDICOMSortByTag.cpp
index 5117e7390c..8cacc7fef5 100644
--- a/Modules/DICOMReader/mitkDICOMSortByTag.cpp
+++ b/Modules/DICOMReader/mitkDICOMSortByTag.cpp
@@ -1,117 +1,125 @@
/*===================================================================
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;
}
+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) )
{
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 8ca6c48596..33a228fa2e 100644
--- a/Modules/DICOMReader/mitkDICOMSortByTag.h
+++ b/Modules/DICOMReader/mitkDICOMSortByTag.h
@@ -1,54 +1,68 @@
/*===================================================================
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;
+
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.cpp b/Modules/DICOMReader/mitkDICOMSortCriterion.cpp
index bbeebb07f0..7086f4305b 100644
--- a/Modules/DICOMReader/mitkDICOMSortCriterion.cpp
+++ b/Modules/DICOMReader/mitkDICOMSortCriterion.cpp
@@ -1,79 +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.
===================================================================*/
#include "mitkDICOMSortCriterion.h"
mitk::DICOMSortCriterion
::DICOMSortCriterion(DICOMSortCriterion::Pointer secondaryCriterion)
:itk::LightObject()
,m_SecondaryCriterion(secondaryCriterion)
{
}
mitk::DICOMSortCriterion
::~DICOMSortCriterion()
{
}
mitk::DICOMSortCriterion
::DICOMSortCriterion(const DICOMSortCriterion& other )
:itk::LightObject()
,m_SecondaryCriterion(other.m_SecondaryCriterion)
{
}
mitk::DICOMSortCriterion&
mitk::DICOMSortCriterion
::operator=(const DICOMSortCriterion& other)
{
if (this != &other)
{
m_SecondaryCriterion = other.m_SecondaryCriterion;
}
return *this;
}
+mitk::DICOMSortCriterion::ConstPointer
+mitk::DICOMSortCriterion
+::GetSecondaryCriterion() const
+{
+ return m_SecondaryCriterion.GetPointer();
+}
+
mitk::DICOMTagList
mitk::DICOMSortCriterion
::GetAllTagsOfInterest() const
{
DICOMTagList allTags;
// iterate all secondary criteria, return all tags
const DICOMSortCriterion* criterionToCheck = this;
while (criterionToCheck)
{
DICOMTagList newElements = criterionToCheck->GetTagsOfInterest();
allTags.insert( allTags.end(), newElements.begin(), newElements.end() ); // append
criterionToCheck = criterionToCheck->m_SecondaryCriterion.GetPointer();
}
return allTags;
}
bool
mitk::DICOMSortCriterion
::NextLevelIsLeftBeforeRight(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right) const
{
if (m_SecondaryCriterion.IsNotNull())
{
return m_SecondaryCriterion->IsLeftBeforeRight(left, right);
}
else
{
return (void*)left < (void*)right;
}
}
diff --git a/Modules/DICOMReader/mitkDICOMSortCriterion.h b/Modules/DICOMReader/mitkDICOMSortCriterion.h
index 061ac3b542..8921bd9290 100644
--- a/Modules/DICOMReader/mitkDICOMSortCriterion.h
+++ b/Modules/DICOMReader/mitkDICOMSortCriterion.h
@@ -1,54 +1,76 @@
/*===================================================================
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;
+
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/mitkDICOMTag.cpp b/Modules/DICOMReader/mitkDICOMTag.cpp
index 3c9538d432..4a54e4d58e 100644
--- a/Modules/DICOMReader/mitkDICOMTag.cpp
+++ b/Modules/DICOMReader/mitkDICOMTag.cpp
@@ -1,166 +1,207 @@
/*===================================================================
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 "mitkDICOMTag.h"
+#include
+#include
+
+#include "mitkLogMacros.h"
+
mitk::DICOMTag
::DICOMTag(unsigned int group, unsigned int element)
:m_Group(group)
,m_Element(element)
{
}
mitk::DICOMTag
::DICOMTag(const DICOMTag& other)
:m_Group(other.m_Group)
,m_Element(other.m_Element)
{
}
bool
mitk::DICOMTag
::operator==(const DICOMTag& other) const
{
return
m_Group == other.m_Group &&
m_Element == other.m_Element
;
}
mitk::DICOMTag&
mitk::DICOMTag
::operator=(const DICOMTag& other)
{
if (this != &other)
{
m_Group = other.m_Group;
m_Element = other.m_Element;
}
return *this;
}
unsigned int
mitk::DICOMTag
::GetGroup() const
{
return m_Group;
}
unsigned int
mitk::DICOMTag
::GetElement() const
{
return m_Element;
}
bool
mitk::DICOMTag
::operator<(const DICOMTag& other) const
{
+ // TODO check this comparison!
return this->m_Group * 0x3000 + this->m_Element <
other.m_Group * 0x3000 + other.m_Element;
}
+std::string
+mitk::DICOMTag
+::GetName() const
+{
+ gdcm::Tag t(m_Group, m_Element);
+
+ const gdcm::Global& g = gdcm::Global::GetInstance(); // sum of all knowledge !
+ const gdcm::Dicts& dicts = g.GetDicts();
+ const gdcm::Dict& pub = dicts.GetPublicDict(); // Part 6
+
+ const gdcm::DictEntry& entry = pub.GetDictEntry(t);
+ std::string name = entry.GetName();
+ if (name.empty())
+ {
+ name = "Unknown Tag";
+ }
+
+ return name;
+}
+
+std::string
+mitk::DICOMTag
+::toHexString(unsigned int i) const
+{
+ std::stringstream ss;
+ ss << std::setfill ('0') << std::setw(4) << std::hex << i;
+ return ss.str();
+}
+
+void
+mitk::DICOMTag
+::Print(std::ostream& os) const
+{
+ os << "(" << toHexString(m_Group) << "," << toHexString(m_Element) << ") " << this->GetName();
+}
void
mitk::DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful)
{
successful = true;
std::istringstream orientationReader(s);
std::string coordinate;
unsigned int dim(0);
while( std::getline( orientationReader, coordinate, '\\' ) && dim < 6 )
{
if (dim<3)
{
right[dim++] = atof(coordinate.c_str());
}
else
{
up[dim++ - 3] = atof(coordinate.c_str());
}
}
if (dim && dim != 6)
{
successful = false;
}
else if (dim == 0)
{
// fill with defaults
right.Fill(0.0);
right[0] = 1.0;
up.Fill(0.0);
up[1] = 1.0;
successful = false;
}
}
bool
mitk::DICOMStringToSpacing(const std::string& s, ScalarType& spacingX, ScalarType& spacingY)
{
bool successful = false;
std::istringstream spacingReader(s);
std::string spacing;
if ( std::getline( spacingReader, spacing, '\\' ) )
{
spacingY = atof( spacing.c_str() );
if ( std::getline( spacingReader, spacing, '\\' ) )
{
spacingX = atof( spacing.c_str() );
successful = true;
}
}
return successful;
}
mitk::Point3D
mitk::DICOMStringToPoint3D(const std::string& s, bool& successful)
{
Point3D p;
successful = true;
std::istringstream originReader(s);
std::string coordinate;
unsigned int dim(0);
while( std::getline( originReader, coordinate, '\\' ) && dim < 3)
{
p[dim++]= atof(coordinate.c_str());
}
if (dim && dim != 3)
{
successful = false;
}
else if (dim == 0)
{
successful = false;
p.Fill(0.0); // assume default (0,0,0)
}
return p;
}
diff --git a/Modules/DICOMReader/mitkDICOMTag.h b/Modules/DICOMReader/mitkDICOMTag.h
index c827e272b6..d693a96d24 100644
--- a/Modules/DICOMReader/mitkDICOMTag.h
+++ b/Modules/DICOMReader/mitkDICOMTag.h
@@ -1,73 +1,89 @@
/*===================================================================
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 mitkTag_h
#define mitkTag_h
#include "mitkVector.h"
#include "DICOMReaderExports.h"
namespace mitk
{
+/**
+ \ingroup DICOMReaderModule
+ \brief Representation of a DICOM tag.
+
+ This class is just meant to combine group and element
+ numbers for better readability and make handling tags
+ more easy by offering comparison methods.
+*/
class DICOMReader_EXPORT DICOMTag
{
public:
DICOMTag(unsigned int group, unsigned int element);
DICOMTag(const DICOMTag& other);
DICOMTag& operator=(const DICOMTag& other);
bool operator==(const DICOMTag& other) const;
bool operator<(const DICOMTag& other) const;
unsigned int GetGroup() const;
unsigned int GetElement() const;
- protected:
+ /// Return the name of this tag (e.g. "SeriesDescription" instead of "(0008,103e)")
+ std::string GetName() const;
+
+ /// add "(group-id,element-id) name" to given stream
+ void Print(std::ostream& os) const;
+
+ private:
+
+ std::string toHexString(unsigned int i) const;
unsigned int m_Group;
unsigned int m_Element;
};
typedef std::vector DICOMTagList;
/**
\brief Convert DICOM string describing a point two Vector3D.
DICOM tags like ImageOrientationPatient contain two vectors as float numbers separated by backslashes:
\verbatim
42.7131\13.77\0.7\137.76\0.3
\endverbatim
*/
void DICOMStringToOrientationVectors(const std::string& s, Vector3D& right, Vector3D& up, bool& successful);
bool DICOMStringToSpacing(const std::string& s, ScalarType& spacingX, ScalarType& spacingY);
/**
\brief Convert DICOM string describing a point to Point3D.
DICOM tags like ImagePositionPatient contain a position as float numbers separated by backslashes:
\verbatim
42.7131\13.77\0.7
\endverbatim
*/
mitk::Point3D DICOMStringToPoint3D(const std::string& s, bool& successful);
}
#endif
diff --git a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp
index f615cb876f..a527e6ecf4 100644
--- a/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp
+++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.cpp
@@ -1,244 +1,270 @@
/*===================================================================
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
{
+ // TODO make this work with all kind of numbers and lists of numbers!!
// 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)
bool conversionError(false);
Vector3D right; right.Fill(0.0);
Vector3D up; right.Fill(0.0);
DICOMStringToOrientationVectors( input, right, up, conversionError );
std::ostringstream ss;
ss.setf(std::ios::fixed, std::ios::floatfield);
ss.precision(m_Precision);
ss << right[0] << "\\"
<< right[1] << "\\"
<< right[2] << "\\"
<< up[0] << "\\"
<< up[1] << "\\"
<< up[2];
return ss.str();
}
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;
}
+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;
}
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;
}
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 efec719b9b..9f6c215451 100644
--- a/Modules/DICOMReader/mitkDICOMTagBasedSorter.h
+++ b/Modules/DICOMReader/mitkDICOMTagBasedSorter.h
@@ -1,91 +1,148 @@
/*===================================================================
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
{
/**
- \brief sort files based on filename (last resort).
+ \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 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 CutDecimalPlaces : public TagValueProcessor
{
public:
CutDecimalPlaces(unsigned int precision);
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 );
+
+ /**
+ \brief Define the sorting criterion (which holds seconardy criteria)
+ */
void SetSortCriterion( DICOMSortCriterion::ConstPointer criterion );
+ /**
+ \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;
+
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);
- bool StringCompare(const mitk::DICOMDatasetAccess* left, const mitk::DICOMDatasetAccess* right);
- bool NumericCompare(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/mitkDICOMTagCache.h b/Modules/DICOMReader/mitkDICOMTagCache.h
index f2b8eb26dc..c07ddd7519 100644
--- a/Modules/DICOMReader/mitkDICOMTagCache.h
+++ b/Modules/DICOMReader/mitkDICOMTagCache.h
@@ -1,35 +1,38 @@
/*===================================================================
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 mitkDICOMTagCache_h
#define mitkDICOMTagCache_h
#include "itkObjectFactory.h"
#include "mitkCommon.h"
#include "DICOMReaderExports.h"
namespace mitk
{
+/**
+ \ingroup DICOMReaderModule
+*/
class DICOMReader_EXPORT DICOMTagCache
{
public:
virtual std::string GetTagValue(DICOMImageFrameInfo* frame, const DICOMTag& tag) const = 0;
};
}
#endif
diff --git a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp
index 0979f8472d..2e1fb66c41 100644
--- a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp
+++ b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.cpp
@@ -1,484 +1,500 @@
/*=================================================================== 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
::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)
{
}
mitk::EquiDistantBlocksSorter
::EquiDistantBlocksSorter(const EquiDistantBlocksSorter& other )
:DICOMDatasetSorter(other)
,m_AcceptTilt(false)
{
}
mitk::EquiDistantBlocksSorter
::~EquiDistantBlocksSorter()
{
}
+void
+mitk::EquiDistantBlocksSorter
+::PrintConfiguration(std::ostream& os, const std::string& indent) const
+{
+ os << indent << "Sort into blocks of equidistant, well-aligned 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);
}
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);
}
}
const mitk::GantryTiltInformation
mitk::EquiDistantBlocksSorter
::GetTiltInformation(const std::string& filename)
{
for (ResultsList::iterator ri = m_SliceGroupingResults.begin();
ri != m_SliceGroupingResults.end();
++ri)
{
SliceGroupingAnalysisResult& result = *ri;
if (filename == result.GetFirstFilenameOfBlock())
{
return result.GetTiltInfo();
}
}
return GantryTiltInformation(); // empty
}
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);
+ 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;
+ 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)
+ 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() ) // mitk::eps is too small; 1/1000 of a mm should be enough to detect tilt
{
/* 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() );
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() );
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();
- double toleratedError(0.005); // max. 1/10mm error when measurement crosses 20 slices in z direction
- if (norm > toleratedError)
+ if (norm > toleratedOriginError)
{
MITK_DEBUG << " File does not fit into the inter-slice distance pattern (diff = "
<< norm << ", allowed "
- << toleratedError << ").";
+ << 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 )
{
DICOMDatasetList datasets = result.GetBlockDatasets();
DICOMDatasetAccess* firstDataset = datasets.front();
DICOMDatasetAccess* lastDataset = datasets.back();
unsigned int numberOfSlicesApart = datasets.size() - 1;
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 = firstDataset->GetTagValueAsString( tagImageOrientation );
bool orientationConversion(false);
DICOMStringToOrientationVectors( orientationValue, right, up, orientationConversion );
if (orientationConversion)
{
std::string firstOriginString = firstDataset->GetTagValueAsString( tagImagePositionPatient );
std::string lastOriginString = lastDataset->GetTagValueAsString( tagImagePositionPatient );
if (!firstOriginString.empty() && !lastOriginString.empty())
{
bool firstOriginConversion(false);
bool lastOriginConversion(false);
Point3D firstOrigin = DICOMStringToPoint3D( firstOriginString, firstOriginConversion );
Point3D lastOrigin = DICOMStringToPoint3D( lastOriginString, lastOriginConversion );
if (firstOriginConversion && lastOriginConversion)
{
GantryTiltInformation updatedTiltInfo( firstOrigin, lastOrigin, right, up, numberOfSlicesApart );
result.FlagGantryTilt(updatedTiltInfo);
}
}
}
}
return result;
}
diff --git a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h
index cc6b2a6ba9..37e9083fbe 100644
--- a/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h
+++ b/Modules/DICOMReader/mitkEquiDistantBlocksSorter.h
@@ -1,155 +1,180 @@
/*===================================================================
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
{
/**
- \brief Split inputs into blocks of equidant slices.
+ \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.
+
+ Detailed implementation in AnalyzeFileForITKImageSeriesReaderSpacingAssumption().
- This kind of splitting is used as a check before loading a DICOM series with ITK ImageSeriesReader.
*/
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);
const GantryTiltInformation GetTiltInformation(const std::string& filename);
+ virtual void PrintConfiguration(std::ostream& os, const std::string& indent = "") const;
+
protected:
/**
- \brief Return type of DicomSeriesReader::AnalyzeFileForITKImageSeriesReaderSpacingAssumption.
+ \brief Return type of AnalyzeFileForITKImageSeriesReaderSpacingAssumption().
- Class contains the grouping result of method DicomSeriesReader::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;
/**
\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;
};
/**
\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;
};
}
#endif
diff --git a/Modules/DICOMReader/mitkGantryTiltInformation.h b/Modules/DICOMReader/mitkGantryTiltInformation.h
index 00a23bdd40..425818cc4e 100644
--- a/Modules/DICOMReader/mitkGantryTiltInformation.h
+++ b/Modules/DICOMReader/mitkGantryTiltInformation.h
@@ -1,129 +1,133 @@
/*===================================================================
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 mitkGantryTiltInformation_h
#define mitkGantryTiltInformation_h
#include "mitkVector.h"
namespace mitk
{
/**
+ \ingroup DICOMReaderModule
\brief Gantry tilt analysis result.
Takes geometry information for two slices of a DICOM series and
calculates whether these fit into an orthogonal block or not.
If NOT, they can either be the result of an acquisition with
gantry tilt OR completly broken by some shearing transformation.
Most calculations are done in the constructor, results can then
be read via the remaining methods.
+
+ This class is a helper to DICOMITKSeriesGDCMReader and can
+ not be used outside of \ref DICOMReaderModule
*/
class GantryTiltInformation
{
public:
// two types to avoid any rounding errors
typedef itk::Point Point3Dd;
typedef itk::Vector Vector3Dd;
/**
\brief Just so we can create empty instances for assigning results later.
*/
GantryTiltInformation();
void Print(std::ostream& os) const;
/**
\brief THE constructor, which does all the calculations.
Determining the amount of tilt is done by checking the distances
of origin1 from planes through origin2. Two planes are considered:
- normal vector along normal of slices (right x up): gives the slice distance
- normal vector along orientation vector "up": gives the shift parallel to the plane orientation
The tilt angle can then be calculated from these distances
\param origin1 origin of the first slice
\param origin2 origin of the second slice
\param right right/up describe the orientatation of borth slices
\param up right/up describe the orientatation of borth slices
\param numberOfSlicesApart how many slices are the given origins apart (1 for neighboring slices)
*/
GantryTiltInformation( const Point3D& origin1,
const Point3D& origin2,
const Vector3D& right,
const Vector3D& up, unsigned int numberOfSlicesApart);
/**
\brief Whether the slices were sheared.
True if any of the shifts along right or up vector are non-zero.
*/
bool IsSheared() const;
/**
\brief Whether the shearing is a gantry tilt or more complicated.
Gantry tilt will only produce shifts in ONE orientation, not in both.
Since the correction code currently only coveres one tilt direction
AND we don't know of medical images with two tilt directions, the
loading code wants to check if our assumptions are true.
*/
bool IsRegularGantryTilt() const;
/**
\brief The offset distance in Y direction for each slice in mm (describes the tilt result).
*/
double GetMatrixCoefficientForCorrectionInWorldCoordinates() const;
/**
\brief The z / inter-slice spacing. Needed to correct ImageSeriesReader's result.
*/
double GetRealZSpacing() const;
/**
\brief The shift between first and last slice in mm.
Needed to resize an orthogonal image volume.
*/
double GetTiltCorrectedAdditionalSize(unsigned int imageSizeZ) const;
/**
\brief Calculated tilt angle in degrees.
*/
double GetTiltAngleInDegrees() const;
- protected:
+ private:
/**
\brief Projection of point p onto line through lineOrigin in direction of lineDirection.
*/
Point3D projectPointOnLine( Point3Dd p, Point3Dd lineOrigin, Vector3Dd lineDirection );
double m_ShiftUp;
double m_ShiftRight;
double m_ShiftNormal;
double m_ITKAssumedSliceSpacing;
unsigned int m_NumberOfSlicesApart;
};
} // namespace
#endif
diff --git a/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp b/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp
index 5b24cd2911..bea8ff52cf 100644
--- a/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp
+++ b/Modules/DICOMReader/mitkSortByImagePositionPatient.cpp
@@ -1,128 +1,135 @@
/*===================================================================
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;
}
+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 510bb56ea3..950196a184 100644
--- a/Modules/DICOMReader/mitkSortByImagePositionPatient.h
+++ b/Modules/DICOMReader/mitkSortByImagePositionPatient.h
@@ -1,50 +1,62 @@
/*===================================================================
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;
+
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 e5ee67655a..0117b8621c 100644
--- a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp
+++ b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.cpp
@@ -1,246 +1,246 @@
/*===================================================================
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()
:DICOMITKSeriesGDCMReader()
,m_Group3DandT(true)
{
}
mitk::ThreeDnTDICOMSeriesReader
::ThreeDnTDICOMSeriesReader(const ThreeDnTDICOMSeriesReader& other )
: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;
}
void
mitk::ThreeDnTDICOMSeriesReader
::SetGroup3DandT(bool on)
{
m_Group3DandT = on;
}
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;
// TODO we should provide this tag as needed via a function
// (however, we currently know that the superclass will use 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)
{
DICOMGDCMImageFrameList& gdcmFrameInfoList = *blockIter;
DICOMImageFrameList frameList = ToDICOMImageFrameList( gdcmFrameInfoList );
assert(!gdcmFrameInfoList.empty());
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 );
// bad copy&paste code, should be handled in a better way
const GantryTiltInformation& tiltInfo = m_EquiDistantBlocksSorter->GetTiltInformation( (gdcmFrameInfoList.front())->GetFilenameIfAvailable() );
block.SetTiltInformation( tiltInfo );
// assume
static const DICOMTag tagPixelSpacing(0x0028,0x0030);
static const DICOMTag tagImagerPixelSpacing(0x0018,0x1164);
std::string pixelSpacingString = (gdcmFrameInfoList.front())->GetTagValueAsString( tagPixelSpacing );
std::string imagerPixelSpacingString = gdcmFrameInfoList.front()->GetTagValueAsString( tagImagerPixelSpacing );
- block.SetPixelSpacingInformation(pixelSpacingString, imagerPixelSpacingString);
+ block.SetPixelSpacingTagValues(pixelSpacingString, imagerPixelSpacingString);
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;
}
// TODO why not handle 3D as a special case of 3D+t?? Late insight?.. .. because of ITK VImageDimension? .. could be a parameter
bool
mitk::ThreeDnTDICOMSeriesReader
::LoadMitkImageForOutput(unsigned int o)
{
PushLocale();
DICOMImageBlockDescriptor& block = this->InternalGetOutput(o);
const DICOMImageFrameList& frames = block.GetImageFrameList();
const GantryTiltInformation tiltInfo = block.GetTiltInformation();
bool hasTilt = block.GetFlag("gantryTilt", false);
if (hasTilt)
{
MITK_DEBUG << "When loading image " << o << ": got tilt info:";
//tiltInfo.Print(std::cout);
}
else
{
MITK_DEBUG << "When loading image " << o << ": has NO info.";
}
int numberOfTimesteps = block.GetIntProperty("timesteps", 1);
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 ); // TODO preloaded images, caching..?
block.SetMitkImage( mitkImage );
PopLocale();
return true;
}
diff --git a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.h b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.h
index f71e71735a..c10b908a1b 100644
--- a/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.h
+++ b/Modules/DICOMReader/mitkThreeDnTDICOMSeriesReader.h
@@ -1,61 +1,83 @@
/*===================================================================
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
{
-/*
- \brief Extends sorting/grouping to ThreeD+t image blocks.
+/**
+ \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 );
+ /// \brief Control whether 3D+t grouping shall actually be attempted.
void SetGroup3DandT(bool on);
// void AllocateOutputImages();
+ /// \brief Load via multiple calls to itk::ImageSeriesReader.
virtual bool LoadImages();
protected:
ThreeDnTDICOMSeriesReader();
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 LoadMitkImageForOutput(unsigned int o);
bool m_Group3DandT;
};
}
#endif