diff --git a/Core/Code/DataManagement/mitkCommon.h b/Core/Code/DataManagement/mitkCommon.h index 12790d651b..a04871dcf6 100644 --- a/Core/Code/DataManagement/mitkCommon.h +++ b/Core/Code/DataManagement/mitkCommon.h @@ -1,123 +1,130 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #ifndef MITK_COMMON_H_DEFINED #define MITK_COMMON_H_DEFINED #ifdef _MSC_VER // This warns about truncation to 255 characters in debug/browse info #pragma warning (disable : 4786) #pragma warning (disable : 4068 ) /* disable unknown pragma warnings */ #endif //add only those headers here that are really necessary for all classes! #include "itkObject.h" #include "mitkConfig.h" #include "mitkLogMacros.h" #ifndef MITK_UNMANGLE_IPPIC #define mitkIpPicDescriptor mitkIpPicDescriptor #endif typedef unsigned int MapperSlotId; #define mitkClassMacro(className,SuperClassName) \ typedef className Self; \ typedef SuperClassName Superclass; \ typedef itk::SmartPointer Pointer; \ typedef itk::SmartPointer ConstPointer; \ itkTypeMacro(className,SuperClassName) /** * Macro for Constructors with one parameter for classes derived from itk::Lightobject **/ #define mitkNewMacro1Param(classname,type) \ static Pointer New(type _arg) \ { \ Pointer smartPtr = new classname ( _arg ); \ smartPtr->UnRegister(); \ return smartPtr; \ } \ /** * Macro for Constructors with two parameters for classes derived from itk::Lightobject **/ #define mitkNewMacro2Param(classname,typea,typeb) \ static Pointer New(typea _arga, typeb _argb) \ { \ Pointer smartPtr = new classname ( _arga, _argb ); \ smartPtr->UnRegister(); \ return smartPtr; \ } \ /** * Macro for Constructors with three parameters for classes derived from itk::Lightobject **/ #define mitkNewMacro3Param(classname,typea,typeb,typec) \ static Pointer New(typea _arga, typeb _argb, typec _argc) \ { \ Pointer smartPtr = new classname ( _arga, _argb, _argc ); \ smartPtr->UnRegister(); \ return smartPtr; \ } \ /** * Macro for Constructors with three parameters for classes derived from itk::Lightobject **/ #define mitkNewMacro4Param(classname,typea,typeb,typec,typed) \ static Pointer New(typea _arga, typeb _argb, typec _argc, typed _argd) \ { \ Pointer smartPtr = new classname ( _arga, _argb, _argc, _argd ); \ smartPtr->UnRegister(); \ return smartPtr; \ } \ /** Get a smart const pointer to an object. Creates the member * Get"name"() (e.g., GetPoints()). */ #define mitkGetObjectMacroConst(name,type) \ virtual type * Get##name () const \ { \ itkDebugMacro("returning " #name " address " << this->m_##name ); \ return this->m_##name.GetPointer(); \ } +#define mitkCloneMacro(classname) \ + virtual Pointer Clone() const \ +{ \ + Pointer smartPtr = new classname(*this); \ + return smartPtr; \ +} + /** provide a macro for adding MS specific __declspec(dllexport/-import) * to classes. * This is needed for the export of symbols, when you build a DLL. Then write * * class MITK_EXPORT ClassName : public SomeClass {}; */ #if defined(WIN32) #ifdef mitkCore_EXPORTS #define MITK_CORE_EXPORT __declspec(dllexport) #else #define MITK_CORE_EXPORT __declspec(dllimport) #endif #ifdef Qmitk_EXPORTS #define QMITK_EXPORT __declspec(dllexport) #else #define QMITK_EXPORT __declspec(dllimport) #endif #else #define MITK_CORE_EXPORT #define QMITK_EXPORT #endif // legacy support for designer plugin #define MITK_EXPORT #endif diff --git a/Core/Code/DataManagement/mitkSurface.cpp b/Core/Code/DataManagement/mitkSurface.cpp index 1af88fc837..cc8eeb0dcf 100644 --- a/Core/Code/DataManagement/mitkSurface.cpp +++ b/Core/Code/DataManagement/mitkSurface.cpp @@ -1,362 +1,375 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #include "mitkSurface.h" #include "mitkInteractionConst.h" #include "mitkSurfaceOperation.h" #include +#include "vtkSmartPointer.h" #include mitk::Surface::Surface() : m_CalculateBoundingBox( false ) { this->InitializeEmpty(); } +mitk::Surface::Surface(const mitk::Surface& other) : m_CalculateBoundingBox(other.m_CalculateBoundingBox), m_RequestedRegion(other.m_RequestedRegion), +m_LargestPossibleRegion(other.m_LargestPossibleRegion) +{ + m_PolyDataSeries = std::vector(); + for ( VTKPolyDataSeries::const_iterator it = other.m_PolyDataSeries.begin(); it != other.m_PolyDataSeries.end(); ++it ) + { + vtkSmartPointer poly = vtkSmartPointer::New(); + poly->DeepCopy(*it); + m_PolyDataSeries.push_back(poly.GetPointer()); + } +} + mitk::Surface::~Surface() { this->ClearData(); } void mitk::Surface::ClearData() { for ( VTKPolyDataSeries::iterator it = m_PolyDataSeries.begin(); it != m_PolyDataSeries.end(); ++it ) { if ( ( *it ) != NULL ) ( *it )->Delete(); } m_PolyDataSeries.clear(); Superclass::ClearData(); } void mitk::Surface::InitializeEmpty() { vtkPolyData* pdnull = NULL; m_PolyDataSeries.resize( 1, pdnull ); Superclass::InitializeTimeSlicedGeometry(1); m_Initialized = true; } void mitk::Surface::SetVtkPolyData( vtkPolyData* polydata, unsigned int t ) { // Adapt the size of the data vector if necessary this->Expand( t+1 ); if(m_PolyDataSeries[ t ] != NULL) { if ( m_PolyDataSeries[ t ] == polydata ) return; // we do not need the reference on the object any longer m_PolyDataSeries[ t ]->Delete(); } m_PolyDataSeries[ t ] = polydata; // call m_VtkPolyData->Register(NULL) to tell // the reference counting that we want to keep a // reference on the object if(m_PolyDataSeries[ t ] != NULL) { m_PolyDataSeries[ t ]->Register( NULL ); } this->Modified(); m_CalculateBoundingBox = true; } bool mitk::Surface::IsEmpty(unsigned int t) const { if(!IsInitialized()) return false; vtkPolyData* polydata = const_cast(this)->GetVtkPolyData(t); return (polydata == NULL) || ( (polydata->GetNumberOfVerts() <= 0) && (polydata->GetNumberOfPolys() <= 0) && (polydata->GetNumberOfStrips() <= 0) && (polydata->GetNumberOfLines() <= 0) ); } vtkPolyData* mitk::Surface::GetVtkPolyData( unsigned int t ) { if ( t < m_PolyDataSeries.size() ) { vtkPolyData* polydata = m_PolyDataSeries[ t ]; if((polydata==NULL) && (GetSource().GetPointer()!=NULL)) { RegionType requestedregion; requestedregion.SetIndex(3, t); requestedregion.SetSize(3, 1); SetRequestedRegion(&requestedregion); GetSource()->Update(); } polydata = m_PolyDataSeries[ t ]; return polydata; } else return NULL; } void mitk::Surface::UpdateOutputInformation() { if ( this->GetSource() ) { this->GetSource()->UpdateOutputInformation(); } if ( ( m_CalculateBoundingBox ) && ( m_PolyDataSeries.size() > 0 ) ) CalculateBoundingBox(); else GetTimeSlicedGeometry()->UpdateInformation(); } void mitk::Surface::CalculateBoundingBox() { // // first make sure, that the associated time sliced geometry has // the same number of geometry 3d's as vtkPolyDatas are present // mitk::TimeSlicedGeometry* timeGeometry = GetTimeSlicedGeometry(); if ( timeGeometry->GetTimeSteps() != m_PolyDataSeries.size() ) { itkExceptionMacro(<<"timeGeometry->GetTimeSteps() != m_PolyDataSeries.size() -- use Initialize(timeSteps) with correct number of timeSteps!"); } // // Iterate over the vtkPolyDatas and update the Geometry // information of each of the items. // for ( unsigned int i = 0 ; i < m_PolyDataSeries.size() ; ++i ) { vtkPolyData* polyData = m_PolyDataSeries[ i ]; vtkFloatingPointType bounds[ ] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; if ( ( polyData != NULL ) && ( polyData->GetNumberOfPoints() > 0 ) ) { polyData->Update(); polyData->ComputeBounds(); polyData->GetBounds( bounds ); } mitk::Geometry3D::Pointer g3d = timeGeometry->GetGeometry3D( i ); assert( g3d.IsNotNull() ); g3d->SetFloatBounds( bounds ); } timeGeometry->UpdateInformation(); mitk::BoundingBox::Pointer bb = const_cast( timeGeometry->GetBoundingBox() ); itkDebugMacro( << "boundingbox min: "<< bb->GetMinimum()); itkDebugMacro( << "boundingbox max: "<< bb->GetMaximum()); 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(((RegionType::IndexValueType)m_PolyDataSeries.size())=0) && (m_RequestedRegion.GetIndex(3)+m_RequestedRegion.GetSize(3)<=m_PolyDataSeries.size()) ) return true; return false; } void mitk::Surface::SetRequestedRegion( itk::DataObject *data ) { mitk::Surface *surfaceData; surfaceData = dynamic_cast(data); if (surfaceData) { m_RequestedRegion = surfaceData->GetRequestedRegion(); } else { // pointer could not be cast back down itkExceptionMacro( << "mitk::Surface::SetRequestedRegion(DataObject*) cannot cast " << typeid(data).name() << " to " << typeid(Surface*).name() ); } } void mitk::Surface::SetRequestedRegion(Surface::RegionType *region) //by arin { if(region!=NULL) { m_RequestedRegion = *region; } else { // pointer could not be cast back down itkExceptionMacro( << "mitk::Surface::SetRequestedRegion(Surface::RegionType*) cannot cast " << typeid(region).name() << " to " << typeid(Surface*).name() ); } } void mitk::Surface::CopyInformation( const itk::DataObject * data) { Superclass::CopyInformation( data ); const mitk::Surface* surfaceData; surfaceData = dynamic_cast( data ); if ( surfaceData ) { m_LargestPossibleRegion = surfaceData->GetLargestPossibleRegion(); } else { // pointer could not be cast back down itkExceptionMacro( << "mitk::Surface::CopyInformation(const DataObject *data) cannot cast " << typeid(data).name() << " to " << typeid(surfaceData).name() ); } } void mitk::Surface::Update() { if ( GetSource() == NULL ) { for ( VTKPolyDataSeries::iterator it = m_PolyDataSeries.begin() ; it != m_PolyDataSeries.end() ; ++it ) { if ( ( *it ) != NULL ) ( *it )->Update(); } } Superclass::Update(); } void mitk::Surface::Expand( unsigned int timeSteps ) { // check if the vector is long enough to contain the new element // at the given position. If not, expand it with sufficient zero-filled elements. if ( timeSteps > m_PolyDataSeries.size() ) { Superclass::Expand( timeSteps ); vtkPolyData* pdnull = NULL; m_PolyDataSeries.resize( timeSteps, pdnull ); m_CalculateBoundingBox = true; } } void mitk::Surface::ExecuteOperation(Operation *operation) { switch ( operation->GetOperationType() ) { case OpSURFACECHANGED: mitk::SurfaceOperation* surfOp = dynamic_cast(operation); if( ! surfOp ) break; unsigned int time = surfOp->GetTimeStep(); if(m_PolyDataSeries[ time ] != NULL) { vtkPolyData* updatePoly = surfOp->GetVtkPolyData(); if( updatePoly ){ this->SetVtkPolyData( updatePoly, time ); this->CalculateBoundingBox(); } } break; } this->Modified(); } unsigned int mitk::Surface::GetSizeOfPolyDataSeries() const { return m_PolyDataSeries.size(); } void mitk::Surface::Graft( const DataObject* data ) { const Self* surface; try { surface = dynamic_cast( data ); } catch(...) { itkExceptionMacro( << "mitk::Surface::Graft cannot cast " << typeid(data).name() << " to " << typeid(const Self *).name() ); return; } if(!surface) { // pointer could not be cast back down itkExceptionMacro( << "mitk::Surface::Graft cannot cast " << typeid(data).name() << " to " << typeid(const Self *).name() ); return; } this->CopyInformation( data ); //clear list of PolyData's m_PolyDataSeries.clear(); // do copy for (unsigned int i=0; iGetSizeOfPolyDataSeries(); i++) { m_PolyDataSeries.push_back(vtkPolyData::New()); m_PolyDataSeries.back()->DeepCopy( const_cast(surface)->GetVtkPolyData( i ) ); //CopyStructure( const_cast(surface)->GetVtkPolyData( i ) ); } } void mitk::Surface::PrintSelf( std::ostream& os, itk::Indent indent ) const { Superclass::PrintSelf(os, indent); os << indent << "\nNumber PolyDatas: " << m_PolyDataSeries.size() << "\n"; unsigned int count = 0; for (VTKPolyDataSeries::const_iterator it = m_PolyDataSeries.begin(); it != m_PolyDataSeries.end(); ++it) { vtkPolyData* pd = *it; if(pd != NULL) { os << "\n"; os << indent << "PolyData at time step " << count << ". \n"; os << indent << "Number of cells " << pd->GetNumberOfCells() << ": \n"; os << indent << "Number of points " << pd->GetNumberOfPoints() << ": \n\n"; os << indent << "VTKPolyData : \n"; pd->Print(os); } else os << indent << "\nEmpty PolyData at time step " << count << ".\n"; count++; } } \ No newline at end of file diff --git a/Core/Code/DataManagement/mitkSurface.h b/Core/Code/DataManagement/mitkSurface.h index 0982423176..805edf389e 100644 --- a/Core/Code/DataManagement/mitkSurface.h +++ b/Core/Code/DataManagement/mitkSurface.h @@ -1,117 +1,120 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #ifndef MITKSURFACEDATA_H_HEADER_INCLUDED #define MITKSURFACEDATA_H_HEADER_INCLUDED #include "mitkBaseData.h" #include "itkImageRegion.h" class vtkPolyData; namespace mitk { //##Documentation //## @brief Class for storing surfaces (vtkPolyData) //## @ingroup Data class MITK_CORE_EXPORT Surface : public BaseData { protected: public: // not yet the best chioce of a region-type for surfaces, but it works for the time being typedef itk::ImageRegion< 5 > RegionType; mitkClassMacro(Surface, BaseData); itkNewMacro(Self); + mitkCloneMacro(Surface); + virtual void SetVtkPolyData(vtkPolyData* polydata, unsigned int t = 0); virtual vtkPolyData* GetVtkPolyData(unsigned int t = 0); virtual void UpdateOutputInformation(); virtual void SetRequestedRegionToLargestPossibleRegion(); virtual bool RequestedRegionIsOutsideOfTheBufferedRegion(); virtual bool VerifyRequestedRegion(); virtual void SetRequestedRegion(itk::DataObject *data); virtual void SetRequestedRegion(Surface::RegionType *region); virtual void CopyInformation(const itk::DataObject *data); virtual bool IsEmpty(unsigned int t) const; unsigned int GetSizeOfPolyDataSeries() const; virtual void Update(); virtual void Expand( unsigned int timeSteps = 1 ); virtual void Graft( const DataObject* data ); const RegionType& GetLargestPossibleRegion() const { m_LargestPossibleRegion.SetIndex(3, 0); m_LargestPossibleRegion.SetSize(3, GetTimeSlicedGeometry()->GetTimeSteps()); return m_LargestPossibleRegion; } //##Documentation //## Get the region object that defines the size and starting index //## for the region of the image requested (i.e., the region of the //## image to be operated on by a filter). virtual const RegionType& GetRequestedRegion() const { return m_RequestedRegion; } void CalculateBoundingBox(); virtual void PrintSelf( std::ostream& os, itk::Indent indent ) const; virtual void ExecuteOperation(Operation *operation); protected: typedef std::vector< vtkPolyData* > VTKPolyDataSeries; Surface(); + Surface(const Surface& other); virtual ~Surface(); virtual void ClearData(); virtual void InitializeEmpty(); VTKPolyDataSeries m_PolyDataSeries; mutable RegionType m_LargestPossibleRegion; RegionType m_RequestedRegion; bool m_CalculateBoundingBox; }; } // namespace mitk #endif /* MITKSURFACEDATA_H_HEADER_INCLUDED */ diff --git a/Core/Code/Testing/mitkSurfaceTest.cpp b/Core/Code/Testing/mitkSurfaceTest.cpp index ae00eee7eb..c3a8c45b20 100644 --- a/Core/Code/Testing/mitkSurfaceTest.cpp +++ b/Core/Code/Testing/mitkSurfaceTest.cpp @@ -1,233 +1,230 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #include "mitkSurface.h" #include "mitkCommon.h" #include "mitkVector.h" +#include "mitkTestingMacros.h" #include "mitkTimeSlicedGeometry.h" #include "vtkPolyData.h" #include "vtkSphereSource.h" #include int mitkSurfaceTest(int /*argc*/, char* /*argv*/[]) { - mitk::Surface::Pointer surface; - std::cout << "Testing mitk::Surface::New(): "; - surface = mitk::Surface::New(); - if (surface.IsNull()) { - std::cout<<"[FAILED]"<PrintSelf(std::cout, 0); - std::cout<<"[PASSED]"<PrintSelf( s, 0); + + MITK_TEST_CONDITION_REQUIRED(s.width()!= 0, "Testing PrintSelf method!"); vtkSphereSource* sphereSource = vtkSphereSource::New(); sphereSource->SetCenter(0,0,0); sphereSource->SetRadius(5.0); sphereSource->SetThetaResolution(10); sphereSource->SetPhiResolution(10); sphereSource->Update(); vtkPolyData* polys = sphereSource->GetOutput(); + MITK_TEST_CONDITION_REQUIRED(surface->GetVtkPolyData() == NULL, "Testing initial state of vtkPolyData"); surface->SetVtkPolyData( polys ); sphereSource->Delete(); - std::cout << "Testing mitk::Surface::SetVtkPolyData(): "; - if (surface->GetVtkPolyData() == NULL ) { - std::cout<<"[FAILED]"<GetVtkPolyData()!= NULL, "Testing set vtkPolyData"); + //std::cout << "Testing mitk::Surface::SetVtkPolyData(): "; + //if (surface->GetVtkPolyData() == NULL ) { + // std::cout<<"[FAILED]"<ComputeBounds(); polys->GetBounds( bounds ); std::cout << "Testing GetBoundingBox() "; surface->UpdateOutputInformation(); surface->SetRequestedRegionToLargestPossibleRegion(); // mitk::BoundingBox bb = const_cast( mitk::BoundingBox* bb = const_cast(surface->GetGeometry()->GetBoundingBox()); mitk::BoundingBox::BoundsArrayType surfBounds = bb->GetBounds(); if ( bounds[0] != surfBounds[0] || bounds[1] != surfBounds[1] || bounds[2] != surfBounds[2] || bounds[3] != surfBounds[3] || bounds[4] != surfBounds[4] || bounds[5] != surfBounds[5] ) { std::cout<<"[FAILED]"<Expand(5); surface->Update(); surface->SetRequestedRegionToLargestPossibleRegion(); mitk::Surface::RegionType requestedRegion = surface->GetRequestedRegion(); if ( requestedRegion.GetSize(3) != 5 ) { std::cout<<"[FAILED]"<GetTimeSlicedGeometry()->GetGeometry3D(0); //geometry->GetVtkTransform()->Identity(); //geometry->GetVtkTransform()->Translate(10,10,10); //geometry->TransferVtkToItkTransform(); //mitk::TimeSlicedGeometry* timeSlicedGeometry = surface->GetTimeSlicedGeometry(); //timeSlicedGeometry->InitializeEvenlyTimed(geometry, 5); vtkFloatingPointType bounds[5][6]; for (int i=0;i<5;i++) { vtkSphereSource* sphereSource = vtkSphereSource::New(); sphereSource->SetCenter(0,0,0); sphereSource->SetRadius(1.0 * (i+1.0)); sphereSource->SetThetaResolution(10); sphereSource->SetPhiResolution(10); sphereSource->Update(); sphereSource->GetOutput()->ComputeBounds(); sphereSource->GetOutput()->GetBounds( bounds[i] ); surface->SetVtkPolyData( sphereSource->GetOutput(),i ); sphereSource->Delete(); } surface->UpdateOutputInformation(); surface->SetRequestedRegionToLargestPossibleRegion(); bool passed = true; for (int i=0;i<5;i++) { mitk::BoundingBox::BoundsArrayType surfBounds = (const_cast(surface->GetTimeSlicedGeometry()->GetGeometry3D(i)->GetBoundingBox()))->GetBounds(); if ( bounds[i][0] != surfBounds[0] || bounds[i][1] != surfBounds[1] || bounds[i][2] != surfBounds[2] || bounds[i][3] != surfBounds[3] || bounds[i][4] != surfBounds[4] || bounds[i][5] != surfBounds[5] ) { passed = false; break; } } if (!passed) { std::cout<<"[FAILED]"<GetUpdatedTimeSlicedGeometry(): \n"; const mitk::TimeSlicedGeometry* inputTimeGeometry = surface->GetUpdatedTimeSlicedGeometry(); int time = 3; int timestep=0; timestep = inputTimeGeometry->MSToTimeStep( time ); std::cout << "time: "<< time << std::endl; std::cout << "timestep: "<SetCenter(0,0,0); sphereSource->SetRadius( 100.0 ); sphereSource->SetThetaResolution(10); sphereSource->SetPhiResolution(10); sphereSource->Update(); surface->SetVtkPolyData( sphereSource->GetOutput(), 3 ); sphereSource->Delete(); inputTimeGeometry = surface->GetUpdatedTimeSlicedGeometry(); time = 3; timestep=0; timestep = inputTimeGeometry->MSToTimeStep( time ); std::cout << "time: "<< time << std::endl; std::cout << "timestep: "<GetTimeSteps(); mitk::Surface::Pointer dummy = mitk::Surface::New(); dummy->Graft(surface); std::cout << "polyData != NULL ??" << std::endl; if (dummy->GetVtkPolyData() == NULL) { std::cout<<"[FAILED]"<GetTimeSteps() << std::endl; if (dummy->GetTimeSteps() != numberoftimesteps) { std::cout<<"[FAILED]"<