diff --git a/Modules/Core/src/DataManagement/mitkSurface.cpp b/Modules/Core/src/DataManagement/mitkSurface.cpp index fcc8f6ff98..9fc0cdf7f8 100644 --- a/Modules/Core/src/DataManagement/mitkSurface.cpp +++ b/Modules/Core/src/DataManagement/mitkSurface.cpp @@ -1,509 +1,508 @@ /*============================================================================ 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 "mitkSurface.h" #include "mitkInteractionConst.h" #include "mitkSurfaceOperation.h" #include #include static vtkSmartPointer DeepCopy(vtkPolyData *other) { if (other == nullptr) return nullptr; vtkSmartPointer copy = vtkSmartPointer::New(); copy->DeepCopy(other); return copy; } static void Update(vtkPolyData * /*polyData*/) { // if (polyData != nullptr) // polyData->Update(); //VTK6_TODO vtk pipeline } mitk::Surface::Surface() : m_CalculateBoundingBox(false) { this->InitializeEmpty(); } mitk::Surface::Surface(const mitk::Surface &other) : BaseData(other), m_LargestPossibleRegion(other.m_LargestPossibleRegion), m_RequestedRegion(other.m_RequestedRegion), m_CalculateBoundingBox(other.m_CalculateBoundingBox) { if (!other.m_PolyDatas.empty()) { m_PolyDatas.resize(other.m_PolyDatas.size()); std::transform(other.m_PolyDatas.cbegin(), other.m_PolyDatas.cend(), m_PolyDatas.begin(), DeepCopy); } else { this->InitializeEmpty(); } } void mitk::Surface::Swap(mitk::Surface &other) { std::swap(m_PolyDatas, other.m_PolyDatas); std::swap(m_LargestPossibleRegion, other.m_LargestPossibleRegion); std::swap(m_RequestedRegion, other.m_RequestedRegion); std::swap(m_CalculateBoundingBox, other.m_CalculateBoundingBox); } mitk::Surface &mitk::Surface::operator=(Surface other) { this->Swap(other); return *this; } mitk::Surface::~Surface() { this->ClearData(); } void mitk::Surface::ClearData() { m_PolyDatas.clear(); Superclass::ClearData(); } const mitk::Surface::RegionType &mitk::Surface::GetLargestPossibleRegion() const { m_LargestPossibleRegion.SetIndex(3, 0); m_LargestPossibleRegion.SetSize(3, GetTimeGeometry()->CountTimeSteps()); return m_LargestPossibleRegion; } const mitk::Surface::RegionType &mitk::Surface::GetRequestedRegion() const { return m_RequestedRegion; } void mitk::Surface::InitializeEmpty() { if (!m_PolyDatas.empty()) this->ClearData(); Superclass::InitializeTimeGeometry(); m_PolyDatas.push_back(nullptr); m_Initialized = true; } void mitk::Surface::SetVtkPolyData(vtkPolyData *polyData, unsigned int t) { this->Expand(t + 1); if (m_PolyDatas[t] != nullptr) { if (m_PolyDatas[t].GetPointer() == polyData) return; } m_PolyDatas[t].TakeReference(polyData); if (polyData != nullptr) polyData->Register(nullptr); m_CalculateBoundingBox = true; this->Modified(); this->UpdateOutputInformation(); } bool mitk::Surface::IsEmptyTimeStep(unsigned int t) const { if (!IsInitialized()) return false; vtkPolyData *polyData = this->GetVtkPolyData(t); - return polyData == nullptr || (polyData->GetNumberOfLines() == 0 && polyData->GetNumberOfPolys() == 0 && - polyData->GetNumberOfStrips() == 0 && polyData->GetNumberOfVerts() == 0); + return polyData == nullptr; } vtkPolyData *mitk::Surface::GetVtkPolyData(unsigned int t) const { if (t < m_PolyDatas.size()) { if (m_PolyDatas[t] == nullptr && this->GetSource().IsNotNull()) { RegionType requestedRegion; requestedRegion.SetIndex(3, t); requestedRegion.SetSize(3, 1); this->m_RequestedRegion = requestedRegion; this->GetSource()->Update(); } return m_PolyDatas[t].GetPointer(); } return nullptr; } void mitk::Surface::UpdateOutputInformation() { if (this->GetSource().IsNotNull()) this->GetSource()->UpdateOutputInformation(); if (m_CalculateBoundingBox == true && !m_PolyDatas.empty()) this->CalculateBoundingBox(); else this->GetTimeGeometry()->Update(); } void mitk::Surface::CalculateBoundingBox() { TimeGeometry *timeGeometry = this->GetTimeGeometry(); if (timeGeometry->CountTimeSteps() != m_PolyDatas.size()) mitkThrow() << "Number of geometry time steps is inconsistent with number of poly data pointers."; for (unsigned int i = 0; i < m_PolyDatas.size(); ++i) { vtkPolyData *polyData = m_PolyDatas[i].GetPointer(); double bounds[6] = {0}; if (polyData != nullptr && polyData->GetNumberOfPoints() > 0) { // polyData->Update(); //VTK6_TODO vtk pipeline polyData->ComputeBounds(); polyData->GetBounds(bounds); } BaseGeometry::Pointer geometry = timeGeometry->GetGeometryForTimeStep(i); if (geometry.IsNull()) mitkThrow() << "Time-sliced geometry is invalid (equals nullptr)."; geometry->SetFloatBounds(bounds); } timeGeometry->Update(); m_CalculateBoundingBox = false; } void mitk::Surface::SetRequestedRegionToLargestPossibleRegion() { m_RequestedRegion = GetLargestPossibleRegion(); } bool mitk::Surface::RequestedRegionIsOutsideOfTheBufferedRegion() { RegionType::IndexValueType end = m_RequestedRegion.GetIndex(3) + m_RequestedRegion.GetSize(3); if (static_cast(m_PolyDatas.size()) < end) return true; for (RegionType::IndexValueType t = m_RequestedRegion.GetIndex(3); t < end; ++t) { if (m_PolyDatas[t] == nullptr) return true; } return false; } bool mitk::Surface::VerifyRequestedRegion() { if (m_RequestedRegion.GetIndex(3) >= 0 && m_RequestedRegion.GetIndex(3) + m_RequestedRegion.GetSize(3) <= m_PolyDatas.size()) return true; return false; } void mitk::Surface::SetRequestedRegion(const itk::DataObject *data) { const auto *surface = dynamic_cast(data); if (surface != nullptr) m_RequestedRegion = surface->GetRequestedRegion(); else mitkThrow() << "Data object used to get requested region is not a mitk::Surface."; } void mitk::Surface::SetRequestedRegion(Surface::RegionType *region) { if (region == nullptr) mitkThrow() << "Requested region is invalid (equals nullptr)"; m_RequestedRegion = *region; } void mitk::Surface::CopyInformation(const itk::DataObject *data) { Superclass::CopyInformation(data); const auto *surface = dynamic_cast(data); if (surface == nullptr) mitkThrow() << "Data object used to get largest possible region is not a mitk::Surface."; m_LargestPossibleRegion = surface->GetLargestPossibleRegion(); } void mitk::Surface::Update() { using ::Update; if (this->GetSource().IsNull()) std::for_each(m_PolyDatas.begin(), m_PolyDatas.end(), Update); Superclass::Update(); } void mitk::Surface::Expand(unsigned int timeSteps) { if (timeSteps > m_PolyDatas.size()) { Superclass::Expand(timeSteps); m_PolyDatas.resize(timeSteps); m_CalculateBoundingBox = true; } } void mitk::Surface::ExecuteOperation(Operation *operation) { switch (operation->GetOperationType()) { case OpSURFACECHANGED: { auto *surfaceOperation = dynamic_cast(operation); if (surfaceOperation == nullptr) break; unsigned int timeStep = surfaceOperation->GetTimeStep(); if (m_PolyDatas[timeStep] != nullptr) { vtkPolyData *updatedPolyData = surfaceOperation->GetVtkPolyData(); if (updatedPolyData != nullptr) { this->SetVtkPolyData(updatedPolyData, timeStep); this->CalculateBoundingBox(); this->Modified(); } } break; } default: return; } } unsigned int mitk::Surface::GetSizeOfPolyDataSeries() const { return m_PolyDatas.size(); } void mitk::Surface::Graft(const DataObject *data) { const auto *surface = dynamic_cast(data); if (surface == nullptr) mitkThrow() << "Data object used to graft surface is not a mitk::Surface."; this->CopyInformation(data); m_PolyDatas.clear(); for (unsigned int i = 0; i < surface->GetSizeOfPolyDataSeries(); ++i) { m_PolyDatas.push_back(vtkSmartPointer::New()); m_PolyDatas.back()->DeepCopy(surface->GetVtkPolyData(i)); } } void mitk::Surface::PrintSelf(std::ostream &os, itk::Indent indent) const { Superclass::PrintSelf(os, indent); os << indent << "\nNumber PolyDatas: " << m_PolyDatas.size() << "\n"; unsigned int count = 0; for (auto it = m_PolyDatas.begin(); it != m_PolyDatas.end(); ++it) { os << "\n"; if (*it != nullptr) { os << indent << "PolyData at time step " << count << ":\n"; os << indent << "Number of cells: " << (*it)->GetNumberOfCells() << "\n"; os << indent << "Number of points: " << (*it)->GetNumberOfPoints() << "\n\n"; os << indent << "VTKPolyData:\n"; (*it)->Print(os); } else { os << indent << "Empty PolyData at time step " << count << "\n"; } ++count; } } bool mitk::Equal(vtkPolyData *leftHandSide, vtkPolyData *rightHandSide, mitk::ScalarType eps, bool verbose) { if ((leftHandSide == nullptr) || (rightHandSide == nullptr)) { MITK_ERROR << "mitk::Equal( vtkPolyData* leftHandSide, vtkPolyData* rightHandSide, mitk::ScalarType eps, bool " "verbose ) does not work for nullptr pointer input."; return false; } return Equal(*leftHandSide, *rightHandSide, eps, verbose); } bool mitk::Equal(vtkPolyData &leftHandSide, vtkPolyData &rightHandSide, mitk::ScalarType eps, bool verbose) { bool noDifferenceFound = true; if (!mitk::Equal(leftHandSide.GetNumberOfCells(), rightHandSide.GetNumberOfCells(), eps, verbose)) { if (verbose) MITK_INFO << "[Equal( vtkPolyData*, vtkPolyData* )] Number of cells not equal"; noDifferenceFound = false; } if (!mitk::Equal(leftHandSide.GetNumberOfVerts(), rightHandSide.GetNumberOfVerts(), eps, verbose)) { if (verbose) MITK_INFO << "[Equal( vtkPolyData*, vtkPolyData* )] Number of vertices not equal"; noDifferenceFound = false; } if (!mitk::Equal(leftHandSide.GetNumberOfLines(), rightHandSide.GetNumberOfLines(), eps, verbose)) { if (verbose) MITK_INFO << "[Equal( vtkPolyData*, vtkPolyData* )] Number of lines not equal"; noDifferenceFound = false; } if (!mitk::Equal(leftHandSide.GetNumberOfPolys(), rightHandSide.GetNumberOfPolys(), eps, verbose)) { if (verbose) MITK_INFO << "[Equal( vtkPolyData*, vtkPolyData* )] Number of polys not equal"; noDifferenceFound = false; } if (!mitk::Equal(leftHandSide.GetNumberOfStrips(), rightHandSide.GetNumberOfStrips(), eps, verbose)) { if (verbose) MITK_INFO << "[Equal( vtkPolyData*, vtkPolyData* )] Number of strips not equal"; noDifferenceFound = false; } { unsigned int numberOfPointsRight = rightHandSide.GetPoints()->GetNumberOfPoints(); unsigned int numberOfPointsLeft = leftHandSide.GetPoints()->GetNumberOfPoints(); if (!mitk::Equal(numberOfPointsLeft, numberOfPointsRight, eps, verbose)) { if (verbose) MITK_INFO << "[Equal( vtkPolyData*, vtkPolyData* )] Number of points not equal"; noDifferenceFound = false; } else { for (unsigned int i(0); i < numberOfPointsRight; i++) { bool pointFound = false; double pointOne[3]; rightHandSide.GetPoints()->GetPoint(i, pointOne); for (unsigned int j(0); j < numberOfPointsLeft; j++) { double pointTwo[3]; leftHandSide.GetPoints()->GetPoint(j, pointTwo); double x = pointOne[0] - pointTwo[0]; double y = pointOne[1] - pointTwo[1]; double z = pointOne[2] - pointTwo[2]; double distance = x * x + y * y + z * z; if (distance < eps) { pointFound = true; break; } } if (!pointFound) { if (verbose) { MITK_INFO << "[Equal( vtkPolyData*, vtkPolyData* )] Right hand side point with id " << i << " and coordinates ( " << std::setprecision(12) << pointOne[0] << " ; " << pointOne[1] << " ; " << pointOne[2] << " ) could not be found in left hand side with epsilon " << eps << "."; } noDifferenceFound = false; break; } } } } return noDifferenceFound; } bool mitk::Equal(mitk::Surface *leftHandSide, mitk::Surface *rightHandSide, mitk::ScalarType eps, bool verbose) { if ((leftHandSide == nullptr) || (rightHandSide == nullptr)) { MITK_ERROR << "mitk::Equal( mitk::Surface* leftHandSide, mitk::Surface* rightHandSide, mitk::ScalarType eps, bool " "verbose ) does not work with nullptr pointer input."; return false; } return Equal(*leftHandSide, *rightHandSide, eps, verbose); } bool mitk::Equal(mitk::Surface &leftHandSide, mitk::Surface &rightHandSide, mitk::ScalarType eps, bool verbose) { bool noDifferenceFound = true; if (!mitk::Equal(leftHandSide.GetSizeOfPolyDataSeries(), rightHandSide.GetSizeOfPolyDataSeries(), eps, verbose)) { if (verbose) MITK_INFO << "[Equal( mitk::surface&, mitk::surface& )] Size of PolyData series not equal."; return false; } // No mitk::Equal for TimeGeometry implemented. // if( ! mitk::Equal( leftHandSide->GetTimeGeometry(), rightHandSide->GetTimeGeometry(), eps, verbose ) ) //{ // if(verbose) // MITK_INFO << "[Equal( mitk::surface&, mitk::surface& )] Time sliced geometries not equal"; // noDifferenceFound = false; //} for (unsigned int i(0); i < rightHandSide.GetSizeOfPolyDataSeries(); i++) { if (!mitk::Equal(*leftHandSide.GetVtkPolyData(i), *rightHandSide.GetVtkPolyData(i), eps, verbose)) { if (verbose) MITK_INFO << "[Equal( mitk::surface&, mitk::surface& )] Poly datas not equal."; noDifferenceFound = false; } } return noDifferenceFound; } diff --git a/Modules/Core/src/IO/mitkIOUtil.cpp b/Modules/Core/src/IO/mitkIOUtil.cpp index 2e05ce7d71..17ff57df47 100644 --- a/Modules/Core/src/IO/mitkIOUtil.cpp +++ b/Modules/Core/src/IO/mitkIOUtil.cpp @@ -1,1015 +1,1015 @@ /*============================================================================ 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 "mitkIOUtil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ITK #include // VTK #include #include #include #include #include static std::string GetLastErrorStr() { #ifdef US_PLATFORM_POSIX return std::string(strerror(errno)); #else // Retrieve the system error message for the last-error code LPVOID lpMsgBuf; DWORD dw = GetLastError(); FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, nullptr); std::string errMsg((LPCTSTR)lpMsgBuf); LocalFree(lpMsgBuf); return errMsg; #endif } #ifdef US_PLATFORM_WINDOWS #include #include // make the posix flags point to the obsolete bsd types on windows #define S_IRUSR S_IREAD #define S_IWUSR S_IWRITE #else #include #include #include #endif #include #include static const char validLetters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // A cross-platform version of the mkstemps function static int mkstemps_compat(char *tmpl, int suffixlen) { static unsigned long long value = 0; int savedErrno = errno; // Lower bound on the number of temporary files to attempt to generate. #define ATTEMPTS_MIN (62 * 62 * 62) /* The number of times to attempt to generate a temporary file. To conform to POSIX, this must be no smaller than TMP_MAX. */ #if ATTEMPTS_MIN < TMP_MAX const unsigned int attempts = TMP_MAX; #else const unsigned int attempts = ATTEMPTS_MIN; #endif const int len = strlen(tmpl); if ((len - suffixlen) < 6 || strncmp(&tmpl[len - 6 - suffixlen], "XXXXXX", 6)) { errno = EINVAL; return -1; } /* This is where the Xs start. */ char *XXXXXX = &tmpl[len - 6 - suffixlen]; /* Get some more or less random data. */ #ifdef US_PLATFORM_WINDOWS { SYSTEMTIME stNow; FILETIME ftNow; // get system time GetSystemTime(&stNow); stNow.wMilliseconds = 500; if (!SystemTimeToFileTime(&stNow, &ftNow)) { errno = -1; return -1; } unsigned long long randomTimeBits = ((static_cast(ftNow.dwHighDateTime) << 32) | static_cast(ftNow.dwLowDateTime)); value = randomTimeBits ^ static_cast(GetCurrentThreadId()); } #else { struct timeval tv; gettimeofday(&tv, nullptr); unsigned long long randomTimeBits = ((static_cast(tv.tv_usec) << 32) | static_cast(tv.tv_sec)); value = randomTimeBits ^ static_cast(getpid()); } #endif for (unsigned int count = 0; count < attempts; value += 7777, ++count) { unsigned long long v = value; /* Fill in the random bits. */ XXXXXX[0] = validLetters[v % 62]; v /= 62; XXXXXX[1] = validLetters[v % 62]; v /= 62; XXXXXX[2] = validLetters[v % 62]; v /= 62; XXXXXX[3] = validLetters[v % 62]; v /= 62; XXXXXX[4] = validLetters[v % 62]; v /= 62; XXXXXX[5] = validLetters[v % 62]; int fd = open(tmpl, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (fd >= 0) { errno = savedErrno; return fd; } else if (errno != EEXIST) { return -1; } } /* We got out of the loop because we ran out of combinations to try. */ errno = EEXIST; return -1; } // A cross-platform version of the POSIX mkdtemp function static char *mkdtemps_compat(char *tmpl, int suffixlen) { static unsigned long long value = 0; int savedErrno = errno; // Lower bound on the number of temporary dirs to attempt to generate. #define ATTEMPTS_MIN (62 * 62 * 62) /* The number of times to attempt to generate a temporary dir. To conform to POSIX, this must be no smaller than TMP_MAX. */ #if ATTEMPTS_MIN < TMP_MAX const unsigned int attempts = TMP_MAX; #else const unsigned int attempts = ATTEMPTS_MIN; #endif const int len = strlen(tmpl); if ((len - suffixlen) < 6 || strncmp(&tmpl[len - 6 - suffixlen], "XXXXXX", 6)) { errno = EINVAL; return nullptr; } /* This is where the Xs start. */ char *XXXXXX = &tmpl[len - 6 - suffixlen]; /* Get some more or less random data. */ #ifdef US_PLATFORM_WINDOWS { SYSTEMTIME stNow; FILETIME ftNow; // get system time GetSystemTime(&stNow); stNow.wMilliseconds = 500; if (!SystemTimeToFileTime(&stNow, &ftNow)) { errno = -1; return nullptr; } unsigned long long randomTimeBits = ((static_cast(ftNow.dwHighDateTime) << 32) | static_cast(ftNow.dwLowDateTime)); value = randomTimeBits ^ static_cast(GetCurrentThreadId()); } #else { struct timeval tv; gettimeofday(&tv, nullptr); unsigned long long randomTimeBits = ((static_cast(tv.tv_usec) << 32) | static_cast(tv.tv_sec)); value = randomTimeBits ^ static_cast(getpid()); } #endif unsigned int count = 0; for (; count < attempts; value += 7777, ++count) { unsigned long long v = value; /* Fill in the random bits. */ XXXXXX[0] = validLetters[v % 62]; v /= 62; XXXXXX[1] = validLetters[v % 62]; v /= 62; XXXXXX[2] = validLetters[v % 62]; v /= 62; XXXXXX[3] = validLetters[v % 62]; v /= 62; XXXXXX[4] = validLetters[v % 62]; v /= 62; XXXXXX[5] = validLetters[v % 62]; #ifdef US_PLATFORM_WINDOWS int fd = _mkdir(tmpl); //, _S_IREAD | _S_IWRITE | _S_IEXEC); #else int fd = mkdir(tmpl, S_IRUSR | S_IWUSR | S_IXUSR); #endif if (fd >= 0) { errno = savedErrno; return tmpl; } else if (errno != EEXIST) { return nullptr; } } /* We got out of the loop because we ran out of combinations to try. */ errno = EEXIST; return nullptr; } //#endif //************************************************************** // mitk::IOUtil method definitions namespace mitk { struct IOUtil::Impl { struct FixedReaderOptionsFunctor : public ReaderOptionsFunctorBase { FixedReaderOptionsFunctor(const IFileReader::Options &options) : m_Options(options) {} bool operator()(LoadInfo &loadInfo) const override { IFileReader *reader = loadInfo.m_ReaderSelector.GetSelected().GetReader(); if (reader) { reader->SetOptions(m_Options); } return false; } private: const IFileReader::Options &m_Options; }; struct FixedWriterOptionsFunctor : public WriterOptionsFunctorBase { FixedWriterOptionsFunctor(const IFileReader::Options &options) : m_Options(options) {} bool operator()(SaveInfo &saveInfo) const override { IFileWriter *writer = saveInfo.m_WriterSelector.GetSelected().GetWriter(); if (writer) { writer->SetOptions(m_Options); } return false; } private: const IFileWriter::Options &m_Options; }; static BaseData::Pointer LoadBaseDataFromFile(const std::string &path, const ReaderOptionsFunctorBase* optionsCallback = nullptr); }; BaseData::Pointer IOUtil::Impl::LoadBaseDataFromFile(const std::string &path, const ReaderOptionsFunctorBase *optionsCallback) { std::vector baseDataList = Load(path, optionsCallback); // The Load(path) call above should throw an exception if nothing could be loaded assert(!baseDataList.empty()); return baseDataList.front(); } #ifdef US_PLATFORM_WINDOWS std::string IOUtil::GetProgramPath() { char path[512]; std::size_t index = std::string(path, GetModuleFileName(nullptr, path, 512)).find_last_of('\\'); return std::string(path, index); } #elif defined(US_PLATFORM_APPLE) #include std::string IOUtil::GetProgramPath() { char path[512]; uint32_t size = sizeof(path); if (_NSGetExecutablePath(path, &size) == 0) { std::size_t index = std::string(path).find_last_of('/'); std::string strPath = std::string(path, index); // const char* execPath = strPath.c_str(); // mitk::StandardFileLocations::GetInstance()->AddDirectoryForSearch(execPath,false); return strPath; } return std::string(); } #else #include #include #include std::string IOUtil::GetProgramPath() { std::stringstream ss; ss << "/proc/" << getpid() << "/exe"; char proc[512] = {0}; ssize_t ch = readlink(ss.str().c_str(), proc, 512); if (ch == -1) return std::string(); std::size_t index = std::string(proc).find_last_of('/'); return std::string(proc, index); } #endif char IOUtil::GetDirectorySeparator() { #ifdef US_PLATFORM_WINDOWS return '\\'; #else return '/'; #endif } std::string IOUtil::GetTempPath() { static std::string result; if (result.empty()) { #ifdef US_PLATFORM_WINDOWS char tempPathTestBuffer[1]; DWORD bufferLength = GetTempPathA(1, tempPathTestBuffer); if (bufferLength == 0) { mitkThrow() << GetLastErrorStr(); } std::vector tempPath(bufferLength); bufferLength = GetTempPathA(bufferLength, &tempPath[0]); if (bufferLength == 0) { mitkThrow() << GetLastErrorStr(); } result.assign(tempPath.begin(), tempPath.begin() + static_cast(bufferLength)); #else result = "/tmp/"; #endif } return result; } std::string IOUtil::CreateTemporaryFile(const std::string &templateName, std::string path) { std::ofstream tmpOutputStream; std::string returnValue = CreateTemporaryFile(tmpOutputStream, templateName, path); tmpOutputStream.close(); return returnValue; } std::string IOUtil::CreateTemporaryFile(std::ofstream &f, const std::string &templateName, std::string path) { return CreateTemporaryFile(f, std::ios_base::out | std::ios_base::trunc, templateName, path); } std::string IOUtil::CreateTemporaryFile(std::ofstream &f, std::ios_base::openmode mode, const std::string &templateName, std::string path) { if (path.empty()) { path = GetTempPath(); } path += templateName; std::vector dst_path(path.begin(), path.end()); dst_path.push_back('\0'); std::size_t lastX = path.find_last_of('X'); std::size_t firstX = path.find_last_not_of('X', lastX); int firstNonX = firstX == std::string::npos ? -1 : firstX - 1; while (lastX != std::string::npos && (lastX - firstNonX) < 6) { lastX = path.find_last_of('X', firstX); firstX = path.find_last_not_of('X', lastX); firstNonX = firstX == std::string::npos ? -1 : firstX - 1; } std::size_t suffixlen = lastX == std::string::npos ? path.size() : path.size() - lastX - 1; int fd = mkstemps_compat(&dst_path[0], suffixlen); if (fd != -1) { path.assign(dst_path.begin(), dst_path.end() - 1); f.open(path.c_str(), mode | std::ios_base::out | std::ios_base::trunc); close(fd); } else { mitkThrow() << "Creating temporary file " << &dst_path[0] << " failed: " << GetLastErrorStr(); } return path; } std::string IOUtil::CreateTemporaryDirectory(const std::string &templateName, std::string path) { if (path.empty()) { path = GetTempPath(); } path += GetDirectorySeparator() + templateName; std::vector dst_path(path.begin(), path.end()); dst_path.push_back('\0'); std::size_t lastX = path.find_last_of('X'); std::size_t firstX = path.find_last_not_of('X', lastX); int firstNonX = firstX == std::string::npos ? -1 : firstX - 1; while (lastX != std::string::npos && (lastX - firstNonX) < 6) { lastX = path.find_last_of('X', firstX); firstX = path.find_last_not_of('X', lastX); firstNonX = firstX == std::string::npos ? -1 : firstX - 1; } std::size_t suffixlen = lastX == std::string::npos ? path.size() : path.size() - lastX - 1; if (mkdtemps_compat(&dst_path[0], suffixlen) == nullptr) { mitkThrow() << "Creating temporary directory " << &dst_path[0] << " failed: " << GetLastErrorStr(); } path.assign(dst_path.begin(), dst_path.end() - 1); return path; } DataStorage::SetOfObjects::Pointer IOUtil::Load(const std::string &path, DataStorage &storage, const ReaderOptionsFunctorBase *optionsCallback) { std::vector paths; paths.push_back(path); return Load(paths, storage, optionsCallback); } DataStorage::SetOfObjects::Pointer IOUtil::Load(const std::string &path, const IFileReader::Options &options, DataStorage &storage) { std::vector loadInfos; loadInfos.push_back(LoadInfo(path)); DataStorage::SetOfObjects::Pointer nodeResult = DataStorage::SetOfObjects::New(); Impl::FixedReaderOptionsFunctor optionsCallback(options); std::string errMsg = Load(loadInfos, nodeResult, &storage, &optionsCallback); if (!errMsg.empty()) { mitkThrow() << errMsg; } return nodeResult; } std::vector IOUtil::Load(const std::string &path, const ReaderOptionsFunctorBase *optionsCallback) { std::vector paths; paths.push_back(path); return Load(paths, optionsCallback); } std::vector IOUtil::Load(const std::string &path, const IFileReader::Options &options) { std::vector loadInfos; loadInfos.push_back(LoadInfo(path)); Impl::FixedReaderOptionsFunctor optionsCallback(options); std::string errMsg = Load(loadInfos, nullptr, nullptr, &optionsCallback); if (!errMsg.empty()) { mitkThrow() << errMsg; } return loadInfos.front().m_Output; } DataStorage::SetOfObjects::Pointer IOUtil::Load(const std::vector &paths, DataStorage &storage, const ReaderOptionsFunctorBase *optionsCallback) { DataStorage::SetOfObjects::Pointer nodeResult = DataStorage::SetOfObjects::New(); std::vector loadInfos; for (const auto &loadInfo : paths) { loadInfos.emplace_back(loadInfo); } std::string errMsg = Load(loadInfos, nodeResult, &storage, optionsCallback); if (!errMsg.empty()) { mitkThrow() << errMsg; } return nodeResult; } std::vector IOUtil::Load(const std::vector &paths, const ReaderOptionsFunctorBase *optionsCallback) { std::vector result; std::vector loadInfos; for (const auto &loadInfo : paths) { loadInfos.emplace_back(loadInfo); } std::string errMsg = Load(loadInfos, nullptr, nullptr, optionsCallback); if (!errMsg.empty()) { mitkThrow() << errMsg; } for (std::vector::const_iterator iter = loadInfos.begin(), iterEnd = loadInfos.end(); iter != iterEnd; ++iter) { result.insert(result.end(), iter->m_Output.begin(), iter->m_Output.end()); } return result; } std::string IOUtil::Load(std::vector &loadInfos, DataStorage::SetOfObjects *nodeResult, DataStorage *ds, const ReaderOptionsFunctorBase *optionsCallback) { if (loadInfos.empty()) { return "No input files given"; } int filesToRead = loadInfos.size(); mitk::ProgressBar::GetInstance()->AddStepsToDo(2 * filesToRead); std::string errMsg; std::map usedReaderItems; std::vector< std::string > read_files; for (auto &loadInfo : loadInfos) { if(std::find(read_files.begin(), read_files.end(), loadInfo.m_Path) != read_files.end()) continue; std::vector readers = loadInfo.m_ReaderSelector.Get(); if (readers.empty()) { if (!itksys::SystemTools::FileExists(Utf8Util::Local8BitToUtf8(loadInfo.m_Path).c_str())) { errMsg += "File '" + loadInfo.m_Path + "' does not exist\n"; } else { errMsg += "No reader available for '" + loadInfo.m_Path + "'\n"; } continue; } bool callOptionsCallback = readers.size() > 1 || !readers.front().GetReader()->GetOptions().empty(); // check if we already used a reader which should be re-used std::vector currMimeTypes = loadInfo.m_ReaderSelector.GetMimeTypes(); std::string selectedMimeType; for (std::vector::const_iterator mimeTypeIter = currMimeTypes.begin(), mimeTypeIterEnd = currMimeTypes.end(); mimeTypeIter != mimeTypeIterEnd; ++mimeTypeIter) { std::map::const_iterator oldSelectedItemIter = usedReaderItems.find(mimeTypeIter->GetName()); if (oldSelectedItemIter != usedReaderItems.end()) { // we found an already used item for a mime-type which is contained // in the current reader set, check all current readers if there service // id equals the old reader for (std::vector::const_iterator currReaderItem = readers.begin(), currReaderItemEnd = readers.end(); currReaderItem != currReaderItemEnd; ++currReaderItem) { if (currReaderItem->GetMimeType().GetName() == mimeTypeIter->GetName() && currReaderItem->GetServiceId() == oldSelectedItemIter->second.GetServiceId() && currReaderItem->GetConfidenceLevel() >= oldSelectedItemIter->second.GetConfidenceLevel()) { // okay, we used the same reader already, re-use its options selectedMimeType = mimeTypeIter->GetName(); callOptionsCallback = false; loadInfo.m_ReaderSelector.Select(oldSelectedItemIter->second.GetServiceId()); loadInfo.m_ReaderSelector.GetSelected().GetReader()->SetOptions( oldSelectedItemIter->second.GetReader()->GetOptions()); break; } } if (!selectedMimeType.empty()) break; } } if (callOptionsCallback && optionsCallback) { callOptionsCallback = (*optionsCallback)(loadInfo); if (!callOptionsCallback && !loadInfo.m_Cancel) { usedReaderItems.erase(selectedMimeType); FileReaderSelector::Item selectedItem = loadInfo.m_ReaderSelector.GetSelected(); usedReaderItems.insert(std::make_pair(selectedItem.GetMimeType().GetName(), selectedItem)); } } if (loadInfo.m_Cancel) { errMsg += "Reading operation(s) cancelled."; break; } IFileReader *reader = loadInfo.m_ReaderSelector.GetSelected().GetReader(); if (reader == nullptr) { errMsg += "Unexpected nullptr reader."; break; } reader->SetProperties(loadInfo.m_Properties); // Do the actual reading try { DataStorage::SetOfObjects::Pointer nodes; if (ds != nullptr) { nodes = reader->Read(*ds); std::vector< std::string > new_files = reader->GetReadFiles(); read_files.insert( read_files.end(), new_files.begin(), new_files.end() ); } else { nodes = DataStorage::SetOfObjects::New(); std::vector baseData = reader->Read(); for (auto iter = baseData.begin(); iter != baseData.end(); ++iter) { if (iter->IsNotNull()) { mitk::DataNode::Pointer node = mitk::DataNode::New(); node->SetData(*iter); nodes->InsertElement(nodes->Size(), node); } } std::vector< std::string > new_files = reader->GetReadFiles(); read_files.insert( read_files.end(), new_files.begin(), new_files.end() ); } for (DataStorage::SetOfObjects::ConstIterator nodeIter = nodes->Begin(), nodeIterEnd = nodes->End(); nodeIter != nodeIterEnd; ++nodeIter) { const mitk::DataNode::Pointer &node = nodeIter->Value(); mitk::BaseData::Pointer data = node->GetData(); if (data.IsNull()) { continue; } data->SetProperty("path", mitk::StringProperty::New(Utf8Util::Local8BitToUtf8(loadInfo.m_Path))); loadInfo.m_Output.push_back(data); if (nodeResult) { nodeResult->push_back(nodeIter->Value()); } } if (loadInfo.m_Output.empty() || (nodeResult && nodeResult->Size() == 0)) { errMsg += "Unknown read error occurred reading " + loadInfo.m_Path; } } catch (const std::exception &e) { errMsg += "Exception occurred when reading file " + loadInfo.m_Path + ":\n" + e.what() + "\n\n"; } mitk::ProgressBar::GetInstance()->Progress(2); --filesToRead; } if (!errMsg.empty()) { MITK_ERROR << errMsg; } mitk::ProgressBar::GetInstance()->Progress(2 * filesToRead); return errMsg; } std::vector IOUtil::Load(const us::ModuleResource &usResource, std::ios_base::openmode mode) { us::ModuleResourceStream resStream(usResource, mode); mitk::CoreServicePointer mimeTypeProvider(mitk::CoreServices::GetMimeTypeProvider()); std::vector mimetypes = mimeTypeProvider->GetMimeTypesForFile(usResource.GetResourcePath()); std::vector data; if (mimetypes.empty()) { mitkThrow() << "No mimetype for resource stream: " << usResource.GetResourcePath(); return data; } mitk::FileReaderRegistry fileReaderRegistry; std::vector> refs = fileReaderRegistry.GetReferences(mimetypes[0]); if (refs.empty()) { mitkThrow() << "No reader available for resource stream: " << usResource.GetResourcePath(); return data; } mitk::IFileReader *reader = fileReaderRegistry.GetReader(refs[0]); reader->SetInput(usResource.GetResourcePath(), &resStream); data = reader->Read(); return data; } BaseData::Pointer IOUtil::Load(const std::string& path, const PropertyList* properties) { LoadInfo loadInfo(path); loadInfo.m_Properties = properties; std::vector loadInfos; loadInfos.push_back(loadInfo); auto errMsg = Load(loadInfos, nullptr, nullptr, nullptr); if (!errMsg.empty()) mitkThrow() << errMsg; return loadInfos.front().m_Output.front(); } void IOUtil::Save(const BaseData *data, const std::string &path, bool setPathProperty) { Save(data, path, IFileWriter::Options(), setPathProperty); } void IOUtil::Save(const BaseData *data, const std::string &path, const IFileWriter::Options &options, bool setPathProperty) { Save(data, std::string(), path, options, setPathProperty); } void IOUtil::Save(const BaseData *data, const std::string &mimeType, const std::string &path, bool addExtension, bool setPathProperty) { Save(data, mimeType, path, IFileWriter::Options(), addExtension, setPathProperty); } void IOUtil::Save(const BaseData *data, const std::string &mimeType, const std::string &path, const IFileWriter::Options &options, bool addExtension, bool setPathProperty) { if ((data == nullptr) || (data->IsEmpty())) - mitkThrow() << "BaseData cannotbe null or empty for save methods in IOUtil.h."; + mitkThrow() << "BaseData cannot be null or empty for save methods in IOUtil.h."; std::string errMsg; if (options.empty()) { errMsg = Save(data, mimeType, path, nullptr, addExtension, setPathProperty); } else { Impl::FixedWriterOptionsFunctor optionsCallback(options); errMsg = Save(data, mimeType, path, &optionsCallback, addExtension, setPathProperty); } if (!errMsg.empty()) { mitkThrow() << errMsg; } } void IOUtil::Save(std::vector &saveInfos, bool setPathProperty) { std::string errMsg = Save(saveInfos, nullptr, setPathProperty); if (!errMsg.empty()) { mitkThrow() << errMsg; } } std::string IOUtil::Save(const BaseData *data, const std::string &mimeTypeName, const std::string &path, WriterOptionsFunctorBase *optionsCallback, bool addExtension, bool setPathProperty) { if (path.empty()) { return "No output filename given"; } mitk::CoreServicePointer mimeTypeProvider(mitk::CoreServices::GetMimeTypeProvider()); MimeType mimeType = mimeTypeProvider->GetMimeTypeForName(mimeTypeName); SaveInfo saveInfo(data, mimeType, path); std::string ext = Utf8Util::Utf8ToLocal8Bit(itksys::SystemTools::GetFilenameExtension(Utf8Util::Local8BitToUtf8(path))); if (saveInfo.m_WriterSelector.IsEmpty()) { return std::string("No suitable writer found for the current data of type ") + data->GetNameOfClass() + (mimeType.IsValid() ? (std::string(" and mime-type ") + mimeType.GetName()) : std::string()) + (ext.empty() ? std::string() : (std::string(" with extension ") + ext)); } // Add an extension if not already specified if (ext.empty() && addExtension) { ext = saveInfo.m_MimeType.GetExtensions().empty() ? std::string() : "." + saveInfo.m_MimeType.GetExtensions().front(); saveInfo.m_Path += ext; } std::vector infos; infos.push_back(saveInfo); return Save(infos, optionsCallback, setPathProperty); } std::string IOUtil::Save(std::vector &saveInfos, WriterOptionsFunctorBase *optionsCallback, bool setPathProperty) { if (saveInfos.empty()) { return "No data for saving available"; } int filesToWrite = saveInfos.size(); mitk::ProgressBar::GetInstance()->AddStepsToDo(2 * filesToWrite); std::string errMsg; std::set usedSaveInfos; for (auto &saveInfo : saveInfos) { const std::string baseDataType = saveInfo.m_BaseData->GetNameOfClass(); std::vector writers = saveInfo.m_WriterSelector.Get(); // Error out if no compatible Writer was found if (writers.empty()) { errMsg += std::string("No writer available for ") + baseDataType + " data.\n"; continue; } bool callOptionsCallback = writers.size() > 1 || !writers[0].GetWriter()->GetOptions().empty(); // check if we already used a writer for this base data type // which should be re-used auto oldSaveInfoIter = usedSaveInfos.find(saveInfo); if (oldSaveInfoIter != usedSaveInfos.end()) { // we previously saved a base data object of the same data with the same mime-type, // check if the same writer is contained in the current writer set and if the // confidence level matches FileWriterSelector::Item oldSelectedItem = oldSaveInfoIter->m_WriterSelector.Get(oldSaveInfoIter->m_WriterSelector.GetSelectedId()); for (std::vector::const_iterator currWriterItem = writers.begin(), currWriterItemEnd = writers.end(); currWriterItem != currWriterItemEnd; ++currWriterItem) { if (currWriterItem->GetServiceId() == oldSelectedItem.GetServiceId() && currWriterItem->GetConfidenceLevel() >= oldSelectedItem.GetConfidenceLevel()) { // okay, we used the same writer already, re-use its options callOptionsCallback = false; saveInfo.m_WriterSelector.Select(oldSaveInfoIter->m_WriterSelector.GetSelectedId()); saveInfo.m_WriterSelector.GetSelected().GetWriter()->SetOptions(oldSelectedItem.GetWriter()->GetOptions()); break; } } } if (callOptionsCallback && optionsCallback) { callOptionsCallback = (*optionsCallback)(saveInfo); if (!callOptionsCallback && !saveInfo.m_Cancel) { usedSaveInfos.erase(saveInfo); usedSaveInfos.insert(saveInfo); } } if (saveInfo.m_Cancel) { errMsg += "Writing operation(s) cancelled."; break; } IFileWriter *writer = saveInfo.m_WriterSelector.GetSelected().GetWriter(); if (writer == nullptr) { errMsg += "Unexpected nullptr writer."; break; } // Do the actual writing try { writer->SetOutputLocation(saveInfo.m_Path); writer->Write(); } catch (const std::exception &e) { errMsg += std::string("Exception occurred when writing to ") + saveInfo.m_Path + ":\n" + e.what() + "\n"; } if (setPathProperty) saveInfo.m_BaseData->GetPropertyList()->SetStringProperty("path", Utf8Util::Local8BitToUtf8(saveInfo.m_Path).c_str()); mitk::ProgressBar::GetInstance()->Progress(2); --filesToWrite; } if (!errMsg.empty()) { MITK_ERROR << errMsg; } mitk::ProgressBar::GetInstance()->Progress(2 * filesToWrite); return errMsg; } IOUtil::SaveInfo::SaveInfo(const BaseData *baseData, const MimeType &mimeType, const std::string &path) : m_BaseData(baseData), m_WriterSelector(baseData, mimeType.GetName(), path), m_MimeType(mimeType.IsValid() ? mimeType // use the original mime-type : (m_WriterSelector.IsEmpty() ? mimeType // no writer found, use the original invalid mime-type : m_WriterSelector.GetDefault().GetMimeType() // use the found default mime-type )), m_Path(path), m_Cancel(false) { } bool IOUtil::SaveInfo::operator<(const IOUtil::SaveInfo &other) const { int r = strcmp(m_BaseData->GetNameOfClass(), other.m_BaseData->GetNameOfClass()); if (r == 0) { return m_WriterSelector.GetSelected().GetMimeType() < other.m_WriterSelector.GetSelected().GetMimeType(); } return r < 0; } IOUtil::LoadInfo::LoadInfo(const std::string &path) : m_Path(path), m_ReaderSelector(path), m_Cancel(false), m_Properties(nullptr) { } } diff --git a/Modules/SceneSerialization/src/mitkSceneIO.cpp b/Modules/SceneSerialization/src/mitkSceneIO.cpp index 888eda6c5c..c96aa37a3e 100644 --- a/Modules/SceneSerialization/src/mitkSceneIO.cpp +++ b/Modules/SceneSerialization/src/mitkSceneIO.cpp @@ -1,575 +1,577 @@ /*============================================================================ 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 #include #include "mitkBaseDataSerializer.h" #include "mitkPropertyListSerializer.h" #include "mitkSceneIO.h" #include "mitkSceneReader.h" #include "mitkBaseRenderer.h" #include "mitkProgressBar.h" #include "mitkRenderingManager.h" #include "mitkStandaloneDataStorage.h" #include #include #include #include #include #include #include "itksys/SystemTools.hxx" #include mitk::SceneIO::SceneIO() : m_WorkingDirectory(""), m_UnzipErrors(0) { } mitk::SceneIO::~SceneIO() { } std::string mitk::SceneIO::CreateEmptyTempDirectory() { mitk::UIDGenerator uidGen; // std::string returnValue = mitk::StandardFileLocations::GetInstance()->GetOptionDirectory() + // Poco::Path::separator() + "SceneIOTemp" + uidGen.GetUID(); std::string returnValue = Poco::Path::temp() + "SceneIOTemp" + uidGen.GetUID(); std::string uniquename = returnValue + Poco::Path::separator(); Poco::File tempdir(uniquename); try { bool existsNot = tempdir.createDirectory(); if (!existsNot) { MITK_ERROR << "Warning: Directory already exitsts: " << uniquename << " (choosing another)"; returnValue = mitk::StandardFileLocations::GetInstance()->GetOptionDirectory() + Poco::Path::separator() + "SceneIOTempDirectory" + uidGen.GetUID(); uniquename = returnValue + Poco::Path::separator(); Poco::File tempdir2(uniquename); if (!tempdir2.createDirectory()) { MITK_ERROR << "Warning: Second directory also already exitsts: " << uniquename; } } } catch (std::exception &e) { MITK_ERROR << "Could not create temporary directory " << uniquename << ":" << e.what(); return ""; } return returnValue; } mitk::DataStorage::Pointer mitk::SceneIO::LoadScene(const std::string &filename, DataStorage *pStorage, bool clearStorageFirst) { mitk::LocaleSwitch localeSwitch("C"); // prepare data storage DataStorage::Pointer storage = pStorage; if (storage.IsNull()) { storage = StandaloneDataStorage::New().GetPointer(); } // test input filename if (filename.empty()) { MITK_ERROR << "No filename given. Not possible to load scene."; return storage; } // test if filename can be read std::ifstream file(filename.c_str(), std::ios::binary); if (!file.good()) { MITK_ERROR << "Cannot open '" << filename << "' for reading"; return storage; } // get new temporary directory m_WorkingDirectory = CreateEmptyTempDirectory(); if (m_WorkingDirectory.empty()) { MITK_ERROR << "Could not create temporary directory. Cannot open scene files."; return storage; } // unzip all filenames contents to temp dir m_UnzipErrors = 0; Poco::Zip::Decompress unzipper(file, Poco::Path(m_WorkingDirectory)); unzipper.EError += Poco::Delegate>( this, &SceneIO::OnUnzipError); unzipper.EOk += Poco::Delegate>( this, &SceneIO::OnUnzipOk); unzipper.decompressAllFiles(); unzipper.EError -= Poco::Delegate>( this, &SceneIO::OnUnzipError); unzipper.EOk -= Poco::Delegate>( this, &SceneIO::OnUnzipOk); if (m_UnzipErrors) { MITK_ERROR << "There were " << m_UnzipErrors << " errors unzipping '" << filename << "'. Will attempt to read whatever could be unzipped."; } // transcode locale-dependent string m_WorkingDirectory = Poco::Path::transcode (m_WorkingDirectory); auto indexFile = m_WorkingDirectory + mitk::IOUtil::GetDirectorySeparator() + "index.xml"; storage = LoadSceneUnzipped(indexFile, storage, clearStorageFirst); // delete temp directory try { Poco::File deleteDir(m_WorkingDirectory); deleteDir.remove(true); // recursive } catch (...) { MITK_ERROR << "Could not delete temporary directory " << m_WorkingDirectory; } // return new data storage, even if empty or uncomplete (return as much as possible but notify calling method) return storage; } mitk::DataStorage::Pointer mitk::SceneIO::LoadSceneUnzipped(const std::string &indexfilename, DataStorage *pStorage, bool clearStorageFirst) { mitk::LocaleSwitch localeSwitch("C"); // prepare data storage DataStorage::Pointer storage = pStorage; if (storage.IsNull()) { storage = StandaloneDataStorage::New().GetPointer(); } if (clearStorageFirst) { try { storage->Remove(storage->GetAll()); } catch (...) { MITK_ERROR << "DataStorage cannot be cleared properly."; } } // test input filename if (indexfilename.empty()) { MITK_ERROR << "No filename given. Not possible to load scene."; return storage; } // transcode locale-dependent string std::string tempfilename; std::string workingDir; itksys::SystemTools::SplitProgramPath(indexfilename, workingDir, tempfilename); // test if index.xml exists // parse index.xml with TinyXML tinyxml2::XMLDocument document; if (tinyxml2::XML_SUCCESS != document.LoadFile(indexfilename.c_str())) { MITK_ERROR << "Could not open/read/parse " << workingDir << mitk::IOUtil::GetDirectorySeparator() << "index.xml\nTinyXML reports: " << document.ErrorStr() << std::endl; return storage; } SceneReader::Pointer reader = SceneReader::New(); if (!reader->LoadScene(document, workingDir, storage)) { MITK_ERROR << "There were errors while loading scene file " << indexfilename << ". Your data may be corrupted"; } // return new data storage, even if empty or uncomplete (return as much as possible but notify calling method) return storage; } bool mitk::SceneIO::SaveScene(DataStorage::SetOfObjects::ConstPointer sceneNodes, const DataStorage *storage, const std::string &filename) { if (!sceneNodes) { MITK_ERROR << "No set of nodes given. Not possible to save scene."; return false; } if (!storage) { MITK_ERROR << "No data storage given. Not possible to save scene."; // \TODO: Technically, it would be possible to // save the nodes without their relation return false; } if (filename.empty()) { MITK_ERROR << "No filename given. Not possible to save scene."; return false; } mitk::LocaleSwitch localeSwitch("C"); try { m_FailedNodes = DataStorage::SetOfObjects::New(); m_FailedProperties = PropertyList::New(); // start XML DOM tinyxml2::XMLDocument document; document.InsertEndChild(document.NewDeclaration()); auto *version = document.NewElement("Version"); version->SetAttribute("Writer", __FILE__); version->SetAttribute("Revision", "$Revision: 17055 $"); version->SetAttribute("FileVersion", 1); document.InsertEndChild(version); // DataStorage::SetOfObjects::ConstPointer sceneNodes = storage->GetSubset( predicate ); if (sceneNodes.IsNull()) { MITK_WARN << "Saving empty scene to " << filename; } else { if (sceneNodes->size() == 0) { MITK_WARN << "Saving empty scene to " << filename; } MITK_INFO << "Storing scene with " << sceneNodes->size() << " objects to " << filename; m_WorkingDirectory = CreateEmptyTempDirectory(); if (m_WorkingDirectory.empty()) { MITK_ERROR << "Could not create temporary directory. Cannot create scene files."; return false; } ProgressBar::GetInstance()->AddStepsToDo(sceneNodes->size()); // find out about dependencies typedef std::map UIDMapType; typedef std::map> SourcesMapType; UIDMapType nodeUIDs; // for dependencies: ID of each node SourcesMapType sourceUIDs; // for dependencies: IDs of a node's parent nodes UIDGenerator nodeUIDGen("OBJECT_"); for (auto iter = sceneNodes->begin(); iter != sceneNodes->end(); ++iter) { DataNode *node = iter->GetPointer(); if (!node) continue; // unlikely event that we get a nullptr pointer as an object for saving. just ignore // generate UIDs for all source objects DataStorage::SetOfObjects::ConstPointer sourceObjects = storage->GetSources(node); for (auto sourceIter = sourceObjects->begin(); sourceIter != sourceObjects->end(); ++sourceIter) { if (std::find(sceneNodes->begin(), sceneNodes->end(), *sourceIter) == sceneNodes->end()) continue; // source is not saved, so don't generate a UID for this source // create a uid for the parent object if (nodeUIDs[*sourceIter].empty()) { nodeUIDs[*sourceIter] = nodeUIDGen.GetUID(); } // store this dependency for writing sourceUIDs[node].push_back(nodeUIDs[*sourceIter]); } if (nodeUIDs[node].empty()) { nodeUIDs[node] = nodeUIDGen.GetUID(); } } // write out objects, dependencies and properties for (auto iter = sceneNodes->begin(); iter != sceneNodes->end(); ++iter) { DataNode *node = iter->GetPointer(); if (node) { auto *nodeElement = document.NewElement("node"); std::string filenameHint(node->GetName()); filenameHint = itksys::SystemTools::MakeCindentifier( filenameHint.c_str()); // escape filename <-- only allow [A-Za-z0-9_], replace everything else with _ // store dependencies auto searchUIDIter = nodeUIDs.find(node); if (searchUIDIter != nodeUIDs.end()) { // store this node's ID nodeElement->SetAttribute("UID", searchUIDIter->second.c_str()); } auto searchSourcesIter = sourceUIDs.find(node); if (searchSourcesIter != sourceUIDs.end()) { // store all source IDs for (auto sourceUIDIter = searchSourcesIter->second.begin(); sourceUIDIter != searchSourcesIter->second.end(); ++sourceUIDIter) { auto *uidElement = document.NewElement("source"); uidElement->SetAttribute("UID", sourceUIDIter->c_str()); nodeElement->InsertEndChild(uidElement); } } // store basedata if (BaseData *data = node->GetData()) { // std::string filenameHint( node->GetName() ); bool error(false); auto *dataElement = SaveBaseData(document, data, filenameHint, error); // returns a reference to a file if (error) { m_FailedNodes->push_back(node); } // store basedata properties PropertyList *propertyList = data->GetPropertyList(); if (propertyList && !propertyList->IsEmpty()) { auto *baseDataPropertiesElement = SavePropertyList(document, propertyList, filenameHint + "-data"); // returns a reference to a file dataElement->InsertEndChild(baseDataPropertiesElement); } nodeElement->InsertEndChild(dataElement); } // store all renderwindow specific propertylists mitk::DataNode::PropertyListKeyNames propertyListKeys = node->GetPropertyListNames(); for (const auto &renderWindowName : propertyListKeys) { PropertyList *propertyList = node->GetPropertyList(renderWindowName); if (propertyList && !propertyList->IsEmpty()) { auto *renderWindowPropertiesElement = SavePropertyList(document, propertyList, filenameHint + "-" + renderWindowName); // returns a reference to a file renderWindowPropertiesElement->SetAttribute("renderwindow", renderWindowName.c_str()); nodeElement->InsertEndChild(renderWindowPropertiesElement); } } // don't forget the renderwindow independent list PropertyList *propertyList = node->GetPropertyList(); if (propertyList && !propertyList->IsEmpty()) { auto *propertiesElement = SavePropertyList(document, propertyList, filenameHint + "-node"); // returns a reference to a file nodeElement->InsertEndChild(propertiesElement); } document.InsertEndChild(nodeElement); } else { MITK_WARN << "Ignoring nullptr node during scene serialization."; } ProgressBar::GetInstance()->Progress(); } // end for all nodes } // end if sceneNodes std::string defaultLocale_WorkingDirectory = Poco::Path::transcode( m_WorkingDirectory ); auto xmlFilename = defaultLocale_WorkingDirectory + Poco::Path::separator() + "index.xml"; if (tinyxml2::XML_SUCCESS != document.SaveFile(xmlFilename.c_str())) { MITK_ERROR << "Could not write scene to " << defaultLocale_WorkingDirectory << Poco::Path::separator() << "index.xml" << "\nTinyXML reports '" << document.ErrorStr() << "'"; return false; } else { try { Poco::File deleteFile(filename.c_str()); if (deleteFile.exists()) { deleteFile.remove(); } // create zip at filename std::ofstream file(filename.c_str(), std::ios::binary | std::ios::out); if (!file.good()) { MITK_ERROR << "Could not open a zip file for writing: '" << filename << "'"; return false; } else { Poco::Zip::Compress zipper(file, true); Poco::Path tmpdir(m_WorkingDirectory); zipper.addRecursive(tmpdir); zipper.close(); } try { Poco::File deleteDir(m_WorkingDirectory); deleteDir.remove(true); // recursive } catch (...) { MITK_ERROR << "Could not delete temporary directory " << m_WorkingDirectory; return false; // ok? } } catch (std::exception &e) { MITK_ERROR << "Could not create ZIP file from " << m_WorkingDirectory << "\nReason: " << e.what(); return false; } return true; } } catch (std::exception &e) { MITK_ERROR << "Caught exception during saving temporary files to disk. Error description: '" << e.what() << "'"; return false; } } tinyxml2::XMLElement *mitk::SceneIO::SaveBaseData(tinyxml2::XMLDocument &doc, BaseData *data, const std::string &filenamehint, bool &error) { assert(data); error = true; // find correct serializer // the serializer must // - create a file containing all information to recreate the BaseData object --> needs to know where to put this // file (and a filename?) // - TODO what to do about writers that creates one file per timestep? auto *element = doc.NewElement("data"); element->SetAttribute("type", data->GetNameOfClass()); // construct name of serializer class std::string serializername(data->GetNameOfClass()); serializername += "Serializer"; std::list thingsThatCanSerializeThis = itk::ObjectFactoryBase::CreateAllInstance(serializername.c_str()); if (thingsThatCanSerializeThis.size() < 1) { MITK_ERROR << "No serializer found for " << data->GetNameOfClass() << ". Skipping object"; } for (auto iter = thingsThatCanSerializeThis.begin(); iter != thingsThatCanSerializeThis.end(); ++iter) { if (auto *serializer = dynamic_cast(iter->GetPointer())) { serializer->SetData(data); serializer->SetFilenameHint(filenamehint); std::string defaultLocale_WorkingDirectory = Poco::Path::transcode( m_WorkingDirectory ); serializer->SetWorkingDirectory(defaultLocale_WorkingDirectory); try { std::string writtenfilename = serializer->Serialize(); element->SetAttribute("file", writtenfilename.c_str()); - error = false; + + if (!writtenfilename.empty()) + error = false; } catch (std::exception &e) { MITK_ERROR << "Serializer " << serializer->GetNameOfClass() << " failed: " << e.what(); } break; } } element->SetAttribute("UID", data->GetUID().c_str()); return element; } tinyxml2::XMLElement *mitk::SceneIO::SavePropertyList(tinyxml2::XMLDocument &doc, PropertyList *propertyList, const std::string &filenamehint) { assert(propertyList); // - TODO what to do about shared properties (same object in two lists or behind several keys)? auto *element = doc.NewElement("properties"); // construct name of serializer class PropertyListSerializer::Pointer serializer = PropertyListSerializer::New(); serializer->SetPropertyList(propertyList); serializer->SetFilenameHint(filenamehint); std::string defaultLocale_WorkingDirectory = Poco::Path::transcode( m_WorkingDirectory ); serializer->SetWorkingDirectory(defaultLocale_WorkingDirectory); try { std::string writtenfilename = serializer->Serialize(); element->SetAttribute("file", writtenfilename.c_str()); PropertyList::Pointer failedProperties = serializer->GetFailedProperties(); if (failedProperties.IsNotNull()) { // move failed properties to global list m_FailedProperties->ConcatenatePropertyList(failedProperties, true); } } catch (std::exception &e) { MITK_ERROR << "Serializer " << serializer->GetNameOfClass() << " failed: " << e.what(); } return element; } const mitk::SceneIO::FailedBaseDataListType *mitk::SceneIO::GetFailedNodes() { return m_FailedNodes.GetPointer(); } const mitk::PropertyList *mitk::SceneIO::GetFailedProperties() { return m_FailedProperties; } void mitk::SceneIO::OnUnzipError(const void * /*pSender*/, std::pair &info) { ++m_UnzipErrors; MITK_ERROR << "Error while unzipping: " << info.second; } void mitk::SceneIO::OnUnzipOk(const void * /*pSender*/, std::pair & /*info*/) { // MITK_INFO << "Unzipped ok: " << info.second.toString(); } diff --git a/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp b/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp index c28b3e54ce..9337bdf315 100644 --- a/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp +++ b/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp @@ -1,463 +1,468 @@ /*============================================================================ 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 "mitkSceneReaderV1.h" #include "Poco/Path.h" #include "mitkBaseRenderer.h" #include "mitkIOUtil.h" #include "mitkProgressBar.h" #include "mitkPropertyListDeserializer.h" #include "mitkSerializerMacros.h" #include #include #include #include #include MITK_REGISTER_SERIALIZER(SceneReaderV1) namespace { typedef std::pair> NodesAndParentsPair; bool NodeSortByLayerIsLessThan(const NodesAndParentsPair &left, const NodesAndParentsPair &right) { if (left.first.IsNotNull() && right.first.IsNotNull()) { int leftLayer; int rightLayer; if (left.first->GetIntProperty("layer", leftLayer) && right.first->GetIntProperty("layer", rightLayer)) { return leftLayer < rightLayer; } else { // fall back to name sort return left.first->GetName() < right.first->GetName(); } } // in all other cases, fall back to stupid pointer comparison // this is not reasonable but at least answers the sorting // question clearly return left.first.GetPointer() < right.first.GetPointer(); } // This is a workaround until we are able to save time-related information in an // actual file format of surfaces. void ApplyProportionalTimeGeometryProperties(mitk::BaseData* data) { auto* geometry = dynamic_cast(data->GetTimeGeometry()); if (nullptr == geometry) return; auto properties = data->GetPropertyList(); float value = 0.0f; if (properties->GetFloatProperty("ProportionalTimeGeometry.FirstTimePoint", value)) { if (value == -std::numeric_limits::infinity()) value = std::numeric_limits::lowest(); geometry->SetFirstTimePoint(value); } if (properties->GetFloatProperty("ProportionalTimeGeometry.StepDuration", value)) geometry->SetStepDuration(value); } mitk::PropertyList::Pointer DeserializeProperties(const tinyxml2::XMLElement *propertiesElement, const std::filesystem::path& basePath) { if (propertiesElement == nullptr) return nullptr; std::filesystem::path path(propertiesElement->Attribute("file")); if (path.empty()) return nullptr; if (!basePath.empty()) path = basePath / path; auto deserializer = mitk::PropertyListDeserializer::New(); deserializer->SetFilename(path.string()); deserializer->Deserialize(); return deserializer->GetOutput(); } } bool mitk::SceneReaderV1::LoadScene(tinyxml2::XMLDocument &document, const std::string &workingDirectory, DataStorage *storage) { assert(storage); bool error(false); // TODO prepare to detect errors (such as cycles) from wrongly written or edited xml files // Get number of elements to initialze progress bar // 1. if there is a element, // - construct a name for the appropriate serializer // - try to instantiate this serializer via itk object factory // - if serializer could be created, use it to read the file into a BaseData object // - if successful, call the new node's SetData(..) // create a node for the tag "data" and test if node was created typedef std::vector DataNodeVector; DataNodeVector DataNodes; unsigned int listSize = 0; for (auto *element = document.FirstChildElement("node"); element != nullptr; element = element->NextSiblingElement("node")) { ++listSize; } ProgressBar::GetInstance()->AddStepsToDo(listSize * 2); // Deserialize base data properties before reading the actual data to be // able to provide them as read-only meta data to the data reader. std::map baseDataPropertyLists; for (auto *nodeElement = document.FirstChildElement("node"); nodeElement != nullptr; nodeElement = nodeElement->NextSiblingElement("node")) { const auto *uid = nodeElement->Attribute("UID"); if (uid == nullptr) continue; auto *dataElement = nodeElement->FirstChildElement("data"); if (dataElement != nullptr) { auto properties = DeserializeProperties(dataElement->FirstChildElement("properties"), workingDirectory); if (properties.IsNotNull()) baseDataPropertyLists[uid] = properties; } } for (auto *element = document.FirstChildElement("node"); element != nullptr; element = element->NextSiblingElement("node")) { mitk::PropertyList* properties = nullptr; const auto *uid = element->Attribute("UID"); if (uid != nullptr) { auto iter = baseDataPropertyLists.find(uid); if (iter != baseDataPropertyLists.end()) properties = iter->second; } const auto *dataElement = element->FirstChildElement("data"); auto dataNode = this->LoadBaseDataFromDataTag(dataElement, properties, workingDirectory, error); if (dataNode.IsNull()) continue; auto* baseData = dataNode->GetData(); if (baseData != nullptr && properties != nullptr) { baseData->SetPropertyList(properties); ApplyProportionalTimeGeometryProperties(baseData); } DataNodes.push_back(dataNode); ProgressBar::GetInstance()->Progress(); } // iterate all nodes // first level nodes should be elements auto nit = DataNodes.begin(); for (auto *element = document.FirstChildElement("node"); element != nullptr || nit != DataNodes.end(); element = element->NextSiblingElement("node"), ++nit) { mitk::DataNode::Pointer node = *nit; // 1. check child nodes const char *uida = element->Attribute("UID"); std::string uid(""); if (uida) { uid = uida; m_NodeForID[uid] = node.GetPointer(); m_IDForNode[node.GetPointer()] = uid; } else { MITK_ERROR << "No UID found for current node. Node will have no parents."; error = true; } // 2. if there are nodes, // - instantiate the appropriate PropertyListDeSerializer // - use them to construct PropertyList objects // - add these properties to the node (if necessary, use renderwindow name) bool success = DecorateNodeWithProperties(node, element, workingDirectory); if (!success) { MITK_ERROR << "Could not load properties for node."; error = true; } // remember node for later adding to DataStorage m_OrderedNodePairs.push_back(std::make_pair(node, std::list())); // 3. if there are elements, remember parent objects for (auto *source = element->FirstChildElement("source"); source != nullptr; source = source->NextSiblingElement("source")) { const char *sourceUID = source->Attribute("UID"); if (sourceUID) { m_OrderedNodePairs.back().second.push_back(std::string(sourceUID)); } } ProgressBar::GetInstance()->Progress(); } // end for all // sort our nodes by their "layer" property // (to be inserted in that order) m_OrderedNodePairs.sort(&NodeSortByLayerIsLessThan); // remove all unknown parent UIDs for (auto nodesIter = m_OrderedNodePairs.begin(); nodesIter != m_OrderedNodePairs.end(); ++nodesIter) { for (auto parentsIter = nodesIter->second.begin(); parentsIter != nodesIter->second.end();) { if (m_NodeForID.find(*parentsIter) == m_NodeForID.end()) { parentsIter = nodesIter->second.erase(parentsIter); MITK_WARN << "Found a DataNode with unknown parents. Will add it to DataStorage without any parent objects."; error = true; } else { ++parentsIter; } } } // repeat the following loop ... // ... for all created nodes unsigned int lastMapSize(0); while (lastMapSize != m_OrderedNodePairs .size()) // this is to prevent infinite loops; each iteration must at least add one node to DataStorage { lastMapSize = m_OrderedNodePairs.size(); // iterate (layer) ordered nodes backwards // we insert the highest layers first for (auto nodesIter = m_OrderedNodePairs.begin(); nodesIter != m_OrderedNodePairs.end(); ++nodesIter) { bool addThisNode(true); // if any parent node is not yet in DataStorage, skip node for now and check later for (auto parentsIter = nodesIter->second.begin(); parentsIter != nodesIter->second.end(); ++parentsIter) { if (!storage->Exists(m_NodeForID[*parentsIter])) { addThisNode = false; break; } } if (addThisNode) { DataStorage::SetOfObjects::Pointer parents = DataStorage::SetOfObjects::New(); for (auto parentsIter = nodesIter->second.begin(); parentsIter != nodesIter->second.end(); ++parentsIter) { parents->push_back(m_NodeForID[*parentsIter]); } // if all parents are found in datastorage (or are unknown), add node to DataStorage storage->Add(nodesIter->first, parents); // remove this node from m_OrderedNodePairs m_OrderedNodePairs.erase(nodesIter); // break this for loop because iterators are probably invalid break; } } } // All nodes that are still in m_OrderedNodePairs at this point are not part of a proper directed graph structure. // We'll add such nodes without any parent information. for (auto nodesIter = m_OrderedNodePairs.begin(); nodesIter != m_OrderedNodePairs.end(); ++nodesIter) { storage->Add(nodesIter->first); MITK_WARN << "Encountered node that is not part of a directed graph structure. Will be added to DataStorage " "without parents."; error = true; } return !error; } mitk::DataNode::Pointer mitk::SceneReaderV1::LoadBaseDataFromDataTag(const tinyxml2::XMLElement *dataElement, const PropertyList *properties, const std::string &workingDirectory, bool &error) { DataNode::Pointer node; if (dataElement) { const char *filename = dataElement->Attribute("file"); if (filename && strlen(filename) != 0) { try { auto baseData = IOUtil::Load(workingDirectory + Poco::Path::separator() + filename, properties); node = DataNode::New(); node->SetData(baseData); } catch (std::exception &e) { MITK_ERROR << "Error during attempt to read '" << filename << "'. Exception says: " << e.what(); error = true; } if (node.IsNull()) { MITK_ERROR << "Error during attempt to read '" << filename << "'. Factory returned nullptr object."; error = true; } } + else + { + MITK_ERROR << "File attribute of data tag is empty!"; + error = true; + } const char* dataUID = dataElement->Attribute("UID"); if (!error && dataUID != nullptr) { UIDManipulator manip(node->GetData()); manip.SetUID(dataUID); } } // in case there was no element we create a new empty node (for appending a propertylist later) if (node.IsNull()) { node = DataNode::New(); } return node; } void mitk::SceneReaderV1::ClearNodePropertyListWithExceptions(DataNode &node, PropertyList &propertyList) { // Basically call propertyList.Clear(), but implement exceptions (see bug 19354) BaseData *data = node.GetData(); PropertyList::Pointer propertiesToKeep = PropertyList::New(); if (dynamic_cast(data)) { /* Older scene files (before changes of bug 17547) could contain a RenderingMode property with value "LevelWindow_Color". Since bug 17547 this value has been removed and replaced by the default value LookupTable_LevelWindow_Color. This new default value does only result in "black-to-white" CT images (or others) if there is a corresponding lookup table. Such a lookup table is provided as a default value by the Image mapper. Since that value was never present in older scene files, we do well in not removing the new default value here. Otherwise the mapper would fall back to another default which is all the colors of the rainbow :-( */ BaseProperty::Pointer lutProperty = propertyList.GetProperty("LookupTable"); propertiesToKeep->SetProperty("LookupTable", lutProperty); /* Older scene files (before changes of T14807) may contain multi-component images without the "Image.Displayed Component" property. As the treatment as multi-component image and the corresponding visualization options hinges on that property we should not delete it, if it was added by the mapper. This is a fix for the issue reported in T19919. */ BaseProperty::Pointer compProperty = propertyList.GetProperty("Image.Displayed Component"); if (compProperty.IsNotNull()) { propertiesToKeep->SetProperty("Image.Displayed Component", compProperty); } } propertyList.Clear(); propertyList.ConcatenatePropertyList(propertiesToKeep); } bool mitk::SceneReaderV1::DecorateNodeWithProperties(DataNode *node, const tinyxml2::XMLElement *nodeElement, const std::string &workingDirectory) { assert(node); assert(nodeElement); bool error(false); for (auto *properties = nodeElement->FirstChildElement("properties"); properties != nullptr; properties = properties->NextSiblingElement("properties")) { const char *propertiesfilea(properties->Attribute("file")); std::string propertiesfile(propertiesfilea ? propertiesfilea : ""); const char *renderwindowa(properties->Attribute("renderwindow")); std::string renderwindow(renderwindowa ? renderwindowa : ""); PropertyList::Pointer propertyList = node->GetPropertyList(renderwindow); // DataNode implementation always returns a propertylist ClearNodePropertyListWithExceptions(*node, *propertyList); // use deserializer to construct new properties PropertyListDeserializer::Pointer deserializer = PropertyListDeserializer::New(); deserializer->SetFilename(workingDirectory + Poco::Path::separator() + propertiesfile); bool success = deserializer->Deserialize(); error |= !success; PropertyList::Pointer readProperties = deserializer->GetOutput(); if (readProperties.IsNotNull()) { propertyList->ConcatenatePropertyList(readProperties, true); // true = replace } else { MITK_ERROR << "Property list reader did not return a property list. This is an implementation error. Please tell " "your developer."; error = true; } } return !error; }