diff --git a/Modules/AlgorithmsExt/src/mitkCropTimestepsImageFilter.cpp b/Modules/AlgorithmsExt/src/mitkCropTimestepsImageFilter.cpp index a4caf11eff..d1c2edf31d 100644 --- a/Modules/AlgorithmsExt/src/mitkCropTimestepsImageFilter.cpp +++ b/Modules/AlgorithmsExt/src/mitkCropTimestepsImageFilter.cpp @@ -1,147 +1,158 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCropTimestepsImageFilter.h" #include #include #include #include void mitk::CropTimestepsImageFilter::VerifyInputImage(const mitk::Image* inputImage) const { if (!inputImage->IsInitialized()) mitkThrow() << "Input image is not initialized."; if (!inputImage->IsVolumeSet()) mitkThrow() << "Input image volume is not set."; auto geometry = inputImage->GetGeometry(); if (nullptr == geometry || !geometry->IsValid()) mitkThrow() << "Input image has invalid geometry."; if (inputImage->GetDimension() != 4) { mitkThrow() << "CropTimestepsImageFilter only works with 2D+t and 3D+t images."; } if (inputImage->GetTimeSteps() ==1) { mitkThrow() << "Input image has only one timestep."; } if (!geometry->GetImageGeometry()) mitkThrow() << "Geometry of input image is not an image geometry."; } void mitk::CropTimestepsImageFilter::GenerateOutputInformation() { Image::ConstPointer input = this->GetInput(); Image::Pointer output = this->GetOutput(); if (m_LowerBoundaryTimestep > m_UpperBoundaryTimestep) { mitkThrow() << "lower timestep is larger than upper timestep."; } if (m_UpperBoundaryTimestep == std::numeric_limits::max()) { m_UpperBoundaryTimestep = input->GetTimeSteps(); } else if (m_UpperBoundaryTimestep > input->GetTimeSteps()) { m_UpperBoundaryTimestep = input->GetTimeSteps(); MITK_WARN << "upper boundary timestep set to " << m_UpperBoundaryTimestep; } m_DesiredRegion = ComputeDesiredRegion(); unsigned int dimension = input->GetDimension(); auto dimensions = new unsigned int[dimension]; itk2vtk(m_DesiredRegion.GetSize(), dimensions); if (dimension > 3) memcpy(dimensions + 3, input->GetDimensions() + 3, (dimension - 3) * sizeof(unsigned int)); dimensions[3] = m_UpperBoundaryTimestep - m_LowerBoundaryTimestep; // create basic slicedGeometry that will be initialized below output->Initialize(mitk::PixelType(input->GetPixelType()), dimension, dimensions); delete[] dimensions; auto newTimeGeometry = AdaptTimeGeometry(input->GetTimeGeometry(), m_LowerBoundaryTimestep, m_UpperBoundaryTimestep); output->SetTimeGeometry(newTimeGeometry); output->SetPropertyList(input->GetPropertyList()); } mitk::SlicedData::RegionType mitk::CropTimestepsImageFilter::ComputeDesiredRegion() const { auto desiredRegion = this->GetInput()->GetLargestPossibleRegion(); auto index = desiredRegion.GetIndex(); auto size = desiredRegion.GetSize(); unsigned int timeDimension = 3; index[timeDimension] = m_LowerBoundaryTimestep; size[timeDimension] = m_UpperBoundaryTimestep - m_LowerBoundaryTimestep; desiredRegion.SetIndex(index); desiredRegion.SetSize(size); return desiredRegion; } mitk::TimeGeometry::Pointer mitk::CropTimestepsImageFilter::AdaptTimeGeometry(mitk::TimeGeometry::ConstPointer sourceGeometry, unsigned int startTimestep, unsigned int endTimestep) const { auto newTimeGeometry = mitk::ArbitraryTimeGeometry::New(); newTimeGeometry->ClearAllGeometries(); - for (unsigned int timestep = startTimestep; timestep < endTimestep; timestep++) { + for (unsigned int timestep = startTimestep; timestep < endTimestep; timestep++) + { auto geometryForTimePoint = sourceGeometry->GetGeometryForTimeStep(timestep); - newTimeGeometry->AppendNewTimeStep(geometryForTimePoint, - sourceGeometry->GetMinimumTimePoint(timestep), - sourceGeometry->GetMaximumTimePoint(timestep)); + auto minTP = sourceGeometry->GetMinimumTimePoint(timestep); + auto maxTP = sourceGeometry->GetMaximumTimePoint(timestep); + /////////////////////////////////////// + // Workarround T27883. See https://phabricator.mitk.org/T27883#219473 for more details. + // This workarround should be removed as soon as T28262 is solved! + if (timestep + 1 == sourceGeometry->CountTimeSteps() && minTP == maxTP) + { + maxTP = minTP + 1.; + } + // End of workarround for T27883 + ////////////////////////////////////// + + newTimeGeometry->AppendNewTimeStepClone(geometryForTimePoint, minTP, maxTP); } return newTimeGeometry.GetPointer(); } void mitk::CropTimestepsImageFilter::GenerateData() { const auto* inputImage = this->GetInput(); mitk::Image::Pointer output = this->GetOutput(); if ((output->IsInitialized() == false)) return; auto timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(inputImage); unsigned int timeStart = m_DesiredRegion.GetIndex(3); unsigned int timeEnd = timeStart + m_DesiredRegion.GetSize(3); for (unsigned int timestep = timeStart; timestep < timeEnd; ++timestep) { timeSelector->SetTimeNr(timestep); timeSelector->UpdateLargestPossibleRegion(); mitk::ImageReadAccessor imageAccessorWithOneTimestep(timeSelector->GetOutput()); output->SetVolume(imageAccessorWithOneTimestep.GetData(), timestep-timeStart); } } void mitk::CropTimestepsImageFilter::SetInput(const InputImageType* image) { if (this->GetInput() == image) return; Superclass::SetInput(image); } void mitk::CropTimestepsImageFilter::SetInput(unsigned int index, const InputImageType* image) { if (0 != index) mitkThrow() << "Input index " << index << " is invalid."; this->SetInput(image); } void mitk::CropTimestepsImageFilter::VerifyInputInformation() { Superclass::VerifyInputInformation(); VerifyInputImage(this->GetInput()); } diff --git a/Modules/Core/src/DataManagement/mitkArbitraryTimeGeometry.cpp b/Modules/Core/src/DataManagement/mitkArbitraryTimeGeometry.cpp index e593a15eca..c0e0037e9e 100644 --- a/Modules/Core/src/DataManagement/mitkArbitraryTimeGeometry.cpp +++ b/Modules/Core/src/DataManagement/mitkArbitraryTimeGeometry.cpp @@ -1,307 +1,350 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include mitk::ArbitraryTimeGeometry::ArbitraryTimeGeometry() = default; mitk::ArbitraryTimeGeometry::~ArbitraryTimeGeometry() = default; void mitk::ArbitraryTimeGeometry::Initialize() { this->ClearAllGeometries(); Geometry3D::Pointer geo = Geometry3D::New(); geo->Initialize(); this->AppendNewTimeStep(geo, 0, 1); Update(); } mitk::TimeStepType mitk::ArbitraryTimeGeometry::CountTimeSteps() const { return static_cast(m_GeometryVector.size()); } mitk::TimePointType mitk::ArbitraryTimeGeometry::GetMinimumTimePoint() const { return m_MinimumTimePoints.empty() ? 0.0 : m_MinimumTimePoints.front(); } mitk::TimePointType mitk::ArbitraryTimeGeometry::GetMaximumTimePoint() const { TimePointType result = 0; if ( !m_MaximumTimePoints.empty() ) { result = m_MaximumTimePoints.back(); } + + /////////////////////////////////////// + // Workarround T27883. See https://phabricator.mitk.org/T27883#219473 for more details. + // This workarround should be removed as soon as T28262 is solved! + if (this->HasCollapsedFinalTimeStep()) + { + result = m_MinimumTimePoints.back() + 1.; + } + // End of workarround for T27883 + ////////////////////////////////////// + return result; } mitk::TimePointType mitk::ArbitraryTimeGeometry::GetMinimumTimePoint( TimeStepType step ) const { - TimePointType result = GetMinimumTimePoint(); - if (step > 0 && step <= m_MaximumTimePoints.size()) + TimePointType result = 0; + if (step < m_MinimumTimePoints.size()) { - result = m_MaximumTimePoints[step - 1]; + result = m_MinimumTimePoints[step]; } + return result; }; mitk::TimePointType mitk::ArbitraryTimeGeometry::GetMaximumTimePoint( TimeStepType step ) const { TimePointType result = 0; if (step < m_MaximumTimePoints.size()) { result = m_MaximumTimePoints[step]; } + + /////////////////////////////////////// + // Workarround T27883. See https://phabricator.mitk.org/T27883#219473 for more details. + // This workarround should be removed as soon as T28262 is solved! + if (step + 1 == m_MaximumTimePoints.size() && this->HasCollapsedFinalTimeStep()) + { + result = m_MinimumTimePoints[step] + 1.; + } + // End of workarround for T27883 + ////////////////////////////////////// + return result; }; mitk::TimeBounds mitk::ArbitraryTimeGeometry::GetTimeBounds() const { TimeBounds bounds; bounds[0] = this->GetMinimumTimePoint(); bounds[1] = this->GetMaximumTimePoint(); return bounds; } mitk::TimeBounds mitk::ArbitraryTimeGeometry::GetTimeBounds(TimeStepType step) const { TimeBounds bounds; bounds[0] = this->GetMinimumTimePoint( step ); bounds[1] = this->GetMaximumTimePoint( step ); return bounds; } bool mitk::ArbitraryTimeGeometry::IsValidTimePoint(TimePointType timePoint) const { return this->GetMinimumTimePoint() <= timePoint && (timePoint < this->GetMaximumTimePoint() || (this->HasCollapsedFinalTimeStep() && timePoint <= this->GetMaximumTimePoint())); } bool mitk::ArbitraryTimeGeometry::IsValidTimeStep(TimeStepType timeStep) const { return timeStep < this->CountTimeSteps(); } mitk::TimePointType mitk::ArbitraryTimeGeometry::TimeStepToTimePoint( TimeStepType timeStep ) const { TimePointType result = 0.0; if (timeStep < m_MinimumTimePoints.size() ) { result = m_MinimumTimePoints[timeStep]; } return result; } mitk::TimeStepType mitk::ArbitraryTimeGeometry::TimePointToTimeStep(TimePointType timePoint) const { mitk::TimeStepType result = 0; if (timePoint >= GetMinimumTimePoint()) { for (auto pos = m_MaximumTimePoints.cbegin(); pos != m_MaximumTimePoints.cend(); ++pos) { - if (timePoint < *pos || (pos==std::prev(m_MaximumTimePoints.cend()) && timePoint <= *pos && this->HasCollapsedFinalTimeStep())) + /////////////////////////////////////// + // Workarround T27883. See https://phabricator.mitk.org/T27883#219473 for more details. + // The part ("+1.") inline marked as workarround should be removed as soon as T28262 is solved! + if (timePoint < *pos + || (pos == std::prev(m_MaximumTimePoints.cend()) + && timePoint <= *pos +1.//<- +1. is the workarround + && this->HasCollapsedFinalTimeStep())) { break; } + // End of workarround for T27883 + ////////////////////////////////////// ++result; } } return result; } mitk::BaseGeometry::Pointer mitk::ArbitraryTimeGeometry::GetGeometryForTimeStep(TimeStepType timeStep) const { if ( IsValidTimeStep( timeStep ) ) { return m_GeometryVector[timeStep]; } else { return nullptr; } } mitk::BaseGeometry::Pointer mitk::ArbitraryTimeGeometry::GetGeometryForTimePoint( TimePointType timePoint ) const { if ( this->IsValidTimePoint( timePoint ) ) { const TimeStepType timeStep = this->TimePointToTimeStep( timePoint ); return this->GetGeometryForTimeStep( timeStep ); } else { return nullptr; } } mitk::BaseGeometry::Pointer mitk::ArbitraryTimeGeometry::GetGeometryCloneForTimeStep( TimeStepType timeStep ) const { if ( timeStep >= m_GeometryVector.size() ) return nullptr; return m_GeometryVector[timeStep]->Clone(); } bool mitk::ArbitraryTimeGeometry::IsValid() const { bool isValid = true; isValid &= m_GeometryVector.size() > 0; return isValid; } void mitk::ArbitraryTimeGeometry::ClearAllGeometries() { m_GeometryVector.clear(); m_MinimumTimePoints.clear(); m_MaximumTimePoints.clear(); } void mitk::ArbitraryTimeGeometry::ReserveSpaceForGeometries( TimeStepType numberOfGeometries ) { m_GeometryVector.reserve( numberOfGeometries ); m_MinimumTimePoints.reserve( numberOfGeometries ); m_MaximumTimePoints.reserve( numberOfGeometries ); } void mitk::ArbitraryTimeGeometry::Expand( mitk::TimeStepType size ) { m_GeometryVector.reserve( size ); const mitk::TimeStepType lastIndex = this->CountTimeSteps() - 1; const TimePointType minTP = this->GetMinimumTimePoint( lastIndex ); TimePointType maxTP = this->GetMaximumTimePoint( lastIndex ); const TimePointType duration = maxTP - minTP; while (m_GeometryVector.size() < size) { m_GeometryVector.push_back( Geometry3D::New().GetPointer() ); m_MinimumTimePoints.push_back( maxTP ); maxTP += duration; m_MaximumTimePoints.push_back( maxTP ); } } void mitk::ArbitraryTimeGeometry::ReplaceTimeStepGeometries(const BaseGeometry *geometry) { for ( auto pos = m_GeometryVector.begin(); pos != m_GeometryVector.end(); ++pos ) { *pos = geometry->Clone(); } } void mitk::ArbitraryTimeGeometry::SetTimeStepGeometry(BaseGeometry *geometry, TimeStepType timeStep) { assert( timeStep <= m_GeometryVector.size() ); if ( timeStep == m_GeometryVector.size() ) { m_GeometryVector.push_back( geometry ); } m_GeometryVector[timeStep] = geometry; } itk::LightObject::Pointer mitk::ArbitraryTimeGeometry::InternalClone() const { itk::LightObject::Pointer parent = Superclass::InternalClone(); ArbitraryTimeGeometry::Pointer newTimeGeometry = dynamic_cast(parent.GetPointer()); newTimeGeometry->m_MinimumTimePoints = this->m_MinimumTimePoints; newTimeGeometry->m_MaximumTimePoints = this->m_MaximumTimePoints; newTimeGeometry->m_GeometryVector.clear(); for (TimeStepType i = 0; i < CountTimeSteps(); ++i) { newTimeGeometry->m_GeometryVector.push_back( this->m_GeometryVector[i]->Clone() ); } return parent; } void mitk::ArbitraryTimeGeometry::AppendNewTimeStep(BaseGeometry *geometry, TimePointType minimumTimePoint, TimePointType maximumTimePoint) { if ( !geometry ) { mitkThrow() << "Cannot append geometry to time geometry. Invalid geometry passed (nullptr pointer)."; } if (maximumTimePoint < minimumTimePoint) { mitkThrow() << "Cannot append geometry to time geometry. Time bound conflict. Maxmimum time point ("< minimumTimePoint ) { mitkThrow() << "Cannot append geometry to time geometry. Time bound conflict new time point and currently last time point overlapp."; } } m_GeometryVector.push_back( geometry ); m_MinimumTimePoints.push_back( minimumTimePoint ); m_MaximumTimePoints.push_back( maximumTimePoint ); } void mitk::ArbitraryTimeGeometry::AppendNewTimeStepClone(const BaseGeometry *geometry, TimePointType minimumTimePoint, TimePointType maximumTimePoint) { BaseGeometry::Pointer clone = geometry->Clone(); this->AppendNewTimeStep(clone, minimumTimePoint, maximumTimePoint); }; void mitk::ArbitraryTimeGeometry::PrintSelf(std::ostream &os, itk::Indent indent) const { Superclass::PrintSelf( os, indent ); os << indent << " MinimumTimePoint: " << this->GetMinimumTimePoint() << " ms" << std::endl; os << indent << " MaximumTimePoint: " << this->GetMaximumTimePoint() << " ms" << std::endl; os << std::endl; os << indent << " min TimeBounds: " << std::endl; for (TimeStepType i = 0; i < m_MinimumTimePoints.size(); ++i) { os << indent.GetNextIndent() << "Step " << i << ": " << m_MinimumTimePoints[i] << " ms" << std::endl; } os << std::endl; os << indent << " max TimeBounds: " << std::endl; for (TimeStepType i = 0; i < m_MaximumTimePoints.size(); ++i) { os << indent.GetNextIndent() << "Step " << i << ": " << m_MaximumTimePoints[i] << " ms" << std::endl; } + /////////////////////////////////////// + // Workarround T27883. See https://phabricator.mitk.org/T27883#219473 for more details. + // This workarround should be removed as soon as T28262 is solved! + if (this->HasCollapsedFinalTimeStep()) + { + os << "Caution: This time geometry has a collapsed finale time step." << std::endl; + os << " Most likely reason is that no duration could be deduced from the original data" << std::endl; + os << " (e.g.DICOM dynamic series stored as single frame images)." << std::endl; + os << " Currently we expand it by 1 ms (see T27883 for more details)." << std::endl; + } + // End of workarround for T27883 + ////////////////////////////////////// } bool mitk::ArbitraryTimeGeometry::HasCollapsedFinalTimeStep() const { bool result = false; if (!m_MaximumTimePoints.empty() && !m_MinimumTimePoints.empty()) { result = m_MinimumTimePoints.back() == m_MaximumTimePoints.back(); } return result; } diff --git a/Modules/Core/src/IO/mitkItkImageIO.cpp b/Modules/Core/src/IO/mitkItkImageIO.cpp index 13297e122a..78fada2e17 100644 --- a/Modules/Core/src/IO/mitkItkImageIO.cpp +++ b/Modules/Core/src/IO/mitkItkImageIO.cpp @@ -1,736 +1,748 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkItkImageIO.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mitk { const char *const PROPERTY_NAME_TIMEGEOMETRY_TYPE = "org.mitk.timegeometry.type"; const char *const PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS = "org.mitk.timegeometry.timepoints"; const char *const PROPERTY_KEY_TIMEGEOMETRY_TYPE = "org_mitk_timegeometry_type"; const char *const PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS = "org_mitk_timegeometry_timepoints"; const char* const PROPERTY_KEY_UID = "org_mitk_uid"; ItkImageIO::ItkImageIO(const ItkImageIO &other) : AbstractFileIO(other), m_ImageIO(dynamic_cast(other.m_ImageIO->Clone().GetPointer())) { this->InitializeDefaultMetaDataKeys(); } std::vector ItkImageIO::FixUpImageIOExtensions(const std::string &imageIOName) { std::vector extensions; // Try to fix-up some known ITK image IO classes if (imageIOName == "GiplImageIO") { extensions.push_back("gipl"); extensions.push_back("gipl.gz"); } else if (imageIOName == "GDCMImageIO") { extensions.push_back("gdcm"); extensions.push_back("dcm"); extensions.push_back("DCM"); extensions.push_back("dc3"); extensions.push_back("DC3"); extensions.push_back("ima"); extensions.push_back("img"); } else if (imageIOName == "PNGImageIO") { extensions.push_back("png"); extensions.push_back("PNG"); } else if (imageIOName == "StimulateImageIO") { extensions.push_back("spr"); } else if (imageIOName == "HDF5ImageIO") { extensions.push_back("hdf"); extensions.push_back("h4"); extensions.push_back("hdf4"); extensions.push_back("h5"); extensions.push_back("hdf5"); extensions.push_back("he4"); extensions.push_back("he5"); extensions.push_back("hd5"); } else if ("GE4ImageIO" == imageIOName || "GE5ImageIO" == imageIOName || "Bruker2dseqImageIO" == imageIOName) { extensions.push_back(""); } if (!extensions.empty()) { MITK_DEBUG << "Fixing up known extensions for " << imageIOName; } return extensions; } void ItkImageIO::FixUpCustomMimeTypeName(const std::string &imageIOName, CustomMimeType &customMimeType) { if ("GE4ImageIO" == imageIOName) { customMimeType.SetName(this->AbstractFileReader::GetMimeTypePrefix() + "ge4"); } else if ("GE5ImageIO" == imageIOName) { customMimeType.SetName(this->AbstractFileReader::GetMimeTypePrefix() + "ge5"); } else if ("Bruker2dseqImageIO" == imageIOName) { customMimeType.SetName(this->AbstractFileReader::GetMimeTypePrefix() + "bruker2dseq"); } } ItkImageIO::ItkImageIO(itk::ImageIOBase::Pointer imageIO) : AbstractFileIO(Image::GetStaticNameOfClass()), m_ImageIO(imageIO) { if (m_ImageIO.IsNull()) { mitkThrow() << "ITK ImageIOBase argument must not be nullptr"; } this->AbstractFileReader::SetMimeTypePrefix(IOMimeTypes::DEFAULT_BASE_NAME() + ".image."); this->InitializeDefaultMetaDataKeys(); std::vector readExtensions = m_ImageIO->GetSupportedReadExtensions(); if (readExtensions.empty()) { std::string imageIOName = m_ImageIO->GetNameOfClass(); MITK_DEBUG << "ITK ImageIOBase " << imageIOName << " does not provide read extensions"; readExtensions = FixUpImageIOExtensions(imageIOName); } CustomMimeType customReaderMimeType; customReaderMimeType.SetCategory("Images"); for (std::vector::const_iterator iter = readExtensions.begin(), endIter = readExtensions.end(); iter != endIter; ++iter) { std::string extension = *iter; if (!extension.empty() && extension[0] == '.') { extension.assign(iter->begin() + 1, iter->end()); } customReaderMimeType.AddExtension(extension); } auto extensions = customReaderMimeType.GetExtensions(); if (extensions.empty() || (extensions.size() == 1 && extensions[0].empty())) { std::string imageIOName = m_ImageIO->GetNameOfClass(); FixUpCustomMimeTypeName(imageIOName, customReaderMimeType); } this->AbstractFileReader::SetMimeType(customReaderMimeType); std::vector writeExtensions = imageIO->GetSupportedWriteExtensions(); if (writeExtensions.empty()) { std::string imageIOName = imageIO->GetNameOfClass(); MITK_DEBUG << "ITK ImageIOBase " << imageIOName << " does not provide write extensions"; writeExtensions = FixUpImageIOExtensions(imageIOName); } if (writeExtensions != readExtensions) { CustomMimeType customWriterMimeType; customWriterMimeType.SetCategory("Images"); for (std::vector::const_iterator iter = writeExtensions.begin(), endIter = writeExtensions.end(); iter != endIter; ++iter) { std::string extension = *iter; if (!extension.empty() && extension[0] == '.') { extension.assign(iter->begin() + 1, iter->end()); } customWriterMimeType.AddExtension(extension); } auto extensions = customWriterMimeType.GetExtensions(); if (extensions.empty() || (extensions.size() == 1 && extensions[0].empty())) { std::string imageIOName = m_ImageIO->GetNameOfClass(); FixUpCustomMimeTypeName(imageIOName, customWriterMimeType); } this->AbstractFileWriter::SetMimeType(customWriterMimeType); } std::string description = std::string("ITK ") + imageIO->GetNameOfClass(); this->SetReaderDescription(description); this->SetWriterDescription(description); this->RegisterService(); } ItkImageIO::ItkImageIO(const CustomMimeType &mimeType, itk::ImageIOBase::Pointer imageIO, int rank) : AbstractFileIO(Image::GetStaticNameOfClass(), mimeType, std::string("ITK ") + imageIO->GetNameOfClass()), m_ImageIO(imageIO) { if (m_ImageIO.IsNull()) { mitkThrow() << "ITK ImageIOBase argument must not be nullptr"; } this->AbstractFileReader::SetMimeTypePrefix(IOMimeTypes::DEFAULT_BASE_NAME() + ".image."); this->InitializeDefaultMetaDataKeys(); if (rank) { this->AbstractFileReader::SetRanking(rank); this->AbstractFileWriter::SetRanking(rank); } this->RegisterService(); } std::vector ConvertMetaDataObjectToTimePointList(const itk::MetaDataObjectBase* data) { const auto* timeGeometryTimeData = dynamic_cast*>(data); std::vector result; if (timeGeometryTimeData) { std::string dataStr = timeGeometryTimeData->GetMetaDataObjectValue(); std::stringstream stream(dataStr); TimePointType tp; while (stream >> tp) { result.push_back(tp); } } return result; }; itk::MetaDataObjectBase::Pointer ConvertTimePointListToMetaDataObject(const mitk::TimeGeometry* timeGeometry) { std::stringstream stream; stream << timeGeometry->GetTimeBounds(0)[0]; const auto maxTimePoints = timeGeometry->CountTimeSteps(); for (TimeStepType pos = 0; pos < maxTimePoints; ++pos) { - stream << " " << timeGeometry->GetTimeBounds(pos)[1]; + auto timeBounds = timeGeometry->GetTimeBounds(pos); + + /////////////////////////////////////// + // Workarround T27883. See https://phabricator.mitk.org/T27883#219473 for more details. + // This workarround should be removed as soon as T28262 is solved! + if (pos + 1 == maxTimePoints && timeBounds[0]==timeBounds[1]) + { + timeBounds[1] = timeBounds[0] + 1.; + } + // End of workarround for T27883 + ////////////////////////////////////// + + stream << " " << timeBounds[1]; } auto result = itk::MetaDataObject::New(); result->SetMetaDataObjectValue(stream.str()); return result.GetPointer(); }; std::vector ItkImageIO::DoRead() { std::vector result; mitk::LocaleSwitch localeSwitch("C"); Image::Pointer image = Image::New(); const unsigned int MINDIM = 2; const unsigned int MAXDIM = 4; const std::string path = this->GetLocalFileName(); MITK_INFO << "loading " << path << " via itk::ImageIOFactory... " << std::endl; // Check to see if we can read the file given the name or prefix if (path.empty()) { mitkThrow() << "Empty filename in mitk::ItkImageIO "; } // Got to allocate space for the image. Determine the characteristics of // the image. m_ImageIO->SetFileName(path); m_ImageIO->ReadImageInformation(); unsigned int ndim = m_ImageIO->GetNumberOfDimensions(); if (ndim < MINDIM || ndim > MAXDIM) { MITK_WARN << "Sorry, only dimensions 2, 3 and 4 are supported. The given file has " << ndim << " dimensions! Reading as 4D."; ndim = MAXDIM; } itk::ImageIORegion ioRegion(ndim); itk::ImageIORegion::SizeType ioSize = ioRegion.GetSize(); itk::ImageIORegion::IndexType ioStart = ioRegion.GetIndex(); unsigned int dimensions[MAXDIM]; dimensions[0] = 0; dimensions[1] = 0; dimensions[2] = 0; dimensions[3] = 0; ScalarType spacing[MAXDIM]; spacing[0] = 1.0f; spacing[1] = 1.0f; spacing[2] = 1.0f; spacing[3] = 1.0f; Point3D origin; origin.Fill(0); unsigned int i; for (i = 0; i < ndim; ++i) { ioStart[i] = 0; ioSize[i] = m_ImageIO->GetDimensions(i); if (i < MAXDIM) { dimensions[i] = m_ImageIO->GetDimensions(i); spacing[i] = m_ImageIO->GetSpacing(i); if (spacing[i] <= 0) spacing[i] = 1.0f; } if (i < 3) { origin[i] = m_ImageIO->GetOrigin(i); } } ioRegion.SetSize(ioSize); ioRegion.SetIndex(ioStart); MITK_INFO << "ioRegion: " << ioRegion << std::endl; m_ImageIO->SetIORegion(ioRegion); void *buffer = new unsigned char[m_ImageIO->GetImageSizeInBytes()]; m_ImageIO->Read(buffer); image->Initialize(MakePixelType(m_ImageIO), ndim, dimensions); image->SetImportChannel(buffer, 0, Image::ManageMemory); const itk::MetaDataDictionary &dictionary = m_ImageIO->GetMetaDataDictionary(); // access direction of itk::Image and include spacing mitk::Matrix3D matrix; matrix.SetIdentity(); unsigned int j, itkDimMax3 = (ndim >= 3 ? 3 : ndim); for (i = 0; i < itkDimMax3; ++i) for (j = 0; j < itkDimMax3; ++j) matrix[i][j] = m_ImageIO->GetDirection(j)[i]; // re-initialize PlaneGeometry with origin and direction PlaneGeometry *planeGeometry = image->GetSlicedGeometry(0)->GetPlaneGeometry(0); planeGeometry->SetOrigin(origin); planeGeometry->GetIndexToWorldTransform()->SetMatrix(matrix); // re-initialize SlicedGeometry3D SlicedGeometry3D *slicedGeometry = image->GetSlicedGeometry(0); slicedGeometry->InitializeEvenlySpaced(planeGeometry, image->GetDimension(2)); slicedGeometry->SetSpacing(spacing); MITK_INFO << slicedGeometry->GetCornerPoint(false, false, false); MITK_INFO << slicedGeometry->GetCornerPoint(true, true, true); // re-initialize TimeGeometry TimeGeometry::Pointer timeGeometry; if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TYPE) || dictionary.HasKey(PROPERTY_KEY_TIMEGEOMETRY_TYPE)) { // also check for the name because of backwards compatibility. Past code version stored with the name and not with // the key itk::MetaDataObject::ConstPointer timeGeometryTypeData = nullptr; if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TYPE)) { timeGeometryTypeData = dynamic_cast *>(dictionary.Get(PROPERTY_NAME_TIMEGEOMETRY_TYPE)); } else { timeGeometryTypeData = dynamic_cast *>(dictionary.Get(PROPERTY_KEY_TIMEGEOMETRY_TYPE)); } if (timeGeometryTypeData->GetMetaDataObjectValue() == ArbitraryTimeGeometry::GetStaticNameOfClass()) { MITK_INFO << "used time geometry: " << ArbitraryTimeGeometry::GetStaticNameOfClass(); typedef std::vector TimePointVector; TimePointVector timePoints; if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS)) { timePoints = ConvertMetaDataObjectToTimePointList(dictionary.Get(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS)); } else if (dictionary.HasKey(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS)) { timePoints = ConvertMetaDataObjectToTimePointList(dictionary.Get(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS)); } if (timePoints.empty()) { MITK_ERROR << "Stored timepoints are empty. Meta information seems to bee invalid. Switch to ProportionalTimeGeometry fallback"; } else if (timePoints.size() - 1 != image->GetDimension(3)) { MITK_ERROR << "Stored timepoints (" << timePoints.size() - 1 << ") and size of image time dimension (" << image->GetDimension(3) << ") do not match. Switch to ProportionalTimeGeometry fallback"; } else { ArbitraryTimeGeometry::Pointer arbitraryTimeGeometry = ArbitraryTimeGeometry::New(); TimePointVector::const_iterator pos = timePoints.begin(); auto prePos = pos++; for (; pos != timePoints.end(); ++prePos, ++pos) { arbitraryTimeGeometry->AppendNewTimeStepClone(slicedGeometry, *prePos, *pos); } timeGeometry = arbitraryTimeGeometry; } } } if (timeGeometry.IsNull()) { // Fallback. If no other valid time geometry has been created, create a ProportionalTimeGeometry MITK_INFO << "used time geometry: " << ProportionalTimeGeometry::GetStaticNameOfClass(); ProportionalTimeGeometry::Pointer propTimeGeometry = ProportionalTimeGeometry::New(); propTimeGeometry->Initialize(slicedGeometry, image->GetDimension(3)); timeGeometry = propTimeGeometry; } image->SetTimeGeometry(timeGeometry); buffer = nullptr; MITK_INFO << "number of image components: " << image->GetPixelType().GetNumberOfComponents(); for (auto iter = dictionary.Begin(), iterEnd = dictionary.End(); iter != iterEnd; ++iter) { if (iter->second->GetMetaDataObjectTypeInfo() == typeid(std::string)) { const std::string &key = iter->first; std::string assumedPropertyName = key; std::replace(assumedPropertyName.begin(), assumedPropertyName.end(), '_', '.'); std::string mimeTypeName = GetMimeType()->GetName(); // Check if there is already a info for the key and our mime type. mitk::CoreServicePointer propPersistenceService(mitk::CoreServices::GetPropertyPersistence()); IPropertyPersistence::InfoResultType infoList = propPersistenceService->GetInfoByKey(key); auto predicate = [&mimeTypeName](const PropertyPersistenceInfo::ConstPointer &x) { return x.IsNotNull() && x->GetMimeTypeName() == mimeTypeName; }; auto finding = std::find_if(infoList.begin(), infoList.end(), predicate); if (finding == infoList.end()) { auto predicateWild = [](const PropertyPersistenceInfo::ConstPointer &x) { return x.IsNotNull() && x->GetMimeTypeName() == PropertyPersistenceInfo::ANY_MIMETYPE_NAME(); }; finding = std::find_if(infoList.begin(), infoList.end(), predicateWild); } PropertyPersistenceInfo::ConstPointer info; if (finding != infoList.end()) { assumedPropertyName = (*finding)->GetName(); info = *finding; } else { // we have not found anything suitable so we generate our own info auto newInfo = PropertyPersistenceInfo::New(); newInfo->SetNameAndKey(assumedPropertyName, key); newInfo->SetMimeTypeName(PropertyPersistenceInfo::ANY_MIMETYPE_NAME()); info = newInfo; } std::string value = dynamic_cast *>(iter->second.GetPointer())->GetMetaDataObjectValue(); mitk::BaseProperty::Pointer loadedProp = info->GetDeserializationFunction()(value); image->SetProperty(assumedPropertyName.c_str(), loadedProp); // Read properties should be persisted unless they are default properties // which are written anyway bool isDefaultKey(false); for (const auto &defaultKey : m_DefaultMetaDataKeys) { if (defaultKey.length() <= assumedPropertyName.length()) { // does the start match the default key if (assumedPropertyName.substr(0, defaultKey.length()).find(defaultKey) != std::string::npos) { isDefaultKey = true; break; } } } if (!isDefaultKey) { propPersistenceService->AddInfo(info); } } } // Handle UID if (dictionary.HasKey(PROPERTY_KEY_UID)) { itk::MetaDataObject::ConstPointer uidData = dynamic_cast*>(dictionary.Get(PROPERTY_KEY_UID)); if (uidData.IsNotNull()) { mitk::UIDManipulator uidManipulator(image); uidManipulator.SetUID(uidData->GetMetaDataObjectValue()); } } MITK_INFO << "...finished!"; result.push_back(image.GetPointer()); return result; } AbstractFileIO::ConfidenceLevel ItkImageIO::GetReaderConfidenceLevel() const { return m_ImageIO->CanReadFile(GetLocalFileName().c_str()) ? IFileReader::Supported : IFileReader::Unsupported; } void ItkImageIO::Write() { const auto *image = dynamic_cast(this->GetInput()); if (image == nullptr) { mitkThrow() << "Cannot write non-image data"; } // Switch the current locale to "C" LocaleSwitch localeSwitch("C"); // Clone the image geometry, because we might have to change it // for writing purposes BaseGeometry::Pointer geometry = image->GetGeometry()->Clone(); // Check if geometry information will be lost if (image->GetDimension() == 2 && !geometry->Is2DConvertable()) { MITK_WARN << "Saving a 2D image with 3D geometry information. Geometry information will be lost! You might " "consider using Convert2Dto3DImageFilter before saving."; // set matrix to identity mitk::AffineTransform3D::Pointer affTrans = mitk::AffineTransform3D::New(); affTrans->SetIdentity(); mitk::Vector3D spacing = geometry->GetSpacing(); mitk::Point3D origin = geometry->GetOrigin(); geometry->SetIndexToWorldTransform(affTrans); geometry->SetSpacing(spacing); geometry->SetOrigin(origin); } LocalFile localFile(this); const std::string path = localFile.GetFileName(); MITK_INFO << "Writing image: " << path << std::endl; try { // Implementation of writer using itkImageIO directly. This skips the use // of templated itkImageFileWriter, which saves the multiplexing on MITK side. const unsigned int dimension = image->GetDimension(); const unsigned int *const dimensions = image->GetDimensions(); const mitk::PixelType pixelType = image->GetPixelType(); const mitk::Vector3D mitkSpacing = geometry->GetSpacing(); const mitk::Point3D mitkOrigin = geometry->GetOrigin(); // Due to templating in itk, we are forced to save a 4D spacing and 4D Origin, // though they are not supported in MITK itk::Vector spacing4D; spacing4D[0] = mitkSpacing[0]; spacing4D[1] = mitkSpacing[1]; spacing4D[2] = mitkSpacing[2]; spacing4D[3] = 1; // There is no support for a 4D spacing. However, we should have a valid value here itk::Vector origin4D; origin4D[0] = mitkOrigin[0]; origin4D[1] = mitkOrigin[1]; origin4D[2] = mitkOrigin[2]; origin4D[3] = 0; // There is no support for a 4D origin. However, we should have a valid value here // Set the necessary information for imageIO m_ImageIO->SetNumberOfDimensions(dimension); m_ImageIO->SetPixelType(pixelType.GetPixelType()); m_ImageIO->SetComponentType(pixelType.GetComponentType() < PixelComponentUserType ? static_cast(pixelType.GetComponentType()) : itk::ImageIOBase::UNKNOWNCOMPONENTTYPE); m_ImageIO->SetNumberOfComponents(pixelType.GetNumberOfComponents()); itk::ImageIORegion ioRegion(dimension); for (unsigned int i = 0; i < dimension; i++) { m_ImageIO->SetDimensions(i, dimensions[i]); m_ImageIO->SetSpacing(i, spacing4D[i]); m_ImageIO->SetOrigin(i, origin4D[i]); mitk::Vector3D mitkDirection; mitkDirection.SetVnlVector(geometry->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(i)); itk::Vector direction4D; direction4D[0] = mitkDirection[0]; direction4D[1] = mitkDirection[1]; direction4D[2] = mitkDirection[2]; // MITK only supports a 3x3 direction matrix. Due to templating in itk, however, we must // save a 4x4 matrix for 4D images. in this case, add an homogneous component to the matrix. if (i == 3) { direction4D[3] = 1; // homogenous component } else { direction4D[3] = 0; } vnl_vector axisDirection(dimension); for (unsigned int j = 0; j < dimension; j++) { axisDirection[j] = direction4D[j] / spacing4D[i]; } m_ImageIO->SetDirection(i, axisDirection); ioRegion.SetSize(i, image->GetLargestPossibleRegion().GetSize(i)); ioRegion.SetIndex(i, image->GetLargestPossibleRegion().GetIndex(i)); } // use compression if available m_ImageIO->UseCompressionOn(); m_ImageIO->SetIORegion(ioRegion); m_ImageIO->SetFileName(path); // Handle time geometry const auto *arbitraryTG = dynamic_cast(image->GetTimeGeometry()); if (arbitraryTG) { itk::EncapsulateMetaData(m_ImageIO->GetMetaDataDictionary(), PROPERTY_KEY_TIMEGEOMETRY_TYPE, ArbitraryTimeGeometry::GetStaticNameOfClass()); auto metaTimePoints = ConvertTimePointListToMetaDataObject(arbitraryTG); m_ImageIO->GetMetaDataDictionary().Set(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS, metaTimePoints); } // Handle properties mitk::PropertyList::Pointer imagePropertyList = image->GetPropertyList(); for (const auto &property : *imagePropertyList->GetMap()) { mitk::CoreServicePointer propPersistenceService(mitk::CoreServices::GetPropertyPersistence()); IPropertyPersistence::InfoResultType infoList = propPersistenceService->GetInfo(property.first, GetMimeType()->GetName(), true); if (infoList.empty()) { continue; } std::string value = mitk::BaseProperty::VALUE_CANNOT_BE_CONVERTED_TO_STRING; try { value = infoList.front()->GetSerializationFunction()(property.second); } catch (const std::exception& e) { MITK_ERROR << "Error when serializing content of property. This often indicates the use of an out dated reader. Property will not be stored. Skipped property: " << property.first << ". Reason: " << e.what(); } catch (...) { MITK_ERROR << "Unkown error when serializing content of property. This often indicates the use of an out dated reader. Property will not be stored. Skipped property: " << property.first; } if (value == mitk::BaseProperty::VALUE_CANNOT_BE_CONVERTED_TO_STRING) { continue; } std::string key = infoList.front()->GetKey(); itk::EncapsulateMetaData(m_ImageIO->GetMetaDataDictionary(), key, value); } // Handle UID itk::EncapsulateMetaData(m_ImageIO->GetMetaDataDictionary(), PROPERTY_KEY_UID, image->GetUID()); ImageReadAccessor imageAccess(image); LocaleSwitch localeSwitch2("C"); m_ImageIO->Write(imageAccess.GetData()); } catch (const std::exception &e) { mitkThrow() << e.what(); } } AbstractFileIO::ConfidenceLevel ItkImageIO::GetWriterConfidenceLevel() const { // Check if the image dimension is supported const auto *image = dynamic_cast(this->GetInput()); if (image == nullptr) { // We cannot write a null object, DUH! return IFileWriter::Unsupported; } if (!m_ImageIO->SupportsDimension(image->GetDimension())) { // okay, dimension is not supported. We have to look at a special case: // 3D-Image with one slice. We can treat that as a 2D image. if ((image->GetDimension() == 3) && (image->GetSlicedGeometry()->GetSlices() == 1)) return IFileWriter::Supported; else return IFileWriter::Unsupported; } // Check if geometry information will be lost if (image->GetDimension() == 2 && !image->GetGeometry()->Is2DConvertable()) { return IFileWriter::PartiallySupported; } return IFileWriter::Supported; } ItkImageIO *ItkImageIO::IOClone() const { return new ItkImageIO(*this); } void ItkImageIO::InitializeDefaultMetaDataKeys() { this->m_DefaultMetaDataKeys.push_back("NRRD.space"); this->m_DefaultMetaDataKeys.push_back("NRRD.kinds"); this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TYPE); this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS); this->m_DefaultMetaDataKeys.push_back("ITK.InputFilterName"); } } diff --git a/Modules/Core/test/mitkArbitraryTimeGeometryTest.cpp b/Modules/Core/test/mitkArbitraryTimeGeometryTest.cpp index b05b96b04c..4935c25e5d 100644 --- a/Modules/Core/test/mitkArbitraryTimeGeometryTest.cpp +++ b/Modules/Core/test/mitkArbitraryTimeGeometryTest.cpp @@ -1,504 +1,532 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkArbitraryTimeGeometry.h" #include "mitkGeometry3D.h" #include "mitkTestFixture.h" #include "mitkTestingMacros.h" #include class mitkArbitraryTimeGeometryTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkArbitraryTimeGeometryTestSuite); // Test the append method MITK_TEST(CountTimeSteps); MITK_TEST(GetMinimumTimePoint); MITK_TEST(GetMaximumTimePoint); MITK_TEST(GetTimeBounds); MITK_TEST(IsValidTimePoint); MITK_TEST(TimeStepToTimePoint); MITK_TEST(TimePointToTimeStep); MITK_TEST(GetGeometryCloneForTimeStep); MITK_TEST(GetGeometryForTimeStep); MITK_TEST(GetGeometryForTimePoint); MITK_TEST(IsValid); MITK_TEST(Expand); MITK_TEST(ReplaceTimeStepGeometries); MITK_TEST(ClearAllGeometries); MITK_TEST(AppendNewTimeStep); MITK_TEST(HasCollapsedFinalTimeStep); CPPUNIT_TEST_SUITE_END(); private: mitk::Geometry3D::Pointer m_Geometry1; mitk::Geometry3D::Pointer m_Geometry2; mitk::Geometry3D::Pointer m_Geometry3; mitk::Geometry3D::Pointer m_Geometry3_5; mitk::Geometry3D::Pointer m_Geometry4; mitk::Geometry3D::Pointer m_Geometry5; mitk::Geometry3D::Pointer m_InvalidGeometry; mitk::Geometry3D::Pointer m_NewGeometry; mitk::TimePointType m_Geometry1MinTP; mitk::TimePointType m_Geometry2MinTP; mitk::TimePointType m_Geometry3MinTP; mitk::TimePointType m_Geometry3_5MinTP; mitk::TimePointType m_Geometry4MinTP; mitk::TimePointType m_Geometry5MinTP; mitk::TimePointType m_NewGeometryMinTP; mitk::TimePointType m_Geometry1MaxTP; mitk::TimePointType m_Geometry2MaxTP; mitk::TimePointType m_Geometry3MaxTP; mitk::TimePointType m_Geometry3_5MaxTP; mitk::TimePointType m_Geometry4MaxTP; mitk::TimePointType m_Geometry5MaxTP; mitk::TimePointType m_NewGeometryMaxTP; mitk::ArbitraryTimeGeometry::Pointer m_emptyTimeGeometry; mitk::ArbitraryTimeGeometry::Pointer m_initTimeGeometry; mitk::ArbitraryTimeGeometry::Pointer m_12345TimeGeometry; mitk::ArbitraryTimeGeometry::Pointer m_123TimeGeometry; mitk::ArbitraryTimeGeometry::Pointer m_123TimeGeometryWithCollapsedEnd; mitk::ArbitraryTimeGeometry::Pointer m_123TimeGeometryWithCollapsedInterim; public: void setUp() override { mitk::TimeBounds bounds; m_Geometry1 = mitk::Geometry3D::New(); m_Geometry2 = mitk::Geometry3D::New(); m_Geometry3 = mitk::Geometry3D::New(); m_Geometry3_5 = mitk::Geometry3D::New(); m_Geometry4 = mitk::Geometry3D::New(); m_Geometry5 = mitk::Geometry3D::New(); m_Geometry1MinTP = 1; m_Geometry2MinTP = 2; m_Geometry3MinTP = 3; m_Geometry3_5MinTP = 3.5; m_Geometry4MinTP = 4; m_Geometry5MinTP = 5; m_Geometry1MaxTP = 1.9; m_Geometry2MaxTP = 2.9; m_Geometry3MaxTP = 3.9; m_Geometry3_5MaxTP = 3.9; m_Geometry4MaxTP = 4.9; m_Geometry5MaxTP = 5.9; m_NewGeometry = mitk::Geometry3D::New(); m_NewGeometryMinTP = 20; m_NewGeometryMaxTP = 21.9; mitk::Point3D origin(42); m_NewGeometry->SetOrigin(origin); m_emptyTimeGeometry = mitk::ArbitraryTimeGeometry::New(); m_emptyTimeGeometry->ClearAllGeometries(); m_initTimeGeometry = mitk::ArbitraryTimeGeometry::New(); m_initTimeGeometry->Initialize(); m_12345TimeGeometry = mitk::ArbitraryTimeGeometry::New(); m_12345TimeGeometry->ClearAllGeometries(); m_12345TimeGeometry->AppendNewTimeStep(m_Geometry1, m_Geometry1MinTP, m_Geometry1MaxTP); m_12345TimeGeometry->AppendNewTimeStep(m_Geometry2, m_Geometry2MinTP, m_Geometry2MaxTP); m_12345TimeGeometry->AppendNewTimeStep(m_Geometry3, m_Geometry3MinTP, m_Geometry3MaxTP); m_12345TimeGeometry->AppendNewTimeStep(m_Geometry4, m_Geometry4MinTP, m_Geometry4MaxTP); m_12345TimeGeometry->AppendNewTimeStep(m_Geometry5, m_Geometry5MinTP, m_Geometry5MaxTP); m_123TimeGeometry = mitk::ArbitraryTimeGeometry::New(); m_123TimeGeometry->ClearAllGeometries(); m_123TimeGeometry->AppendNewTimeStep(m_Geometry1, m_Geometry1MinTP, m_Geometry1MaxTP); m_123TimeGeometry->AppendNewTimeStep(m_Geometry2, m_Geometry2MinTP, m_Geometry2MaxTP); m_123TimeGeometry->AppendNewTimeStep(m_Geometry3, m_Geometry3MinTP, m_Geometry3MaxTP); m_123TimeGeometryWithCollapsedEnd = mitk::ArbitraryTimeGeometry::New(); m_123TimeGeometryWithCollapsedEnd->ClearAllGeometries(); m_123TimeGeometryWithCollapsedEnd->AppendNewTimeStep(m_Geometry1, m_Geometry1MinTP, m_Geometry1MaxTP); m_123TimeGeometryWithCollapsedEnd->AppendNewTimeStep(m_Geometry2, m_Geometry2MinTP, m_Geometry2MaxTP); m_123TimeGeometryWithCollapsedEnd->AppendNewTimeStep(m_Geometry3, m_Geometry3MinTP, m_Geometry3MinTP); m_123TimeGeometryWithCollapsedInterim = mitk::ArbitraryTimeGeometry::New(); m_123TimeGeometryWithCollapsedInterim->ClearAllGeometries(); m_123TimeGeometryWithCollapsedInterim->AppendNewTimeStep(m_Geometry1, m_Geometry1MinTP, m_Geometry1MaxTP); m_123TimeGeometryWithCollapsedInterim->AppendNewTimeStep(m_Geometry2, m_Geometry2MinTP, m_Geometry2MinTP); m_123TimeGeometryWithCollapsedInterim->AppendNewTimeStep(m_Geometry3, m_Geometry3MinTP, m_Geometry3MaxTP); } void tearDown() override {} void CountTimeSteps() { MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->CountTimeSteps() == 0, "Testing CountTimeSteps with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->CountTimeSteps() == 1, "Testing CountTimeSteps with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->CountTimeSteps() == 5, "Testing CountTimeSteps with m_12345TimeGeometry"); } void GetMinimumTimePoint() { MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetMinimumTimePoint() == 0.0, "Testing GetMinimumTimePoint with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->GetMinimumTimePoint() == 0.0, "Testing GetMinimumTimePoint with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetMinimumTimePoint() == 1.0, "Testing GetMinimumTimePoint with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetMinimumTimePoint(2) == 0.0, "Testing GetMinimumTimePoint(2) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->GetMinimumTimePoint(2) == 0.0, "Testing GetMinimumTimePoint(2) with m_initTimeGeometry"); - MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetMinimumTimePoint(2) == 2.9, - "Testing GetMinimumTimePoint(2) with m_12345TimeGeometry"); + /////////////////////////////////////// + // Workarround T27883. See https://phabricator.mitk.org/T27883#219473 for more details. + // This workarround should be removed/reevaluated as soon as T28262 is solved and we know + // how time geometries should behave in the future! + MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetMinimumTimePoint(2) == 3.0, + "Testing GetMinimumTimePoint(2) with m_12345TimeGeometry"); + // Deactivated falling original test + // MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetMinimumTimePoint(2) == 2.9, + // "Testing GetMinimumTimePoint(2) with m_12345TimeGeometry"); + // End of workarround for T27883 + ////////////////////////////////////// } void GetMaximumTimePoint() { MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetMaximumTimePoint() == 0.0, "Testing GetMaximumTimePoint with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->GetMaximumTimePoint() == 1.0, "Testing GetMaximumTimePoint with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetMaximumTimePoint() == 5.9, "Testing GetMaximumTimePoint with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetMaximumTimePoint(2) == 0.0, "Testing GetMaximumTimePoint(2) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->GetMaximumTimePoint(2) == 0.0, "Testing GetMaximumTimePoint(2) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetMaximumTimePoint(2) == 3.9, "Testing GetMaximumTimePoint(2) with m_12345TimeGeometry"); } void GetTimeBounds() { MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetMaximumTimePoint(2) == 0.0, "Testing GetMaximumTimePoint(2) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->GetMaximumTimePoint(2) == 0.0, "Testing GetMaximumTimePoint(2) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetMaximumTimePoint(2) == 3.9, "Testing GetMaximumTimePoint(2) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetTimeBounds()[0] == 0.0, "Testing GetTimeBounds lower part with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->GetTimeBounds()[0] == 0.0, "Testing GetTimeBounds lower part with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetTimeBounds()[0] == 1.0, "Testing GetTimeBounds lower part with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetTimeBounds()[1] == 0.0, "Testing GetTimeBounds with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->GetTimeBounds()[1] == 1.0, "Testing GetTimeBounds with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetTimeBounds()[1] == 5.9, "Testing GetTimeBounds with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetTimeBounds(3)[0] == 0.0, "Testing GetTimeBounds(3) lower part with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->GetTimeBounds(3)[0] == 0.0, "Testing GetTimeBounds(3) lower part with m_initTimeGeometry"); - MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetTimeBounds(3)[0] == 3.9, - "Testing GetTimeBounds(3) lower part with m_12345TimeGeometry"); + /////////////////////////////////////// + // Workarround T27883. See https://phabricator.mitk.org/T27883#219473 for more details. + // This workarround should be removed/reevaluated as soon as T28262 is solved and we know + // how time geometries should behave in the future! + MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetTimeBounds(3)[0] == 4.0, + "Testing GetTimeBounds(3) lower part with m_12345TimeGeometry"); + // Deactivated falling original test + // MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetTimeBounds(3)[0] == 3.9, + // "Testing GetTimeBounds(3) lower part with m_12345TimeGeometry"); + // End of workarround for T27883 + ////////////////////////////////////// + MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetTimeBounds(3)[1] == 0.0, "Testing GetTimeBounds(3) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->GetTimeBounds(3)[1] == 0.0, "Testing GetTimeBounds(3) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetTimeBounds(3)[1] == 4.9, "Testing GetTimeBounds(3) with m_12345TimeGeometry"); } void IsValidTimePoint() { MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->IsValidTimePoint(-1) == false, "Testing IsValidTimePoint(-1) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->IsValidTimePoint(-1) == false, "Testing IsValidTimePoint(-1) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->IsValidTimePoint(-1) == false, "Testing IsValidTimePoint(-1) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->IsValidTimePoint(0) == false, "Testing IsValidTimePoint(0) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->IsValidTimePoint(0) == true, "Testing IsValidTimePoint(0) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->IsValidTimePoint(0) == false, "Testing IsValidTimePoint(0) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->IsValidTimePoint(1) == false, "Testing IsValidTimePoint(1) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->IsValidTimePoint(1) == false, "Testing IsValidTimePoint(1) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->IsValidTimePoint(1) == true, "Testing IsValidTimePoint(1) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->IsValidTimePoint(2.5) == false, "Testing IsValidTimePoint(2.5) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->IsValidTimePoint(2.5) == false, "Testing IsValidTimePoint(2.5) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->IsValidTimePoint(2.5) == true, "Testing IsValidTimePoint(2.5) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->IsValidTimePoint(5.89) == false, "Testing IsValidTimePoint(5.89) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->IsValidTimePoint(5.89) == false, "Testing IsValidTimePoint(5.89) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->IsValidTimePoint(5.89) == true, "Testing IsValidTimePoint(5.89) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->IsValidTimePoint(10) == false, "Testing IsValidTimePoint(10) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->IsValidTimePoint(10) == false, "Testing IsValidTimePoint(10) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->IsValidTimePoint(10) == false, "Testing IsValidTimePoint(10) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->IsValidTimeStep(0) == false, "Testing IsValidTimeStep(0) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->IsValidTimeStep(0) == true, "Testing IsValidTimeStep(0) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->IsValidTimeStep(0) == true, "Testing IsValidTimeStep(0) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->IsValidTimeStep(1) == false, "Testing IsValidTimeStep(1) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->IsValidTimeStep(1) == false, "Testing IsValidTimeStep(1) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->IsValidTimeStep(1) == true, "Testing IsValidTimeStep(1) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->IsValidTimeStep(6) == false, "Testing IsValidTimeStep(6) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->IsValidTimeStep(6) == false, "Testing IsValidTimeStep(6) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->IsValidTimeStep(6) == false, "Testing IsValidTimeStep(6) with m_12345TimeGeometry"); //checked collapsed cases MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometryWithCollapsedInterim->IsValidTimePoint(m_123TimeGeometryWithCollapsedInterim->GetMaximumTimePoint()) == false, "Testing that m_123TimeGeometryWithCollapsedInterim does not inclued the max bound in validity"); MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometryWithCollapsedEnd->IsValidTimePoint(m_123TimeGeometryWithCollapsedEnd->GetMaximumTimePoint()) == true, "Testing that m_123TimeGeometryWithCollapsedEnd does inclued the max bound in validity, because it has an collapsed final time step. (see also T27259)"); } void TimeStepToTimePoint() { MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->TimeStepToTimePoint(0) == 0.0, "Testing TimeStepToTimePoint(0) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->TimeStepToTimePoint(0) == 0.0, "Testing TimeStepToTimePoint(0) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->TimeStepToTimePoint(0) == 1.0, "Testing TimeStepToTimePoint(0) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->TimeStepToTimePoint(1) == 0.0, "Testing TimeStepToTimePoint(1) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->TimeStepToTimePoint(1) == 0.0, "Testing TimeStepToTimePoint(1) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->TimeStepToTimePoint(1) == 2.0, "Testing TimeStepToTimePoint(1) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->TimeStepToTimePoint(6) == 0.0, "Testing TimeStepToTimePoint(6) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->TimeStepToTimePoint(6) == 0.0, "Testing TimeStepToTimePoint(6) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->TimeStepToTimePoint(6) == 0.0, "Testing TimeStepToTimePoint(6) with m_12345TimeGeometry"); } void TimePointToTimeStep() { MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->TimePointToTimeStep(0.0) == 0, "Testing TimePointToTimeStep(0.0) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->TimePointToTimeStep(0.0) == 0, "Testing TimePointToTimeStep(0.0) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->TimePointToTimeStep(0.0) == 0, "Testing TimePointToTimeStep(0.0) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->TimePointToTimeStep(0.5) == 0, "Testing TimePointToTimeStep(0.5) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->TimePointToTimeStep(0.5) == 0, "Testing TimePointToTimeStep(0.5) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->TimePointToTimeStep(0.5) == 0, "Testing TimePointToTimeStep(0.5) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->TimePointToTimeStep(3.5) == 0, "Testing TimePointToTimeStep(3.5) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->TimePointToTimeStep(3.5) == m_initTimeGeometry->CountTimeSteps(), "Testing TimePointToTimeStep(3.5) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->TimePointToTimeStep(3.5) == 2, "Testing TimePointToTimeStep(3.5) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->TimePointToTimeStep(5.8) == 0, "Testing TimePointToTimeStep(5.8) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->TimePointToTimeStep(5.8) == m_initTimeGeometry->CountTimeSteps(), "Testing TimePointToTimeStep(5.8) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->TimePointToTimeStep(5.8) == 4, "Testing TimePointToTimeStep(5.8) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->TimePointToTimeStep(5.9) == m_12345TimeGeometry->CountTimeSteps(), "Testing TimePointToTimeStep(5.9) with m_12345TimeGeometry"); //checked collapsed cases MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometryWithCollapsedInterim->TimePointToTimeStep(m_123TimeGeometryWithCollapsedInterim->GetMaximumTimePoint()) == m_123TimeGeometryWithCollapsedInterim->CountTimeSteps(), "Testing m_123TimeGeometryWithCollapsedInterim does not map the max time poit."); MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometryWithCollapsedEnd->TimePointToTimeStep(m_123TimeGeometryWithCollapsedEnd->GetMaximumTimePoint()) == 2, "Testing that m_123TimeGeometryWithCollapsedEnd does map the max bound, because it has an collapsed final time step. (see also T27259)"); } void GetGeometryCloneForTimeStep() { MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetGeometryCloneForTimeStep(0).IsNull(), "Testing GetGeometryCloneForTimeStep(0) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->GetGeometryCloneForTimeStep(0).IsNotNull(), "Testing GetGeometryCloneForTimeStep(0) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetGeometryCloneForTimeStep(0).IsNotNull(), "Testing GetGeometryCloneForTimeStep(0) with m_12345TimeGeometry"); } void GetGeometryForTimeStep() { MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetGeometryForTimeStep(0).IsNull(), "Testing GetGeometryForTimePoint(0) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->GetGeometryForTimeStep(0).IsNotNull(), "Testing GetGeometryForTimePoint(0) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->GetGeometryForTimeStep(1).IsNull(), "Testing GetGeometryForTimePoint(1) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED( m_12345TimeGeometry->GetGeometryForTimeStep(0).GetPointer() == m_Geometry1.GetPointer(), "Testing GetGeometryForTimePoint(0) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED( m_12345TimeGeometry->GetGeometryForTimeStep(3).GetPointer() == m_Geometry4.GetPointer(), "Testing GetGeometryForTimePoint(3) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED( m_12345TimeGeometry->GetGeometryForTimeStep(4).GetPointer() == m_Geometry5.GetPointer(), "Testing GetGeometryForTimePoint(4) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetGeometryForTimeStep(5).IsNull(), "Testing GetGeometryForTimePoint(5) with m_12345TimeGeometry"); } void GetGeometryForTimePoint() { MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetGeometryForTimePoint(0).IsNull(), "Testing GetGeometryForTimeStep(0) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->GetGeometryForTimePoint(0).IsNotNull(), "Testing GetGeometryForTimeStep(0) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetGeometryForTimePoint(0).IsNull(), "Testing GetGeometryForTimeStep(0) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetGeometryForTimePoint(1.5).IsNull(), "Testing GetGeometryForTimeStep(1.5) with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->GetGeometryForTimePoint(1.5).IsNull(), "Testing GetGeometryForTimeStep(1.5) with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED( m_12345TimeGeometry->GetGeometryForTimePoint(1.5).GetPointer() == m_Geometry1.GetPointer(), "Testing GetGeometryForTimeStep(1.5) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED( m_12345TimeGeometry->GetGeometryForTimePoint(3.5).GetPointer() == m_Geometry3.GetPointer(), "Testing GetGeometryForTimeStep(3.5) with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetGeometryForTimePoint(5.9).IsNull(), "Testing GetGeometryForTimeStep(5.9) with m_12345TimeGeometry"); } void IsValid() { MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->IsValid() == false, "Testing IsValid() with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->IsValid() == true, "Testing IsValid() with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->IsValid() == true, "Testing IsValid() with m_12345TimeGeometry"); } void Expand() { m_12345TimeGeometry->Expand(3); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->CountTimeSteps() == 5, "Testing Expand(3) doesn't change m_12345TimeGeometry"); m_12345TimeGeometry->Expand(7); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->CountTimeSteps() == 7, "Testing Expand(7) with m_12345TimeGeometry"); } void ReplaceTimeStepGeometries() { // Test replace time step geometries m_12345TimeGeometry->ReplaceTimeStepGeometries(m_NewGeometry); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->CountTimeSteps() == 5, "Testing ReplaceTimeStepGeometries() with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED( m_12345TimeGeometry->GetGeometryForTimeStep(0)->GetOrigin() == m_NewGeometry->GetOrigin(), "Testing ReplaceTimeStepGeometries(): check if first geometry of m_12345TimeGeometry " "was replaced m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED( m_12345TimeGeometry->GetGeometryForTimeStep(4)->GetOrigin() == m_NewGeometry->GetOrigin(), "Testing ReplaceTimeStepGeometries(): check if last geometry of m_12345TimeGeometry " "was replaced m_12345TimeGeometry"); } void ClearAllGeometries() { // Test clear all geometries m_12345TimeGeometry->ClearAllGeometries(); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->CountTimeSteps() == 0, "Testing ClearAllGeometries() with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetMinimumTimePoint() == 0, "Testing ClearAllGeometries() with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->GetMaximumTimePoint() == 0, "Testing ClearAllGeometries() with m_12345TimeGeometry"); } void AppendNewTimeStep() { // Test append MITK_TEST_FOR_EXCEPTION(mitk::Exception, m_123TimeGeometry->AppendNewTimeStep(nullptr, 0, 1)); MITK_TEST_FOR_EXCEPTION(mitk::Exception, m_123TimeGeometry->AppendNewTimeStep(m_Geometry3_5,m_Geometry3_5MinTP,m_Geometry3_5MaxTP)); MITK_TEST_FOR_EXCEPTION(mitk::Exception, m_123TimeGeometry->AppendNewTimeStep(m_Geometry4, m_Geometry4MaxTP, m_Geometry4MinTP)); //valid but inverted bounds m_emptyTimeGeometry->AppendNewTimeStep(m_Geometry4, m_Geometry4MinTP, m_Geometry4MaxTP); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->CountTimeSteps() == 1, "Testing AppendNewTimeStep() with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetMinimumTimePoint() == 4, "Testing ClearAllGeometries() with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->GetMaximumTimePoint() == 4.9, "Testing ClearAllGeometries() with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometry->CountTimeSteps() == 3, "Testing AppendNewTimeStep() with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometry->GetMinimumTimePoint() == 1, "Testing ClearAllGeometries() with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometry->GetMaximumTimePoint() == 3.9, "Testing ClearAllGeometries() with m_emptyTimeGeometry"); m_123TimeGeometry->AppendNewTimeStep(m_Geometry4, m_Geometry4MinTP, m_Geometry4MaxTP); MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometry->CountTimeSteps() == 4, "Testing AppendNewTimeStep() with m_123TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometry->GetMinimumTimePoint() == 1, "Testing AppendNewTimeStep() with m_123TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometry->GetMaximumTimePoint() == 4.9, "Testing AppendNewTimeStep() with m_123TimeGeometry"); - MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometry->GetMinimumTimePoint(3) == 3.9, - "Testing AppendNewTimeStep() with m_123TimeGeometry"); + /////////////////////////////////////// + // Workarround T27883. See https://phabricator.mitk.org/T27883#219473 for more details. + // This workarround should be removed/reevaluated as soon as T28262 is solved and we know + // how time geometries should behave in the future! + MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometry->GetMinimumTimePoint(3) == 4.0, + "Testing AppendNewTimeStep() with m_123TimeGeometry"); + // Deactivated falling original test + // MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometry->GetMinimumTimePoint(3) == 3.9, + // "Testing AppendNewTimeStep() with m_123TimeGeometry"); + // End of workarround for T27883 + ////////////////////////////////////// } void HasCollapsedFinalTimeStep() { MITK_TEST_CONDITION_REQUIRED(m_emptyTimeGeometry->HasCollapsedFinalTimeStep() == false, "Testing HasCollapsedFinalTimeStep() with m_emptyTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_initTimeGeometry->HasCollapsedFinalTimeStep() == false, "Testing HasCollapsedFinalTimeStep() with m_initTimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_12345TimeGeometry->HasCollapsedFinalTimeStep() == false, "Testing HasCollapsedFinalTimeStep() with m_12345TimeGeometry"); MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometryWithCollapsedEnd->HasCollapsedFinalTimeStep() == true, "Testing HasCollapsedFinalTimeStep() with m_123TimeGeometryWithCollapsedEnd"); MITK_TEST_CONDITION_REQUIRED(m_123TimeGeometryWithCollapsedInterim->HasCollapsedFinalTimeStep() == false, "Testing HasCollapsedFinalTimeStep() with m_123TimeGeometryWithCollapsedInterim"); } }; MITK_TEST_SUITE_REGISTRATION(mitkArbitraryTimeGeometry) diff --git a/Modules/DICOMTesting/src/mitkTestDICOMLoading.cpp b/Modules/DICOMTesting/src/mitkTestDICOMLoading.cpp index ed2c8e71b0..9d63c65aa9 100644 --- a/Modules/DICOMTesting/src/mitkTestDICOMLoading.cpp +++ b/Modules/DICOMTesting/src/mitkTestDICOMLoading.cpp @@ -1,572 +1,585 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ //#define MBILOG_ENABLE_DEBUG #include "mitkTestDICOMLoading.h" #include "mitkDICOMIOMetaInformationPropertyConstants.h" #include "mitkDICOMProperty.h" +#include "mitkArbitraryTimeGeometry.h" #include #include #include #include "itksys/SystemTools.hxx" mitk::TestDICOMLoading::TestDICOMLoading() :m_PreviousCLocale(nullptr) { } void mitk::TestDICOMLoading::SetDefaultLocale() { // remember old locale only once if (m_PreviousCLocale == nullptr) { m_PreviousCLocale = setlocale(LC_NUMERIC, nullptr); // set to "C" setlocale(LC_NUMERIC, "C"); m_PreviousCppLocale = std::cin.getloc(); std::locale l( "C" ); std::cin.imbue(l); std::cout.imbue(l); } } void mitk::TestDICOMLoading::ResetUserLocale() { if (m_PreviousCLocale) { setlocale(LC_NUMERIC, m_PreviousCLocale); std::cin.imbue(m_PreviousCppLocale); std::cout.imbue(m_PreviousCppLocale); m_PreviousCLocale = nullptr; } } mitk::TestDICOMLoading::ImageList mitk::TestDICOMLoading ::LoadFiles( const StringList& files ) { for (auto iter = files.begin(); iter != files.end(); ++iter) { MITK_DEBUG << "File " << *iter; } ImageList result; ClassicDICOMSeriesReader::Pointer reader = this->BuildDICOMReader(); reader->SetTagLookupTableToPropertyFunctor(mitk::GetDICOMPropertyForDICOMValuesFunctor); reader->SetInputFiles( files ); reader->AnalyzeInputFiles(); reader->PrintOutputs(std::cout,true); reader->LoadImages(); unsigned int numberOfImages = reader->GetNumberOfOutputs(); for (unsigned imageIndex = 0; imageIndex < numberOfImages; ++imageIndex) { const DICOMImageBlockDescriptor& block = reader->GetOutput(imageIndex); result.push_back( block.GetMitkImage() ); } return result; } mitk::ClassicDICOMSeriesReader::Pointer mitk::TestDICOMLoading ::BuildDICOMReader() { ClassicDICOMSeriesReader::Pointer reader = ClassicDICOMSeriesReader::New(); reader->SetFixTiltByShearing(true); return reader; } mitk::Image::Pointer mitk::TestDICOMLoading ::DecorateVerifyCachedImage( const StringList& files, mitk::DICOMTagCache* tagCache, mitk::Image::Pointer cachedImage ) { DICOMImageBlockDescriptor block; DICOMImageFrameList framelist; for (auto iter = files.begin(); iter != files.end(); ++iter) { framelist.push_back( DICOMImageFrameInfo::New(*iter) ); } block.SetImageFrameList( framelist ); block.SetTagCache( tagCache ); block.SetMitkImage( cachedImage ); // this should/will create a propertylist describing the image slices return block.GetMitkImage(); } mitk::Image::Pointer mitk::TestDICOMLoading ::DecorateVerifyCachedImage( const StringList& files, mitk::Image::Pointer cachedImage ) { ClassicDICOMSeriesReader::Pointer reader = this->BuildDICOMReader(); reader->SetTagLookupTableToPropertyFunctor(mitk::GetDICOMPropertyForDICOMValuesFunctor); reader->SetInputFiles( files ); reader->AnalyzeInputFiles(); // This just creates a "tag cache and a nice DICOMImageBlockDescriptor. // Both of these could also be produced in a different way. The only // important thing is, that the DICOMImageBlockDescriptor knows a // tag-cache object when PropertyDecorateCachedMitkImageForImageBlockDescriptor // is called. if ( reader->GetNumberOfOutputs() != 1 ) { MITK_ERROR << "Reader produce " << reader->GetNumberOfOutputs() << " images instead of 1 expected.."; return nullptr; } DICOMImageBlockDescriptor block = reader->GetOutput(0); // creates a block copy block.SetMitkImage( cachedImage ); // this should/will create a propertylist describing the image slices return block.GetMitkImage(); } std::string mitk::TestDICOMLoading::ComponentTypeToString(int type) { if (type == itk::ImageIOBase::UCHAR) return "UCHAR"; else if (type == itk::ImageIOBase::CHAR) return "CHAR"; else if (type == itk::ImageIOBase::USHORT) return "USHORT"; else if (type == itk::ImageIOBase::SHORT) return "SHORT"; else if (type == itk::ImageIOBase::UINT) return "UINT"; else if (type == itk::ImageIOBase::INT) return "INT"; else if (type == itk::ImageIOBase::ULONG) return "ULONG"; else if (type == itk::ImageIOBase::LONG) return "LONG"; else if (type == itk::ImageIOBase::FLOAT) return "FLOAT"; else if (type == itk::ImageIOBase::DOUBLE) return "DOUBLE"; else return "UNKNOWN"; } // add a line to stringstream result (see DumpImageInformation #define DumpLine(field, data) DumpILine(0, field, data) // add an indented(!) line to stringstream result (see DumpImageInformation #define DumpILine(indent, field, data) \ { \ std::string DumpLine_INDENT; DumpLine_INDENT.resize(indent, ' ' ); \ result << DumpLine_INDENT << field << ": " << data << "\n"; \ } std::string mitk::TestDICOMLoading::DumpImageInformation( const Image* image ) { std::stringstream result; if (image == nullptr) return result.str(); SetDefaultLocale(); // basic image data DumpLine( "Pixeltype", ComponentTypeToString(image->GetPixelType().GetComponentType()) ); DumpLine( "BitsPerPixel", image->GetPixelType().GetBpe() ); DumpLine( "Dimension", image->GetDimension() ); result << "Dimensions: "; for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) result << image->GetDimension(dim) << " "; result << "\n"; // geometry data result << "Geometry: \n"; const TimeGeometry* timeGeometry = image->GetTimeGeometry(); BaseGeometry* geometry = timeGeometry->GetGeometryForTimeStep(0); if (geometry) { AffineTransform3D* transform = geometry->GetIndexToWorldTransform(); if (transform) { result << " " << "Matrix: "; const AffineTransform3D::MatrixType& matrix = transform->GetMatrix(); for (unsigned int i = 0; i < 3; ++i) for (unsigned int j = 0; j < 3; ++j) result << matrix[i][j] << " "; result << "\n"; result << " " << "Offset: "; const AffineTransform3D::OutputVectorType& offset = transform->GetOffset(); for (unsigned int i = 0; i < 3; ++i) result << offset[i] << " "; result << "\n"; result << " " << "Center: "; const AffineTransform3D::InputPointType& center = transform->GetCenter(); for (unsigned int i = 0; i < 3; ++i) result << center[i] << " "; result << "\n"; result << " " << "Translation: "; const AffineTransform3D::OutputVectorType& translation = transform->GetTranslation(); for (unsigned int i = 0; i < 3; ++i) result << translation[i] << " "; result << "\n"; result << " " << "Scale: "; const double* scale = transform->GetScale(); for (unsigned int i = 0; i < 3; ++i) result << scale[i] << " "; result << "\n"; result << " " << "Origin: "; const Point3D& origin = geometry->GetOrigin(); for (unsigned int i = 0; i < 3; ++i) result << origin[i] << " "; result << "\n"; result << " " << "Spacing: "; const Vector3D& spacing = geometry->GetSpacing(); for (unsigned int i = 0; i < 3; ++i) result << spacing[i] << " "; result << "\n"; result << " " << "TimeBounds: "; - const TimeBounds timeBounds = timeGeometry->GetTimeBounds(); + /////////////////////////////////////// + // Workarround T27883. See https://phabricator.mitk.org/T27883#219473 for more details. + // This workarround should be removed as soon as T28262 is solved! + TimeBounds timeBounds = timeGeometry->GetTimeBounds(); + auto atg = dynamic_cast(timeGeometry); + if (atg && atg->HasCollapsedFinalTimeStep()) + { + timeBounds[1] = timeBounds[1] - 1.; + } + //Original code: + //const TimeBounds timeBounds = timeGeometry->GetTimeBounds(); + // + // End of workarround for T27883 + ////////////////////////////////////// for (unsigned int i = 0; i < 2; ++i) - result << timeBounds[i] << " "; + result << timeBounds[i] << " "; result << "\n"; - } } // io dicom meta information AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_CONFIGURATION(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_FILES(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_GANTRY_TILT_CORRECTED(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_IMPLEMENTATION_LEVEL(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_IMPLEMENTATION_LEVEL_STRING(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_PIXEL_SPACING_INTERPRETATION(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_PIXEL_SPACING_INTERPRETATION_STRING(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_3D_plus_t(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_DCMTK(), image, result); AddPropertyToDump(mitk::DICOMIOMetaInformationPropertyConstants::READER_GDCM(), image, result); ResetUserLocale(); return result.str(); } void mitk::TestDICOMLoading::AddPropertyToDump(const mitk::PropertyKeyPath& key, const mitk::Image* image, std::stringstream& result) { auto propKey = mitk::PropertyKeyPathToPropertyName(key); auto prop = image->GetProperty(propKey.c_str()); if (prop.IsNotNull()) { auto value = prop->GetValueAsString(); auto dicomProp = dynamic_cast< mitk::DICOMProperty*>(prop.GetPointer()); if (dicomProp != nullptr) { auto strippedProp = dicomProp->Clone(); if (key == mitk::DICOMIOMetaInformationPropertyConstants::READER_FILES()) {//strip dicom file information from path to ensure generalized dump files auto timePoints = strippedProp->GetAvailableTimeSteps(); for (auto timePoint : timePoints) { auto slices = strippedProp->GetAvailableSlices(timePoint); for (auto slice : slices) { auto value = strippedProp->GetValue(timePoint, slice); value = itksys::SystemTools::GetFilenameName(value); strippedProp->SetValue(timePoint, slice, value); } } } value = mitk::PropertyPersistenceSerialization::serializeTemporoSpatialStringPropertyToJSON(strippedProp); } result << propKey << ": " << value << "\n"; } } std::string mitk::TestDICOMLoading::trim(const std::string& pString, const std::string& pWhitespace) { const size_t beginStr = pString.find_first_not_of(pWhitespace); if (beginStr == std::string::npos) { // no content return ""; } const size_t endStr = pString.find_last_not_of(pWhitespace); const size_t range = endStr - beginStr + 1; return pString.substr(beginStr, range); } std::string mitk::TestDICOMLoading::reduce(const std::string& pString, const std::string& pFill, const std::string& pWhitespace) { // trim first std::string result(trim(pString, pWhitespace)); // replace sub ranges size_t beginSpace = result.find_first_of(pWhitespace); while (beginSpace != std::string::npos) { const size_t endSpace = result.find_first_not_of(pWhitespace, beginSpace); const size_t range = endSpace - beginSpace; result.replace(beginSpace, range, pFill); const size_t newStart = beginSpace + pFill.length(); beginSpace = result.find_first_of(pWhitespace, newStart); } return result; } bool mitk::TestDICOMLoading::CompareSpacedValueFields( const std::string& reference, const std::string& test, double /*eps*/ ) { bool result(true); // tokenize string, compare each token, if possible by float comparison std::stringstream referenceStream(reduce(reference)); std::stringstream testStream(reduce(test)); std::string refToken; std::string testToken; while ( std::getline( referenceStream, refToken, ' ' ) && std::getline ( testStream, testToken, ' ' ) ) { float refNumber; float testNumber; if ( this->StringToNumber(refToken, refNumber) ) { if ( this->StringToNumber(testToken, testNumber) ) { // print-out compared tokens if DEBUG output allowed MITK_DEBUG << "Reference Token '" << refToken << "'" << " value " << refNumber << ", test Token '" << testToken << "'" << " value " << testNumber; bool old_result = result; result &= ( std::abs(refNumber - testNumber) < 0.0001f /*mitk::eps*/ ); // log the token/number which causes the test to fail if( old_result != result) { MITK_ERROR << std::setprecision(16) << "Reference Token '" << refToken << "'" << " value " << refNumber << ", test Token '" << testToken << "'" << " value " << testNumber; MITK_ERROR << "[FALSE] - difference: " << std::setprecision(16) << std::abs(refNumber - testNumber) << " EPS: " << 0.0001f; //mitk::eps; } } else { MITK_ERROR << refNumber << " cannot be compared to '" << testToken << "'"; } } else { MITK_DEBUG << "Token '" << refToken << "'" << " handled as string"; result &= refToken == testToken; } } if ( std::getline( referenceStream, refToken, ' ' ) ) { MITK_ERROR << "Reference string still had values when test string was already parsed: ref '" << reference << "', test '" << test << "'"; result = false; } else if ( std::getline( testStream, testToken, ' ' ) ) { MITK_ERROR << "Test string still had values when reference string was already parsed: ref '" << reference << "', test '" << test << "'"; result = false; } return result; } bool mitk::TestDICOMLoading::CompareImageInformationDumps( const std::string& referenceDump, const std::string& testDump ) { KeyValueMap reference = ParseDump(referenceDump); KeyValueMap test = ParseDump(testDump); bool testResult(true); // verify all expected values for (KeyValueMap::const_iterator refIter = reference.begin(); refIter != reference.end(); ++refIter) { const std::string& refKey = refIter->first; const std::string& refValue = refIter->second; if ( test.find(refKey) != test.end() ) { const std::string& testValue = test[refKey]; if (refKey == mitk::PropertyKeyPathToPropertyName(mitk::DICOMIOMetaInformationPropertyConstants::READER_DCMTK())) { //check dcmtk version always against the current version of the system bool thisTestResult = testValue == std::string(" ") + PACKAGE_VERSION; testResult &= thisTestResult; MITK_DEBUG << refKey << ": '" << PACKAGE_VERSION << "' == '" << testValue << "' ? " << (thisTestResult ? "YES" : "NO"); } else if (refKey == mitk::PropertyKeyPathToPropertyName(mitk::DICOMIOMetaInformationPropertyConstants::READER_GDCM())) {//check gdcm version always against the current version of the system bool thisTestResult = testValue == std::string(" ") + gdcm::Version::GetVersion(); testResult &= thisTestResult; MITK_DEBUG << refKey << ": '" << gdcm::Version::GetVersion() << "' == '" << testValue << "' ? " << (thisTestResult ? "YES" : "NO"); } else { bool thisTestResult = CompareSpacedValueFields(refValue, testValue); testResult &= thisTestResult; MITK_DEBUG << refKey << ": '" << refValue << "' == '" << testValue << "' ? " << (thisTestResult ? "YES" : "NO"); } } else { MITK_ERROR << "Reference dump contains a key'" << refKey << "' (value '" << refValue << "')." ; MITK_ERROR << "This key is expected to be generated for tests (but was not). Most probably you need to update your test data."; return false; } } // now check test dump does not contain any additional keys for (KeyValueMap::const_iterator testIter = test.begin(); testIter != test.end(); ++testIter) { const std::string& key = testIter->first; const std::string& value = testIter->second; if (key == mitk::PropertyKeyPathToPropertyName(mitk::DICOMIOMetaInformationPropertyConstants::READER_DCMTK())) {//check dcmtk version always against the current version of the system bool thisTestResult = value == std::string(" ")+PACKAGE_VERSION; testResult &= thisTestResult; MITK_DEBUG << key << ": '" << PACKAGE_VERSION << "' == '" << value << "' ? " << (thisTestResult ? "YES" : "NO"); } else if (key == mitk::PropertyKeyPathToPropertyName(mitk::DICOMIOMetaInformationPropertyConstants::READER_GDCM())) {//check gdcm version always against the current version of the system bool thisTestResult = value == std::string(" ") + gdcm::Version::GetVersion(); testResult &= thisTestResult; MITK_DEBUG << key << ": '" << gdcm::Version::GetVersion() << "' == '" << value << "' ? " << (thisTestResult ? "YES" : "NO"); } else if ( reference.find(key) == reference.end() ) { MITK_ERROR << "Test dump contains an unexpected key'" << key << "' (value '" << value << "')." ; MITK_ERROR << "This key is not expected. Most probably you need to update your test data."; return false; } } return testResult; } mitk::TestDICOMLoading::KeyValueMap mitk::TestDICOMLoading::ParseDump( const std::string& dump ) { KeyValueMap parsedResult; std::string shredder(dump); std::stack surroundingKeys; std::stack expectedIndents; expectedIndents.push(0); while (true) { std::string::size_type newLinePos = shredder.find( '\n' ); if (newLinePos == std::string::npos || newLinePos == 0) break; std::string line = shredder.substr( 0, newLinePos ); shredder = shredder.erase( 0, newLinePos+1 ); std::string::size_type keyPosition = line.find_first_not_of( ' ' ); std::string::size_type colonPosition = line.find( ':' ); std::string key = line.substr(keyPosition, colonPosition - keyPosition); std::string::size_type firstSpacePosition = key.find_first_of(" "); if (firstSpacePosition != std::string::npos) { key.erase(firstSpacePosition); } if ( keyPosition > expectedIndents.top() ) { // more indent than before expectedIndents.push(keyPosition); } else { if (!surroundingKeys.empty()) { surroundingKeys.pop(); // last of same length } while (expectedIndents.top() != keyPosition) { expectedIndents.pop(); if (!surroundingKeys.empty()) { surroundingKeys.pop(); } }; // unwind until current indent is found } if (!surroundingKeys.empty()) { key = surroundingKeys.top() + "." + key; // construct current key name } surroundingKeys.push(key); // this is the new embracing key std::string value = line.substr(colonPosition+1); MITK_DEBUG << " Key: '" << key << "' value '" << value << "'" ; parsedResult[key] = value; // store parsing result } return parsedResult; }