diff --git a/Modules/IGT/Algorithms/mitkNavigationDataEvaluationFilter.cpp b/Modules/IGT/Algorithms/mitkNavigationDataEvaluationFilter.cpp index 4fcef91f9c..89ec5d87a7 100644 --- a/Modules/IGT/Algorithms/mitkNavigationDataEvaluationFilter.cpp +++ b/Modules/IGT/Algorithms/mitkNavigationDataEvaluationFilter.cpp @@ -1,314 +1,302 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkNavigationDataEvaluationFilter.h" #include #define _USE_MATH_DEFINES #include mitk::NavigationDataEvaluationFilter::NavigationDataEvaluationFilter() -: mitk::NavigationDataToNavigationDataFilter() + : mitk::NavigationDataToNavigationDataFilter() { - } - mitk::NavigationDataEvaluationFilter::~NavigationDataEvaluationFilter() { - } void mitk::NavigationDataEvaluationFilter::GenerateData() { + this->CreateOutputsForAllInputs(); // make sure that we have the same number of outputs as inputs + this->CreateMembersForAllInputs(); - this->CreateOutputsForAllInputs(); // make sure that we have the same number of outputs as inputs - this->CreateMembersForAllInputs(); - - /* update outputs with tracking data from tools */ - for (unsigned int i = 0; i < this->GetNumberOfOutputs() ; ++i) + /* update outputs with tracking data from tools */ + for (unsigned int i = 0; i < this->GetNumberOfOutputs(); ++i) + { + //first copy outputs to inputs + mitk::NavigationData* output = this->GetOutput(i); + assert(output); + const mitk::NavigationData* input = this->GetInput(i); + assert(input); + if (input->IsDataValid() == false) { output->SetDataValid(false); } + else { output->Graft(input); } + + //then save statistics + if (input->IsDataValid()) { - //first copy outputs to inputs - mitk::NavigationData* output = this->GetOutput(i); - assert(output); - const mitk::NavigationData* input = this->GetInput(i); - assert(input); - if (input->IsDataValid() == false) {output->SetDataValid(false);} - else {output->Graft(input);} - - //then save statistics - if(input->IsDataValid()) - { - m_LoggedPositions[i].push_back(input->GetPosition()); - m_LoggedQuaternions[i].push_back(input->GetOrientation()); - } - else - { - m_InvalidSamples[i]++; - } + m_LoggedPositions[i].push_back(input->GetPosition()); + m_LoggedQuaternions[i].push_back(input->GetOrientation()); } - + else + { + m_InvalidSamples[i]++; + } + } } void mitk::NavigationDataEvaluationFilter::CreateMembersForAllInputs() { - while(this->m_LoggedPositions.size() < this->GetNumberOfInputs()) - { - std::pair > newElement(m_LoggedPositions.size(),std::vector()); + while (this->m_LoggedPositions.size() < this->GetNumberOfInputs()) + { + std::pair > newElement(m_LoggedPositions.size(), std::vector()); m_LoggedPositions.insert(newElement); - } - while(this->m_LoggedQuaternions.size() < this->GetNumberOfInputs()) - { - std::pair > newElement(m_LoggedQuaternions.size(),std::vector()); + } + while (this->m_LoggedQuaternions.size() < this->GetNumberOfInputs()) + { + std::pair > newElement(m_LoggedQuaternions.size(), std::vector()); m_LoggedQuaternions.insert(newElement); - } - while(this->m_InvalidSamples.size() < this->GetNumberOfInputs()) - { - std::pair newElement(m_InvalidSamples.size(),0); + } + while (this->m_InvalidSamples.size() < this->GetNumberOfInputs()) + { + std::pair newElement(m_InvalidSamples.size(), 0); m_InvalidSamples.insert(newElement); - } - - + } } void mitk::NavigationDataEvaluationFilter::ResetStatistic() { -for (unsigned int i = 0; i < m_LoggedPositions.size(); i++) m_LoggedPositions[i] = std::vector(); -for (unsigned int i = 0; i < m_LoggedQuaternions.size(); i++) m_LoggedQuaternions[i] = std::vector(); -for (unsigned int i = 0; i < m_InvalidSamples.size(); i++) m_InvalidSamples[i] = 0; + for (unsigned int i = 0; i < m_LoggedPositions.size(); i++) m_LoggedPositions[i] = std::vector(); + for (unsigned int i = 0; i < m_LoggedQuaternions.size(); i++) m_LoggedQuaternions[i] = std::vector(); + for (unsigned int i = 0; i < m_InvalidSamples.size(); i++) m_InvalidSamples[i] = 0; } int mitk::NavigationDataEvaluationFilter::GetNumberOfAnalysedNavigationData(int input) { -return this->m_LoggedPositions[input].size(); + return this->m_LoggedPositions[input].size(); } - mitk::Point3D mitk::NavigationDataEvaluationFilter::GetPositionMean(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionMean(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionMean(); } mitk::Vector3D mitk::NavigationDataEvaluationFilter::GetPositionStandardDeviation(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionStandardDeviation(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionStandardDeviation(); } mitk::Vector3D mitk::NavigationDataEvaluationFilter::GetPositionSampleStandardDeviation(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionSampleStandardDeviation(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionSampleStandardDeviation(); } mitk::Quaternion mitk::NavigationDataEvaluationFilter::GetQuaternionMean(int input) { -return GetMean(m_LoggedQuaternions[input]); + return GetMean(m_LoggedQuaternions[input]); } mitk::Quaternion mitk::NavigationDataEvaluationFilter::GetQuaternionStandardDeviation(int input) { -mitk::Quaternion returnValue; -std::vector list1 = std::vector(); -std::vector list2 = std::vector(); -std::vector list3 = std::vector(); -std::vector list4 = std::vector(); -for (unsigned int i=0; i list1 = std::vector(); + std::vector list2 = std::vector(); + std::vector list3 = std::vector(); + std::vector list4 = std::vector(); + for (unsigned int i = 0; i < m_LoggedQuaternions[input].size(); i++) { - list1.push_back(m_LoggedQuaternions[input].at(i)[0]); - list2.push_back(m_LoggedQuaternions[input].at(i)[1]); - list3.push_back(m_LoggedQuaternions[input].at(i)[2]); - list4.push_back(m_LoggedQuaternions[input].at(i)[3]); + list1.push_back(m_LoggedQuaternions[input].at(i)[0]); + list2.push_back(m_LoggedQuaternions[input].at(i)[1]); + list3.push_back(m_LoggedQuaternions[input].at(i)[2]); + list4.push_back(m_LoggedQuaternions[input].at(i)[3]); } -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(); -returnValue[0] = myCalculator->GetStabw(list1); -returnValue[1] = myCalculator->GetStabw(list2); -returnValue[2] = myCalculator->GetStabw(list3); -returnValue[3] = myCalculator->GetStabw(list4); -return returnValue; + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(); + returnValue[0] = myCalculator->GetStabw(list1); + returnValue[1] = myCalculator->GetStabw(list2); + returnValue[2] = myCalculator->GetStabw(list3); + returnValue[3] = myCalculator->GetStabw(list4); + return returnValue; } mitk::Vector3D mitk::NavigationDataEvaluationFilter::GetEulerAnglesMean(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(QuaternionsToEulerAngles(m_LoggedQuaternions[input]))); -mitk::Vector3D returnValue; -returnValue[0] = myCalculator->GetPositionMean()[0]; -returnValue[1] = myCalculator->GetPositionMean()[1]; -returnValue[2] = myCalculator->GetPositionMean()[2]; -return returnValue; + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(QuaternionsToEulerAngles(m_LoggedQuaternions[input]))); + mitk::Vector3D returnValue; + returnValue[0] = myCalculator->GetPositionMean()[0]; + returnValue[1] = myCalculator->GetPositionMean()[1]; + returnValue[2] = myCalculator->GetPositionMean()[2]; + return returnValue; } double mitk::NavigationDataEvaluationFilter::GetEulerAnglesRMS(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(QuaternionsToEulerAngles(m_LoggedQuaternions[input]))); -return myCalculator->GetPositionErrorRMS(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(QuaternionsToEulerAngles(m_LoggedQuaternions[input]))); + return myCalculator->GetPositionErrorRMS(); } double mitk::NavigationDataEvaluationFilter::GetEulerAnglesRMSDegree(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(QuaternionsToEulerAnglesGrad(m_LoggedQuaternions[input]))); -return myCalculator->GetPositionErrorRMS(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(QuaternionsToEulerAnglesGrad(m_LoggedQuaternions[input]))); + return myCalculator->GetPositionErrorRMS(); } - - double mitk::NavigationDataEvaluationFilter::GetPositionErrorMean(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionErrorMean(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionErrorMean(); } double mitk::NavigationDataEvaluationFilter::GetPositionErrorStandardDeviation(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionErrorStandardDeviation(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionErrorStandardDeviation(); } double mitk::NavigationDataEvaluationFilter::GetPositionErrorSampleStandardDeviation(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionErrorSampleStandardDeviation(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionErrorSampleStandardDeviation(); } - double mitk::NavigationDataEvaluationFilter::GetPositionErrorRMS(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionErrorRMS(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionErrorRMS(); } double mitk::NavigationDataEvaluationFilter::GetPositionErrorMedian(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionErrorMedian(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionErrorMedian(); } double mitk::NavigationDataEvaluationFilter::GetPositionErrorMax(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionErrorMax(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionErrorMax(); } double mitk::NavigationDataEvaluationFilter::GetPositionErrorMin(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionErrorMin(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionErrorMin(); } int mitk::NavigationDataEvaluationFilter::GetNumberOfInvalidSamples(int input) { -return m_InvalidSamples[input]; + return m_InvalidSamples[input]; } double mitk::NavigationDataEvaluationFilter::GetPercentageOfInvalidSamples(int input) { -return (m_InvalidSamples[input]/(m_InvalidSamples[input]+((double)m_LoggedPositions[input].size())))*100.0; + return (m_InvalidSamples[input] / (m_InvalidSamples[input] + ((double)m_LoggedPositions[input].size())))*100.0; } mitk::Quaternion mitk::NavigationDataEvaluationFilter::GetMean(std::vector list) { -//calculate mean -mitk::Quaternion mean; -mean[0] = 0; -mean[1] = 0; -mean[2] = 0; -mean[3] = 0; - -for (unsigned int i=0; i pSet) { mitk::PointSet::Pointer returnValue = mitk::PointSet::New(); - for (unsigned int i=0; iInsertPoint(i,pSet.at(i)); + for (unsigned int i = 0; i < pSet.size(); i++) returnValue->InsertPoint(i, pSet.at(i)); return returnValue; } mitk::PointSet::Pointer mitk::NavigationDataEvaluationFilter::VectorToPointSet(std::vector pSet) { mitk::PointSet::Pointer returnValue = mitk::PointSet::New(); - for (unsigned int i=0; iInsertPoint(i,thisPoint); - } + returnValue->InsertPoint(i, thisPoint); + } return returnValue; } std::vector mitk::NavigationDataEvaluationFilter::QuaternionsToEulerAngles(std::vector quaterions) { std::vector returnValue = std::vector(); - for (unsigned int i=0; i mitk::NavigationDataEvaluationFilter::QuaternionsToEulerAnglesGrad(std::vector quaterions) { double PI = M_PI; std::vector returnValue = std::vector(); std::vector eulerAnglesRadians = QuaternionsToEulerAngles(quaterions); - for (unsigned int i=0; i #include #include #include "MitkIGTBaseExports.h" #include #include #include namespace mitk { class MITKIGTBASE_EXPORT StaticIGTHelperFunctions { public: /** Computes the angle in the plane perpendicular to the rotation axis of the two quaterions. * Therefore, a vector is rotated with the difference of both rotations and the angle is computed. - * In some cases you might want to defice this vector e.g., if working with 5D tools. By default - * the vector is defined along the Z-axis which works for Aurora 5D tools. + * In some cases you might want to define this vector e.g., if working with 5D tools. For NDI Aurora + * 5D tools you need to defined this vector along the Z-axis. * @return Returns the angle in degrees. **/ static double GetAngleBetweenTwoQuaterions(mitk::Quaternion a, mitk::Quaternion b, itk::Vector rotationVector); - /** Computes the angle in the plane perpendicular to the rotation axis of the two quaterions. - * Therefore, a vector is rotated with the difference of both rotations and the angle is computed. - * In some cases you might want to defice this vector e.g., if working with 5D tools. By default - * the vector is defined along the Z-axis which works for Aurora 5D tools. + /** Computes difference between two quaternions in degree, which is the minimum rotation angle between + * these two quaternions. + * The used formula is described here: https://fgiesen.wordpress.com/2013/01/07/small-note-on-quaternion-distance-metrics/ * @return Returns the angle in degrees. **/ static double GetAngleBetweenTwoQuaterions(mitk::Quaternion a, mitk::Quaternion b); /** Converts euler angles (in degrees) to a rotation matrix. */ static itk::Matrix ConvertEulerAnglesToRotationMatrix(double alpha, double beta, double gamma); /** @brief Computes the fiducial registration error out of two sets of fiducials. * The two sets must have the same size and the points must correspond to each other. * @param transform This transform is applied to the image fiducials before the FRE calculation if it is given. * @return Returns the FRE. Returns -1 if there was an error. */ static double ComputeFRE(mitk::PointSet::Pointer imageFiducials, mitk::PointSet::Pointer realWorldFiducials, vtkSmartPointer transform = NULL); }; } diff --git a/Modules/IGTBase/src/mitkStaticIGTHelperFunctions.cpp b/Modules/IGTBase/src/mitkStaticIGTHelperFunctions.cpp index 4f12a78c05..c9872b6ebd 100644 --- a/Modules/IGTBase/src/mitkStaticIGTHelperFunctions.cpp +++ b/Modules/IGTBase/src/mitkStaticIGTHelperFunctions.cpp @@ -1,115 +1,120 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include double mitk::StaticIGTHelperFunctions::GetAngleBetweenTwoQuaterions(mitk::Quaternion a, mitk::Quaternion b, itk::Vector rotationVector) { double returnValue; - itk::Vector point; //caution 5D-Tools: correct verctor along the tool axis is needed + itk::Vector point; //caution 5D-Tools: correct vector along the tool axis is needed point[0] = rotationVector[0]; point[1] = rotationVector[1]; point[2] = rotationVector[2]; //Quaternions used for rotations should alway be normalized, so just to be safe: a.normalize(); b.normalize(); itk::Matrix rotMatrixA; for(int i=0; i<3; i++) for(int j=0; j<3; j++) rotMatrixA[i][j] = a.rotation_matrix_transpose().transpose()[i][j]; itk::Matrix rotMatrixB; for(int i=0; i<3; i++) for(int j=0; j<3; j++) rotMatrixB[i][j] = b.rotation_matrix_transpose().transpose()[i][j]; itk::Vector pt1 = rotMatrixA * point; itk::Vector pt2 = rotMatrixB * point; returnValue = (pt1[0]*pt2[0]+pt1[1]*pt2[1]+pt1[2]*pt2[2]) / ( sqrt(pow(pt1[0],2.0)+pow(pt1[1],2.0)+pow(pt1[2],2.0)) * sqrt(pow(pt2[0],2.0)+pow(pt2[1],2.0)+pow(pt2[2],2.0))); returnValue = acos(returnValue) * 57.296; //57,296 = 180/Pi ; conversion to degrees return returnValue; } double mitk::StaticIGTHelperFunctions::GetAngleBetweenTwoQuaterions(mitk::Quaternion a, mitk::Quaternion b) { - itk::Vector rotationVector = itk::Vector(); - rotationVector[0] = 0; - rotationVector[1] = 0; - rotationVector[2] = 1000; - return GetAngleBetweenTwoQuaterions(a,b,rotationVector); + //formula returnValue = 2 * acos ( a b ) + //(+ normalization because we need unit quaternions) + //derivation from here: https://fgiesen.wordpress.com/2013/01/07/small-note-on-quaternion-distance-metrics/ + double returnValue = ((a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]) / (sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2] + a[3] * a[3])*sqrt(b[0] * b[0] + b[1] * b[1] + b[2] * b[2] + b[3] * b[3]))); + returnValue = 2 * acos(returnValue); + return returnValue; } itk::Matrix mitk::StaticIGTHelperFunctions::ConvertEulerAnglesToRotationMatrix(double alpha, double beta, double gamma) { double PI = 3.141592653589793; alpha = alpha * PI / 180; beta = beta * PI / 180; gamma = gamma * PI / 180; //convert angles to matrix: itk::Matrix matrix; /* x-Konvention (Z, X, Z) matrix[0][0] = cos(alpha) * cos(gamma) - sin(alpha) * cos(beta) * sin(gamma); matrix[0][1] = -cos(alpha) * sin(gamma)- sin(alpha) * cos(beta) * cos(gamma); matrix[0][2] = sin(alpha) * sin(beta); matrix[1][0] = sin(alpha) * cos(gamma) + cos(alpha) * cos(beta) * sin(gamma); matrix[1][1] = cos(alpha) * cos(beta) * cos(gamma) - sin(alpha) * sin(gamma); matrix[1][2] = -cos(alpha) * sin(beta); matrix[2][0] = sin(beta) * sin(gamma); matrix[2][1] = sin(beta) * cos(gamma); matrix[2][2] = cos(beta); */ //Luftfahrtnorm (DIN 9300) (Yaw-Pitch-Roll, Z, Y, X) matrix[0][0] = cos(beta) * cos(alpha); matrix[0][1] = cos(beta) * sin(alpha); matrix[0][2] = -sin(beta); matrix[1][0] = sin(gamma) * sin(beta) * cos(alpha) - cos(gamma) * sin(alpha) ; matrix[1][1] = sin(gamma) * sin(beta) * sin(alpha) + cos(gamma) * cos(alpha); matrix[1][2] = sin(gamma) * cos(beta); matrix[2][0] = cos(gamma) * sin(beta) * cos(alpha) + sin(gamma) * sin(alpha); matrix[2][1] = cos(gamma) * sin(beta) * sin(alpha) - sin(gamma) * cos(alpha); matrix[2][2] = cos(gamma) * cos(beta); return matrix; } double mitk::StaticIGTHelperFunctions::ComputeFRE(mitk::PointSet::Pointer imageFiducials, mitk::PointSet::Pointer realWorldFiducials, vtkSmartPointer transform) { - if (imageFiducials->GetSize() != realWorldFiducials->GetSize()) return -1; + if (imageFiducials->GetSize() != realWorldFiducials->GetSize()) + { + MITK_WARN << "Cannot compute FRE, got different numbers of points (1: " << imageFiducials->GetSize() << " /2: " << realWorldFiducials->GetSize() << ")"; + return -1; + } double FRE = 0; for (int i = 0; i < imageFiducials->GetSize(); i++) { itk::Point current_image_fiducial_point = imageFiducials->GetPoint(i); if (transform != NULL) { current_image_fiducial_point = transform->TransformPoint(imageFiducials->GetPoint(i)[0], imageFiducials->GetPoint(i)[1], imageFiducials->GetPoint(i)[2]); } double cur_error_squared = current_image_fiducial_point.SquaredEuclideanDistanceTo(realWorldFiducials->GetPoint(i)); FRE += cur_error_squared; } FRE = sqrt(FRE / (double)imageFiducials->GetSize()); return FRE; } diff --git a/Plugins/PluginList.cmake b/Plugins/PluginList.cmake index b07a6c786a..1aeaaa1516 100644 --- a/Plugins/PluginList.cmake +++ b/Plugins/PluginList.cmake @@ -1,75 +1,76 @@ # Plug-ins must be ordered according to their dependencies set(MITK_PLUGINS org.blueberry.core.runtime:ON org.blueberry.core.expressions:OFF org.blueberry.core.commands:OFF org.blueberry.core.jobs:OFF org.blueberry.ui.qt:OFF org.blueberry.ui.qt.help:OFF org.blueberry.ui.qt.log:ON org.blueberry.ui.qt.objectinspector:OFF #org.blueberry.test:ON #org.blueberry.uitest:ON #Testing/org.blueberry.core.runtime.tests:ON #Testing/org.blueberry.osgi.tests:ON org.mitk.core.services:ON org.mitk.gui.common:ON org.mitk.planarfigure:ON org.mitk.core.ext:OFF org.mitk.core.jobs:OFF org.mitk.diffusionimaging:OFF org.mitk.simulation:OFF org.mitk.gui.qt.application:ON org.mitk.gui.qt.coreapplication:OFF org.mitk.gui.qt.ext:OFF org.mitk.gui.qt.extapplication:OFF org.mitk.gui.qt.common:ON org.mitk.gui.qt.stdmultiwidgeteditor:ON org.mitk.gui.qt.common.legacy:OFF org.mitk.gui.qt.cmdlinemodules:OFF org.mitk.gui.qt.diffusionimagingapp:OFF org.mitk.gui.qt.datamanager:ON org.mitk.gui.qt.datamanagerlight:OFF org.mitk.gui.qt.properties:ON org.mitk.gui.qt.basicimageprocessing:OFF org.mitk.gui.qt.dicom:OFF org.mitk.gui.qt.dicominspector:OFF org.mitk.gui.qt.diffusionimaging:OFF org.mitk.gui.qt.dosevisualization:OFF org.mitk.gui.qt.geometrytools:OFF org.mitk.gui.qt.igtexamples:OFF org.mitk.gui.qt.igttracking:OFF org.mitk.gui.qt.openigtlink:OFF org.mitk.gui.qt.imagecropper:OFF org.mitk.gui.qt.imagenavigator:ON org.mitk.gui.qt.viewnavigator:OFF org.mitk.gui.qt.materialeditor:OFF org.mitk.gui.qt.measurementtoolbox:OFF org.mitk.gui.qt.moviemaker:OFF org.mitk.gui.qt.pointsetinteraction:OFF org.mitk.gui.qt.pointsetinteractionmultispectrum:OFF org.mitk.gui.qt.python:OFF org.mitk.gui.qt.registration:OFF org.mitk.gui.qt.remeshing:OFF org.mitk.gui.qt.segmentation:OFF org.mitk.gui.qt.simulation:OFF org.mitk.gui.qt.aicpregistration:OFF org.mitk.gui.qt.toftutorial:OFF org.mitk.gui.qt.tofutil:OFF org.mitk.gui.qt.tubegraph:OFF org.mitk.gui.qt.ugvisualization:OFF org.mitk.gui.qt.ultrasound:OFF org.mitk.gui.qt.volumevisualization:OFF org.mitk.gui.qt.eventrecorder:OFF org.mitk.gui.qt.xnat:OFF org.mitk.gui.qt.igt.app.echotrack:OFF org.mitk.gui.qt.spectrocamrecorder:OFF org.mitk.gui.qt.classificationsegmentation:OFF org.mitk.gui.qt.overlaymanager:OFF + org.mitk.gui.qt.igt.app.hummelprotocolmeasurements:OFF ) diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/CMakeLists.txt b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/CMakeLists.txt new file mode 100644 index 0000000000..4a35b65739 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/CMakeLists.txt @@ -0,0 +1,7 @@ +project(org_mitk_gui_qt_igtapphummelprotocolmeasurements) + +mitk_create_plugin( + EXPORT_DIRECTIVE IGTTRACKINGSEMIAUTOMATICMEASUREMENT_EXPORT + EXPORTED_INCLUDE_SUFFIXES src + MODULE_DEPENDS MitkIGT MitkIGTUI MitkCameraCalibration +) diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/Manual.dox b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/Manual.dox new file mode 100644 index 0000000000..1bfc037b22 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/Manual.dox @@ -0,0 +1,19 @@ +/** +\bundlemainpage{org.mitk.gui.qt.igttrackingsemiautomaticmeasurement} IGT Tracking Semi Automatic Measurement + +\imageMacro{icon.png,"Icon of IGT Tracking Semi Automatic Measurement",2.00} + +Available sections: + - \ref org.mitk.gui.qt.igttrackingsemiautomaticmeasurementOverview + +\section org.mitk.gui.qt.igttrackingsemiautomaticmeasurementOverview +Dieses PlugIn dient zur semiautomatischen Aufzeichnung von Messreihen mit Trackingsystemen. Entsprechend konfiguriert ist es auch für das Hummel-Protokoll einsetzbar. Zentrale Komponente des PlugIns ist eine IGT Pipeline zur Messdatenaufzeichnung, wie in Abbildung 1 dargestellt. + +\imageMacro{pipeline.png,"Icon of IGT Tracking Semi Automatic Measurement",10.00} + +Ein Screenshot der Benutzeroberfläche des PlugIns ist in Abbildung 2 zu sehen. Das Initialisieren und Starten des Trackingsystems erfolgt dabei im nicht dargestellten Tab "Tracking Initialization", der im Wesentlichen aus dem TrackingDeviceConfigurationWidget besteht. Zur Durchführung der Messungen unterstützt das PlugIn das Laden einer Liste mit Dateinamen für die Messungen, wie im oberen Teil des Screenshots zu sehen. Diese Liste wird abgearbeitet, wobei mit dem Button "Start Next Measurement" jeweils die nächste Messung gestartet wird. Die während der Messung aufgezeichneten Daten werden in eine Datei das Ausgabeverzeichnis geschrieben. Dabei entspricht der Dateiname dem aktuellen Namen aus der Liste. Die Anzahl der aufzuzeichnenden Messwerte pro Messung kann in den Einstellungen angegeben werden. Gab es bei einer Messung einen Fehler kann die Messung durch Auswahl des entsprechenden Buttons auch wiederholt werden. + +\imageMacro{screenshot.png,"Icon of IGT Tracking Semi Automatic Measurement",10.00} + +Das PlugIn unterstützt außerdem die Ansteuerung eines zweiten Trackingsystems. Dieses System soll einen am Phantom angebrachtes Tool (Reference Sensor) tracken und so sicherstellen, dass sich das Phantom während der Messung nicht bewegt. Wurde eine Bewegung des Phantoms festgestellt wird im unteren Teil des PlugIns "NOT OK" angezeigt und die Messung muss ggf. wiederholt werden. +*/ diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/icon.png b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/icon.png new file mode 100644 index 0000000000..c6d6160a7b Binary files /dev/null and b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/icon.png differ diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/pipeline.png b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/pipeline.png new file mode 100644 index 0000000000..c241b19f0c Binary files /dev/null and b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/pipeline.png differ diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/screenshot.png b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/screenshot.png new file mode 100644 index 0000000000..9aa1ad96e3 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/screenshot.png differ diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/doxygen/modules.dox b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/doxygen/modules.dox new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/doxygen/modules.dox @@ -0,0 +1 @@ + diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/files.cmake b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/files.cmake new file mode 100644 index 0000000000..dca2349880 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/files.cmake @@ -0,0 +1,42 @@ +SET(SRC_CPP_FILES + +) + +SET(INTERNAL_CPP_FILES + QmitkIGTTrackingSemiAutomaticMeasurementView.cpp + QmitkIGTTrackingDataEvaluationView.cpp + mitkPluginActivator.cpp + mitkNavigationDataCSVSequentialPlayer.cpp + mitkHummelProtocolEvaluation.cpp +) + +SET(UI_FILES + src/internal/QmitkIGTTrackingSemiAutomaticMeasurementViewControls.ui + src/internal/QmitkIGTTrackingDataEvaluationViewControls.ui +) + +SET(MOC_H_FILES + src/internal/QmitkIGTTrackingSemiAutomaticMeasurementView.h + src/internal/QmitkIGTTrackingDataEvaluationView.h + src/internal/mitkPluginActivator.h +) + +SET(CACHED_RESOURCE_FILES + resources/iconMeasurementTracking.svg + resources/iconMeasurementEvaluation.svg + plugin.xml +) + +SET(QRC_FILES + resources/QmitkIGTTrackingSemiAutomaticMeasurementView.qrc +) + +SET(CPP_FILES) + +foreach(file ${SRC_CPP_FILES}) + SET(CPP_FILES ${CPP_FILES} src/${file}) +endforeach(file ${SRC_CPP_FILES}) + +foreach(file ${INTERNAL_CPP_FILES}) + SET(CPP_FILES ${CPP_FILES} src/internal/${file}) +endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/manifest_headers.cmake new file mode 100644 index 0000000000..3c096ce558 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/manifest_headers.cmake @@ -0,0 +1,5 @@ +set(Plugin-Name "IGT APP: Hummel Protocol Measurements") +set(Plugin-Version "0.1") +set(Plugin-Vendor "DKFZ, Medical and Biological Informatics") +set(Plugin-ContactAddress "http://www.mitk.org") +set(Require-Plugin org.mitk.gui.qt.common.legacy) diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/plugin.xml b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/plugin.xml new file mode 100644 index 0000000000..fcffbca51a --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/plugin.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/QmitkIGTTrackingSemiAutomaticMeasurementView.qrc b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/QmitkIGTTrackingSemiAutomaticMeasurementView.qrc new file mode 100644 index 0000000000..ec7237a283 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/QmitkIGTTrackingSemiAutomaticMeasurementView.qrc @@ -0,0 +1,8 @@ + + + iconMeasurementTracking.svg + + + iconMeasurementEvaluation.svg + + diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/iconMeasurementEvaluation.svg b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/iconMeasurementEvaluation.svg new file mode 100644 index 0000000000..041b2ba8c2 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/iconMeasurementEvaluation.svg @@ -0,0 +1,1177 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/iconMeasurementTracking.svg b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/iconMeasurementTracking.svg new file mode 100644 index 0000000000..7d859cb212 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/iconMeasurementTracking.svg @@ -0,0 +1,1263 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationView.cpp b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationView.cpp new file mode 100644 index 0000000000..ffaa5c5699 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationView.cpp @@ -0,0 +1,1256 @@ +/*========================================================================= + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +=========================================================================*/ + +// Blueberry +#include +#include + +// Qmitk +#include "QmitkIGTTrackingDataEvaluationView.h" +#include "QmitkStdMultiWidget.h" + +// Qt +#include +#include +#include + +// MITK +#include "mitkNavigationDataCSVSequentialPlayer.h" +#include +#include +#include +#include +#include + + +//ITK +#include + +//VNL +#include + +//vtk headers +#include +#include +#include + +const std::string QmitkIGTTrackingDataEvaluationView::VIEW_ID = "org.mitk.views.igttrackingdataevaluation"; + +QmitkIGTTrackingDataEvaluationView::QmitkIGTTrackingDataEvaluationView() + : QmitkFunctionality() + , m_Controls(0) + , m_MultiWidget(NULL) + , m_scalingfactor(1) +{ + m_CSVtoXMLInputFilenameVector = std::vector(); + m_CSVtoXMLOutputFilenameVector = std::vector(); +} + +QmitkIGTTrackingDataEvaluationView::~QmitkIGTTrackingDataEvaluationView() +{ +} + +void QmitkIGTTrackingDataEvaluationView::CreateQtPartControl(QWidget *parent) +{ + // build up qt view, unless already done + if (!m_Controls) + { + // create GUI widgets from the Qt Designer's .ui file + m_Controls = new Ui::QmitkIGTTrackingDataEvaluationViewControls; + m_Controls->setupUi(parent); + + connect(m_Controls->m_LoadInputFileList, SIGNAL(clicked()), this, SLOT(OnLoadFileList())); + connect(m_Controls->m_StartEvaluation, SIGNAL(clicked()), this, SLOT(OnEvaluateData())); + connect(m_Controls->m_AddToCurrentList, SIGNAL(clicked()), this, SLOT(OnAddToCurrentList())); + connect(m_Controls->m_GeneratePointSetOfMeanPositions, SIGNAL(clicked()), this, SLOT(OnGeneratePointSet())); + connect(m_Controls->m_GenerateRotationLines, SIGNAL(clicked()), this, SLOT(OnGenerateRotationLines())); + connect(m_Controls->m_GeneratePointSet, SIGNAL(clicked()), this, SLOT(OnGenerateGroundTruthPointSet())); + connect(m_Controls->m_Convert, SIGNAL(clicked()), this, SLOT(OnConvertCSVtoXMLFile())); + connect(m_Controls->m_loadCSVtoXMLInputList, SIGNAL(clicked()), this, SLOT(OnCSVtoXMLLoadInputList())); + connect(m_Controls->m_loadCSVtoXMLOutputList, SIGNAL(clicked()), this, SLOT(OnCSVtoXMLLoadOutputList())); + connect(m_Controls->m_OrientationCalculationGenerateReference, SIGNAL(clicked()), this, SLOT(OnOrientationCalculation_CalcRef())); + connect(m_Controls->m_OrientationCalculationWriteOrientationsToFile, SIGNAL(clicked()), this, SLOT(OnOrientationCalculation_CalcOrientandWriteToFile())); + connect(m_Controls->m_GeneratePointSetsOfSinglePositions, SIGNAL(clicked()), this, SLOT(OnGeneratePointSetsOfSinglePositions())); + connect(m_Controls->m_StartEvaluationAll, SIGNAL(clicked()), this, SLOT(OnEvaluateDataAll())); + connect(m_Controls->m_GridMatching, SIGNAL(clicked()), this, SLOT(OnPerfomGridMatching())); + connect(m_Controls->m_ComputeRotation, SIGNAL(clicked()), this, SLOT(OnComputeRotation())); + + //initialize data storage combo boxes + m_Controls->m_ReferencePointSetComboBox->SetDataStorage(this->GetDataStorage()); + m_Controls->m_ReferencePointSetComboBox->SetAutoSelectNewItems(true); + m_Controls->m_ReferencePointSetComboBox->SetPredicate(mitk::NodePredicateDataType::New("PointSet")); + m_Controls->m_MeasurementPointSetComboBox->SetDataStorage(this->GetDataStorage()); + m_Controls->m_MeasurementPointSetComboBox->SetAutoSelectNewItems(true); + m_Controls->m_MeasurementPointSetComboBox->SetPredicate(mitk::NodePredicateDataType::New("PointSet")); + } +} + +void QmitkIGTTrackingDataEvaluationView::OnComputeRotation() +{ + //Get all data from UI + auto EvaluationDataCollection = GetAllDataFromUIList(); + //Compute mean Quaternions + auto OrientationVector = GetMeanOrientationsOfAllData(EvaluationDataCollection); + + //Compute Rotations + + itk::Vector rotationVec; + //adapt for Aurora 5D tools: [0,0,1000] + rotationVec[0] = 0; //X + rotationVec[1] = 10000; //Y + rotationVec[2] = 10000; //Z + + + std::vector allOrientationErrors; + for (int i = 0; i < (OrientationVector.size() - 1); i++) + { + double AngleBetweenTwoQuaternions = mitk::StaticIGTHelperFunctions::GetAngleBetweenTwoQuaterions(OrientationVector.at(i), OrientationVector.at(i+1), rotationVec); + double AngularError = abs(AngleBetweenTwoQuaternions - 11.25); + std::stringstream description; + description << "Rotation Error ROT" << (i + 1) << " / ROT" << (i + 2); + allOrientationErrors.push_back({ AngularError, description.str() }); + MITK_INFO << description.str() << ": " << AngularError; + } + + //compute statistics + std::vector orientationErrorStatistics; + orientationErrorStatistics = mitk::HummelProtocolEvaluation::ComputeStatistics(allOrientationErrors); + MITK_INFO << "## Rotation error statistics: ##"; + for (auto stat : orientationErrorStatistics) { MITK_INFO << stat.description << ": " << stat.distanceError; } + + //write results to file + allOrientationErrors.insert(allOrientationErrors.end(), orientationErrorStatistics.begin(), orientationErrorStatistics.end()); + allOrientationErrors.push_back({rotationVec[0],"Rot Vector [x]"}); + allOrientationErrors.push_back({rotationVec[1], "Rot Vector [y]"}); + allOrientationErrors.push_back({rotationVec[2], "Rot Vector [z]"}); + std::stringstream filenameOrientationStat; + filenameOrientationStat << std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str() << ".orientationStatistics.csv"; + MITK_INFO << "Writing output to file " << filenameOrientationStat.str(); + writeToFile(filenameOrientationStat.str(), allOrientationErrors); +} + +void QmitkIGTTrackingDataEvaluationView::OnPerfomGridMatching() +{ + mitk::PointSet::Pointer reference = dynamic_cast(m_Controls->m_ReferencePointSetComboBox->GetSelectedNode()->GetData()); + mitk::PointSet::Pointer measurement = dynamic_cast(m_Controls->m_MeasurementPointSetComboBox->GetSelectedNode()->GetData()); + //convert point sets to vtk poly data + vtkSmartPointer sourcePoints = vtkSmartPointer::New(); + vtkSmartPointer targetPoints = vtkSmartPointer::New(); + for (int i = 0; iGetSize(); i++) + { + double point[3] = { reference->GetPoint(i)[0], reference->GetPoint(i)[1], reference->GetPoint(i)[2] }; + sourcePoints->InsertNextPoint(point); + double point_targets[3] = { measurement->GetPoint(i)[0], measurement->GetPoint(i)[1], measurement->GetPoint(i)[2] }; + targetPoints->InsertNextPoint(point_targets); + } + //compute transform + vtkSmartPointer transform = vtkSmartPointer::New(); + transform->SetSourceLandmarks(sourcePoints); + transform->SetTargetLandmarks(targetPoints); + transform->SetModeToRigidBody(); + transform->Modified(); + transform->Update(); + //compute FRE of transform + double FRE = mitk::StaticIGTHelperFunctions::ComputeFRE(reference, measurement, transform); + MITK_INFO << "FRE after grid matching: " + QString::number(FRE) + " mm"; + //convert from vtk to itk data types + itk::Matrix rotationFloat = itk::Matrix(); + itk::Vector translationFloat = itk::Vector(); + itk::Matrix rotationDouble = itk::Matrix(); + itk::Vector translationDouble = itk::Vector(); + + vtkSmartPointer m = transform->GetMatrix(); + for (int k = 0; k<3; k++) for (int l = 0; l<3; l++) + { + rotationFloat[k][l] = m->GetElement(k, l); + rotationDouble[k][l] = m->GetElement(k, l); + + } + for (int k = 0; k<3; k++) + { + translationFloat[k] = m->GetElement(k, 3); + translationDouble[k] = m->GetElement(k, 3); + } + //create affine transform 3D + mitk::AffineTransform3D::Pointer mitkTransform = mitk::AffineTransform3D::New(); + mitkTransform->SetMatrix(rotationDouble); + mitkTransform->SetOffset(translationDouble); + mitk::NavigationData::Pointer transformNavigationData = mitk::NavigationData::New(mitkTransform); + m_Controls->m_ReferencePointSetComboBox->GetSelectedNode()->GetData()->GetGeometry()->SetIndexToWorldTransform(mitkTransform); + m_Controls->m_ReferencePointSetComboBox->GetSelectedNode()->GetData()->GetGeometry()->Modified(); + + //write to file + + std::stringstream filename; + filename << std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str() << ".GridMatchingResult.csv"; + MITK_INFO << "Writing output to file " << filename.str(); + std::vector FRE_Error; + FRE_Error.push_back({ FRE, "FRE after grid matching [mm]" }); + writeToFile(filename.str(), FRE_Error); +} + +void QmitkIGTTrackingDataEvaluationView::OnOrientationCalculation_CalcRef() +{ + if (m_FilenameVector.size() != 3) + { + MessageBox("Need exactly three points as reference, aborting!"); + return; + } + + //start loop and iterate through all files of list + for (int i = 0; i < m_FilenameVector.size(); i++) + { + //create navigation data player + mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = mitk::NavigationDataCSVSequentialPlayer::New(); + myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); + myPlayer->SetFileName(m_FilenameVector.at(i)); + + //check if the stream is valid and skip file if not + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + continue; + } + */ + + //create evaluation filter + mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); + + //connect pipeline + for (int j = 0; j < myPlayer->GetNumberOfOutputs(); j++) { myEvaluationFilter->SetInput(j, myPlayer->GetOutput(j)); } + + //update pipline until number of samlples is reached + for (int j = 0; j < m_Controls->m_NumberOfSamples->value(); j++) + { + myEvaluationFilter->Update(); + } + + //store mean position as reference + switch (i) + { + case 0: + m_RefPoint1 = myEvaluationFilter->GetPositionMean(0); + break; + case 1: + m_RefPoint2 = myEvaluationFilter->GetPositionMean(0); + break; + case 2: + m_RefPoint3 = myEvaluationFilter->GetPositionMean(0); + break; + } + } + MessageBox("Created Reference!"); +} + +void QmitkIGTTrackingDataEvaluationView::OnOrientationCalculation_CalcOrientandWriteToFile() +{ + //start loop and iterate through all files of list + for (int i = 0; i < m_FilenameVector.size(); i++) + { + //create navigation data player + mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = mitk::NavigationDataCSVSequentialPlayer::New(); + myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); + myPlayer->SetFileName(m_FilenameVector.at(i)); + + //check if the stream is valid and skip file if not + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + continue; + } + */ + + //open file header + QString outputname = QString(m_FilenameVector.at(i).c_str()) + "_orientationFile.csv"; + m_CurrentWriteFile.open(outputname.toStdString().c_str(), std::ios::out); + if (m_CurrentWriteFile.bad()) + { + MessageBox("Error: Can't open output file!"); + return; + } + + //write header to file + m_CurrentWriteFile << "Nr;Calypso_Time;Valid_Reference;MeasureTool_Measurement-Tool[x];MeasureTool_Measurement-Tool[y];MeasureTool_Measurement-Tool[z];MeasureTool_Measurement-Tool[qx];MeasureTool_Measurement-Tool[qy];MeasureTool_Measurement-Tool[qz];MeasureTool_Measurement-Tool[qr]\n"; + + //update pipeline until number of samples is reached + int step = 0; + mitk::Point3D point1, point2, point3; + mitk::Quaternion current_orientation; + + for (int j = 0; !myPlayer->IsAtEnd(); j++) + { + myPlayer->Update(); + mitk::NavigationData::Pointer currentNavData = myPlayer->GetOutput(0); + switch (step) + { + case 0: + step++; + point1 = currentNavData->GetPosition(); + break; + case 1: + step++; + point2 = currentNavData->GetPosition(); + break; + case 2: + step = 0; + point3 = currentNavData->GetPosition(); + + //compute transform from reference to current points + if (point1[0] == 0 && + point1[1] == 0 && + point1[2] == 0 && + point2[0] == 0 && + point2[1] == 0 && + point2[2] == 0 && + point3[0] == 0 && + point3[1] == 0 && + point3[2] == 0 + ) current_orientation.fill(0); + else + { + /* Drehen um eine Achse um das "Umschlagen" zu vermeiden + itk::Matrix rot180degreeAroundY; + rot180degreeAroundY.Fill(0); + rot180degreeAroundY[0][0] = -1; + rot180degreeAroundY[1][1] = 1; + rot180degreeAroundY[2][2] = -1; + point1 = rot180degreeAroundY * point1; + point2 = rot180degreeAroundY * point2; + point3 = rot180degreeAroundY * point3; + */ + + vtkSmartPointer transform = vtkSmartPointer::New(); + vtkSmartPointer sourcePoints = vtkSmartPointer::New(); + double sourcepoint1[3] = { point1[0], point1[1], point1[2] }; + double sourcepoint2[3] = { point2[0], point2[1], point2[2] }; + double sourcepoint3[3] = { point3[0], point3[1], point3[2] }; + sourcePoints->InsertNextPoint(sourcepoint1); + sourcePoints->InsertNextPoint(sourcepoint2); + sourcePoints->InsertNextPoint(sourcepoint3); + vtkSmartPointer targetPoints = vtkSmartPointer::New(); + double targetpoint1[3] = { m_RefPoint1[0], m_RefPoint1[1], m_RefPoint1[2] }; + double targetpoint2[3] = { m_RefPoint2[0], m_RefPoint2[1], m_RefPoint2[2] }; + double targetpoint3[3] = { m_RefPoint3[0], m_RefPoint3[1], m_RefPoint3[2] }; + targetPoints->InsertNextPoint(targetpoint1); + targetPoints->InsertNextPoint(targetpoint2); + targetPoints->InsertNextPoint(targetpoint3); + + transform->SetSourceLandmarks(sourcePoints); + transform->SetTargetLandmarks(targetPoints); + transform->Modified(); + transform->Update(); + + mitk::Transform::Pointer newTransform = mitk::Transform::New(); + newTransform->SetMatrix(transform->GetMatrix()); + current_orientation = newTransform->GetOrientation(); + + //add pointset with the three positions + if ((j > 15) && (j < 18)) + { + mitk::DataNode::Pointer newNode = mitk::DataNode::New(); + mitk::PointSet::Pointer newPointSet = mitk::PointSet::New(); + newPointSet->InsertPoint(0, point1); + newPointSet->InsertPoint(1, point2); + newPointSet->InsertPoint(2, point3); + QString name = QString(m_FilenameVector.at(i).c_str()); + newNode->SetName(name.toStdString().c_str()); + newNode->SetData(newPointSet); + newNode->SetFloatProperty("pointsize", 0.1); + this->GetDataStorage()->Add(newNode); + } + } + + break; + } + m_CurrentWriteFile << i << ";"; + m_CurrentWriteFile << currentNavData->GetTimeStamp() << ";"; //IMPORTANT: change to GetIGTTimeStamp in new version! + m_CurrentWriteFile << "true;"; + m_CurrentWriteFile << currentNavData->GetPosition()[0] << ";"; + m_CurrentWriteFile << currentNavData->GetPosition()[1] << ";"; + m_CurrentWriteFile << currentNavData->GetPosition()[2] << ";"; + m_CurrentWriteFile << current_orientation.x() << ";"; + m_CurrentWriteFile << current_orientation.y() << ";"; + m_CurrentWriteFile << current_orientation.z() << ";"; + m_CurrentWriteFile << current_orientation.r() << ";"; + m_CurrentWriteFile << "\n"; + } + //close output file + m_CurrentWriteFile.close(); + } + MessageBox("Finished!"); +} + +void QmitkIGTTrackingDataEvaluationView::StdMultiWidgetAvailable(QmitkStdMultiWidget &stdMultiWidget) +{ + m_MultiWidget = &stdMultiWidget; +} + +void QmitkIGTTrackingDataEvaluationView::StdMultiWidgetNotAvailable() +{ + m_MultiWidget = NULL; +} + +void QmitkIGTTrackingDataEvaluationView::OnAddToCurrentList() +{ + //read in files + QStringList files = QFileDialog::getOpenFileNames(NULL, "Select one or more files to open", "/", "CSV (*.csv)"); + if (files.isEmpty()) return; + + for (int i = 0; i < files.size(); i++){ + std::string tmp = files.at(i).toStdString().c_str(); + m_FilenameVector.push_back(tmp); + } + /* + //save old locale + char * oldLocale; + oldLocale = setlocale( LC_ALL, 0 ); + + //define own locale + std::locale C("C"); + setlocale( LC_ALL, "C" ); + */ //TODO: check if this is needed here, and load old locale if yes + + /* + //read file + std::ifstream file; + file.open(filename.toStdString().c_str(), std::ios::in); + if (file.good()) + { + //read out file + file.seekg(0L, std::ios::beg); // move to begin of file + while (!file.eof()) + { + std::string buffer; + std::getline(file, buffer); // read out file line by line + if (buffer.size() > 0) + { + std::string thisFilename = ""; + if (m_Controls->m_AddPath->isChecked()) thisFilename = m_Controls->m_ListPath->text().toStdString(); + thisFilename.append(buffer); + m_FilenameVector.push_back(thisFilename); + } + } + } + */ + //fill list at GUI + m_Controls->m_FileList->clear(); + for (unsigned int i = 0; i < m_FilenameVector.size(); i++) { new QListWidgetItem(tr(m_FilenameVector.at(i).c_str()), m_Controls->m_FileList); } +} + +void QmitkIGTTrackingDataEvaluationView::OnLoadFileList() +{ + m_FilenameVector = std::vector(); + m_FilenameVector.clear(); + OnAddToCurrentList(); +} + +void QmitkIGTTrackingDataEvaluationView::OnEvaluateDataAll() +{ + std::vector results5cm, results15cm, results30cm, resultsAccum; + mitk::HummelProtocolEvaluation::HummelProtocolMeasurementVolume volume; + if (m_Controls->m_standardVolume->isChecked()) + { + volume = mitk::HummelProtocolEvaluation::standard; + mitk::HummelProtocolEvaluation::Evaluate5cmDistances(m_PointSetMeanPositions, volume, results5cm); + mitk::HummelProtocolEvaluation::Evaluate15cmDistances(m_PointSetMeanPositions, volume, results15cm); + mitk::HummelProtocolEvaluation::Evaluate30cmDistances(m_PointSetMeanPositions, volume, results30cm); + mitk::HummelProtocolEvaluation::EvaluateAccumulatedDistances(m_PointSetMeanPositions, volume, resultsAccum); + } + else + { + volume = mitk::HummelProtocolEvaluation::small; + mitk::HummelProtocolEvaluation::Evaluate5cmDistances(m_PointSetMeanPositions, volume, results5cm); + } + + //write results to file + std::stringstream filename5cm; + filename5cm << std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str() << ".results5cm.csv"; + MITK_INFO << "Writing output to file " << filename5cm.str(); + writeToFile(filename5cm.str(), results5cm); + + std::stringstream filename15cm; + filename15cm << std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str() << ".results15cm.csv"; + MITK_INFO << "Writing output to file " << filename15cm.str(); + writeToFile(filename15cm.str(), results15cm); + + std::stringstream filename30cm; + filename30cm << std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str() << ".results30cm.csv"; + MITK_INFO << "Writing output to file " << filename30cm.str(); + writeToFile(filename30cm.str(), results30cm); + + std::stringstream filenameAccum; + filenameAccum << std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str() << ".resultsAccumDist.csv"; + MITK_INFO << "Writing output to file " << filenameAccum.str(); + writeToFile(filenameAccum.str(), resultsAccum); +} + +void QmitkIGTTrackingDataEvaluationView::OnEvaluateData() +{ + //open output file + m_CurrentWriteFile.open(std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str(), std::ios::out); + if (m_CurrentWriteFile.bad()) + { + MessageBox("Error: Can't open output file!"); + return; + } + + std::vector jitterValues; + + //write output file header + WriteHeader(); + + //start loop and iterate through all files of list + for (int i = 0; i < m_FilenameVector.size(); i++) + { + //create navigation data player + mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = mitk::NavigationDataCSVSequentialPlayer::New(); + myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); + myPlayer->SetFileName(m_FilenameVector.at(i)); + + //check if the stream is valid and skip file if not + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + + continue; + } + */ + + //create evaluation filter + mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); + + //connect pipeline + for (int j = 0; j < myPlayer->GetNumberOfOutputs(); j++) { myEvaluationFilter->SetInput(j, myPlayer->GetOutput(j)); } + + if (myPlayer->GetNumberOfSnapshots() < m_Controls->m_NumberOfSamples->value()) + { + MITK_WARN << "Number of snapshots (" << myPlayer->GetNumberOfSnapshots() << ") smaller than number of samples to evaluate (" << m_Controls->m_NumberOfSamples->value() << ") ! Cannot proceed!"; + return; + } + + //update pipline until number of samples is reached + for (int j = 0; j < m_Controls->m_NumberOfSamples->value(); j++) + { + myEvaluationFilter->Update(); + //Debug output: + //std::cout.precision(5); + //std::cout << "Euler " << j << ";" << myPlayer->GetOutput()->GetOrientation().rotation_euler_angles()[0] << ";" << myPlayer->GetOutput()->GetOrientation().rotation_euler_angles()[1] << ";" << myPlayer->GetOutput()->GetOrientation().rotation_euler_angles()[2] << "\n"; + } + + //store all jitter values in separate vector for statistics + jitterValues.push_back({ myEvaluationFilter->GetPositionErrorRMS(0), "RMS" }); + + //write result to output file + WriteDataSet(myEvaluationFilter, m_FilenameVector.at(i)); + } + + //close output file for single data + m_CurrentWriteFile.close(); + + //compute statistics + std::vector jitterStatistics = mitk::HummelProtocolEvaluation::ComputeStatistics(jitterValues); + MITK_INFO << "## Jitter (RMS) statistics: ##"; + for (auto jitterStat : jitterStatistics) {MITK_INFO << jitterStat.description << ": " << jitterStat.distanceError;} + + //write statistic results to separate file + std::stringstream filenameJitterStat; + filenameJitterStat << std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str() << ".resultsJitterStatistics.csv"; + MITK_INFO << "Writing output to file " << filenameJitterStat.str(); + writeToFile(filenameJitterStat.str(), jitterStatistics); + + //calculate angles if option is on + if (m_Controls->m_settingDifferenceAngles->isChecked() || m_Controls->m_DifferencesSLERP->isChecked()) CalculateDifferenceAngles(); + + MessageBox("Finished!"); +} + +void QmitkIGTTrackingDataEvaluationView::OnGeneratePointSetsOfSinglePositions() +{ + m_scalingfactor = m_Controls->m_ScalingFactor->value(); + + //start loop and iterate through all files of list + for (int i = 0; i < m_FilenameVector.size(); i++) + { + //create point set for this file + mitk::PointSet::Pointer thisPointSet = mitk::PointSet::New(); + + //create navigation data player + mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = mitk::NavigationDataCSVSequentialPlayer::New(); + myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); + myPlayer->SetFileName(m_FilenameVector.at(i)); + + //check if the stream is valid and skip file if not + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + + continue; + } + */ + + //update pipline until number of samlples is reached and store every single point + for (int j = 0; j < m_Controls->m_NumberOfSamples->value(); j++) + { + myPlayer->Update(); + mitk::Point3D thisPoint = myPlayer->GetOutput()->GetPosition(); + thisPoint[0] *= m_scalingfactor; + thisPoint[1] *= m_scalingfactor; + thisPoint[2] *= m_scalingfactor; + thisPointSet->InsertPoint(j, thisPoint); + } + + //add point set to data storage + mitk::DataNode::Pointer newNode = mitk::DataNode::New(); + QString name = this->m_Controls->m_prefix->text() + QString("PointSet_of_All_Positions_") + QString::number(i); + newNode->SetName(name.toStdString()); + newNode->SetData(thisPointSet); + this->GetDataStorage()->Add(newNode); + } +} + +void QmitkIGTTrackingDataEvaluationView::OnGeneratePointSet() +{ + m_scalingfactor = m_Controls->m_ScalingFactor->value(); + + mitk::PointSet::Pointer generatedPointSet = mitk::PointSet::New(); + + //start loop and iterate through all files of list + for (int i = 0; i < m_FilenameVector.size(); i++) + { + //create navigation data player + mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = mitk::NavigationDataCSVSequentialPlayer::New(); + myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); + myPlayer->SetFileName(m_FilenameVector.at(i)); + + //check if the stream is valid and skip file if not + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + + continue; + } + */ + + //create evaluation filter + mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); + + //connect pipeline + for (int j = 0; j < myPlayer->GetNumberOfOutputs(); j++) { myEvaluationFilter->SetInput(j, myPlayer->GetOutput(j)); } + + //update pipline until number of samlples is reached + for (int j = 0; j < m_Controls->m_NumberOfSamples->value(); j++) { myEvaluationFilter->Update(); } + + //add mean position to point set + mitk::Point3D meanPos = myEvaluationFilter->GetPositionMean(0); + if (m_scalingfactor != 1) + { + meanPos[0] *= m_scalingfactor; + meanPos[1] *= m_scalingfactor; + meanPos[2] *= m_scalingfactor; + } + generatedPointSet->InsertPoint(i, meanPos); + } + + //add point set to data storage + mitk::DataNode::Pointer newNode = mitk::DataNode::New(); + QString name = this->m_Controls->m_prefix->text() + "PointSet_of_Mean_Positions"; + newNode->SetName(name.toStdString()); + newNode->SetData(generatedPointSet); + newNode->SetFloatProperty("pointsize", 5); + this->GetDataStorage()->Add(newNode); + m_PointSetMeanPositions = generatedPointSet; +} + +void QmitkIGTTrackingDataEvaluationView::OnGenerateRotationLines() +{ + m_scalingfactor = m_Controls->m_ScalingFactor->value(); + + //start loop and iterate through all files of list + for (int i = 0; i < m_FilenameVector.size(); i++) + { + //create navigation data player + mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = mitk::NavigationDataCSVSequentialPlayer::New(); + myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); + myPlayer->SetFileName(m_FilenameVector.at(i)); + + //check if the stream is valid and skip file if not + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + } + else + */ + { + //create evaluation filter + mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); + + //connect pipeline + for (int j = 0; j < myPlayer->GetNumberOfOutputs(); j++) { myEvaluationFilter->SetInput(j, myPlayer->GetOutput(j)); } + + //update pipline until number of samlples is reached + for (int j = 0; j < m_Controls->m_NumberOfSamples->value(); j++) + { + myEvaluationFilter->Update(); + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + continue; + } + */ + } + //if (!myPlayer->IsAtEnd()) continue; + + //create line from mean pos to a second point which lies along the sensor (1,0,0 in tool coordinates for aurora) + mitk::Point3D meanPos = myEvaluationFilter->GetPositionMean(0); + if (m_scalingfactor != 1) + { + meanPos[0] *= m_scalingfactor; + meanPos[1] *= m_scalingfactor; + meanPos[2] *= m_scalingfactor; + } + mitk::Point3D secondPoint; + mitk::Point3D thirdPoint; + mitk::Point3D fourthPoint; + + mitk::FillVector3D(secondPoint, 2, 0, 0); //X + vnl_vector secondPointTransformed = myEvaluationFilter->GetQuaternionMean(0).rotation_matrix_transpose().transpose() * secondPoint.Get_vnl_vector() + meanPos.Get_vnl_vector(); + mitk::Point3D secondPointTransformedMITK; + mitk::FillVector3D(secondPointTransformedMITK, secondPointTransformed[0], secondPointTransformed[1], secondPointTransformed[2]); + + mitk::FillVector3D(thirdPoint, 0, 4, 0); //Y + vnl_vector thirdPointTransformed = myEvaluationFilter->GetQuaternionMean(0).rotation_matrix_transpose().transpose() * thirdPoint.Get_vnl_vector() + meanPos.Get_vnl_vector(); + mitk::Point3D thirdPointTransformedMITK; + mitk::FillVector3D(thirdPointTransformedMITK, thirdPointTransformed[0], thirdPointTransformed[1], thirdPointTransformed[2]); + + mitk::FillVector3D(fourthPoint, 0, 0, 6); //Z + vnl_vector fourthPointTransformed = myEvaluationFilter->GetQuaternionMean(0).rotation_matrix_transpose().transpose() * fourthPoint.Get_vnl_vector() + meanPos.Get_vnl_vector(); + mitk::Point3D fourthPointTransformedMITK; + mitk::FillVector3D(fourthPointTransformedMITK, fourthPointTransformed[0], fourthPointTransformed[1], fourthPointTransformed[2]); + + mitk::PointSet::Pointer rotationLine = mitk::PointSet::New(); + rotationLine->InsertPoint(0, secondPointTransformedMITK); + rotationLine->InsertPoint(1, meanPos); + rotationLine->InsertPoint(2, thirdPointTransformedMITK); + rotationLine->InsertPoint(3, meanPos); + rotationLine->InsertPoint(4, fourthPointTransformedMITK); + + mitk::DataNode::Pointer newNode = mitk::DataNode::New(); + QString nodeName = this->m_Controls->m_prefix->text() + "RotationLineNumber" + QString::number(i); + newNode->SetName(nodeName.toStdString()); + newNode->SetData(rotationLine); + newNode->SetBoolProperty("show contour", true); + newNode->SetFloatProperty("pointsize", 0.5); + this->GetDataStorage()->Add(newNode); + } + } +} + +void QmitkIGTTrackingDataEvaluationView::OnGenerateGroundTruthPointSet() +{ + mitk::PointSet::Pointer generatedPointSet = mitk::PointSet::New(); + int currentPointID = 0; + mitk::Point3D currentPoint; + mitk::FillVector3D(currentPoint, 0, 0, 0); + for (int i = 0; i < m_Controls->m_PointNumber2->value(); i++) + { + for (int j = 0; j < m_Controls->m_PointNumber1->value(); j++) + { + generatedPointSet->InsertPoint(currentPointID, currentPoint); + currentPointID++; + currentPoint[1] += m_Controls->m_PointDistance->value(); + } + currentPoint[1] = 0; + currentPoint[2] += m_Controls->m_PointDistance->value(); + } + mitk::DataNode::Pointer newNode = mitk::DataNode::New(); + QString nodeName = "GroundTruthPointSet_" + QString::number(m_Controls->m_PointNumber1->value()) + "x" + QString::number(m_Controls->m_PointNumber2->value()) + "_(" + QString::number(m_Controls->m_PointDistance->value()) + "mm)"; + newNode->SetName(nodeName.toStdString()); + newNode->SetData(generatedPointSet); + newNode->SetFloatProperty("pointsize", 5); + this->GetDataStorage()->Add(newNode); +} + +void QmitkIGTTrackingDataEvaluationView::OnConvertCSVtoXMLFile() +{ + if (m_Controls->m_ConvertSingleFile->isChecked()) + { //convert one file + int lines = ConvertOneFile(this->m_Controls->m_InputCSV->text().toStdString(), this->m_Controls->m_OutputXML->text().toStdString()); + + QString result = "Converted one file with" + QString::number(lines) + " data sets"; + MessageBox(result.toStdString()); + } + else //converte file list + { + if (m_CSVtoXMLInputFilenameVector.empty() || m_CSVtoXMLOutputFilenameVector.empty()) + { + MessageBox("Error: one list is not loaded!"); + return; + } + else if (m_CSVtoXMLInputFilenameVector.size() != m_CSVtoXMLOutputFilenameVector.size()) + { + MessageBox("Error: lists do not have the same number of files!"); + return; + } + for (int i = 0; i < m_CSVtoXMLInputFilenameVector.size(); i++) + { + int lines = ConvertOneFile(m_CSVtoXMLInputFilenameVector.at(i), m_CSVtoXMLOutputFilenameVector.at(i)); + } + QString result = "Converted " + QString::number(m_CSVtoXMLInputFilenameVector.size()) + " files from file list!"; + MessageBox(result.toStdString()); + } +} + +int QmitkIGTTrackingDataEvaluationView::ConvertOneFile(std::string inputFilename, std::string outputFilename) +{ + std::vector myNavigationDatas = GetNavigationDatasFromFile(inputFilename); + mitk::NavigationDataRecorderDeprecated::Pointer myRecorder = mitk::NavigationDataRecorderDeprecated::New(); + myRecorder->SetFileName(outputFilename.c_str()); + mitk::NavigationData::Pointer input = mitk::NavigationData::New(); + if (m_Controls->m_ConvertCSV->isChecked()) myRecorder->SetOutputFormat(mitk::NavigationDataRecorderDeprecated::csv); + myRecorder->AddNavigationData(input); + myRecorder->StartRecording(); + for (int i = 0; i < myNavigationDatas.size(); i++) + { + input->Graft(myNavigationDatas.at(i)); + myRecorder->Update(); + } + myRecorder->StopRecording(); + return myNavigationDatas.size(); +} + +void QmitkIGTTrackingDataEvaluationView::OnCSVtoXMLLoadInputList() +{ + //read in filename + QString filename = QFileDialog::getOpenFileName(NULL, tr("Open Measurement Filename List"), "/", tr("All Files (*.*)")); + if (filename.isNull()) return; + + m_CSVtoXMLInputFilenameVector = this->GetFileContentLineByLine(filename.toStdString()); + + m_Controls->m_labelCSVtoXMLInputList->setText("READY"); +} + +void QmitkIGTTrackingDataEvaluationView::OnCSVtoXMLLoadOutputList() +{ + //read in filename + QString filename = QFileDialog::getOpenFileName(NULL, tr("Open Measurement Filename List"), "/", tr("All Files (*.*)")); + if (filename.isNull()) return; + + m_CSVtoXMLOutputFilenameVector = this->GetFileContentLineByLine(filename.toStdString()); + + m_Controls->m_labelCSVtoXMLOutputList->setText("READY"); +} + +void QmitkIGTTrackingDataEvaluationView::MessageBox(std::string s) +{ + QMessageBox msgBox; + msgBox.setText(s.c_str()); + msgBox.exec(); +} + +void QmitkIGTTrackingDataEvaluationView::WriteHeader() +{ + m_CurrentWriteFile << "Filename;"; + m_CurrentWriteFile << "N;"; + m_CurrentWriteFile << "N_invalid;"; + m_CurrentWriteFile << "Percentage_invalid;"; + + if (m_Controls->m_settingPosMean->isChecked()) + { + m_CurrentWriteFile << "Position_Mean[x];"; + m_CurrentWriteFile << "Position_Mean[y];"; + m_CurrentWriteFile << "Position_Mean[z];"; + } + + if (m_Controls->m_settingPosStabw->isChecked()) + { + m_CurrentWriteFile << "Position_StandDev[x];"; + m_CurrentWriteFile << "Position_StandDev[y];"; + m_CurrentWriteFile << "Position_StandDev[z];"; + } + + if (m_Controls->m_settingPosSampleStabw->isChecked()) + { + m_CurrentWriteFile << "Position_SampleStandDev[x];"; + m_CurrentWriteFile << "Position_SampleStandDev[y];"; + m_CurrentWriteFile << "Position_SampleStandDev[z];"; + } + + if (m_Controls->m_settingQuaternionMean->isChecked()) + { + m_CurrentWriteFile << "Quaternion_Mean[qx];"; + m_CurrentWriteFile << "Quaternion_Mean[qy];"; + m_CurrentWriteFile << "Quaternion_Mean[qz];"; + m_CurrentWriteFile << "Quaternion_Mean[qr];"; + } + + if (m_Controls->m_settionQuaternionStabw->isChecked()) + { + m_CurrentWriteFile << "Quaternion_StandDev[qx];"; + m_CurrentWriteFile << "Quaternion_StandDev[qy];"; + m_CurrentWriteFile << "Quaternion_StandDev[qz];"; + m_CurrentWriteFile << "Quaternion_StandDev[qr];"; + } + + if (m_Controls->m_settingPosErrorMean->isChecked()) m_CurrentWriteFile << "PositionError_Mean;"; + + if (m_Controls->m_settingPosErrorStabw->isChecked()) m_CurrentWriteFile << "PositionError_StandDev;"; + + if (m_Controls->m_settingPosErrorSampleStabw->isChecked()) m_CurrentWriteFile << "PositionError_SampleStandDev;"; + + if (m_Controls->m_settingPosErrorRMS->isChecked()) m_CurrentWriteFile << "PositionError_RMS;"; + + if (m_Controls->m_settingPosErrorMedian->isChecked()) m_CurrentWriteFile << "PositionError_Median;"; + + if (m_Controls->m_settingPosErrorMinMax->isChecked()) + { + m_CurrentWriteFile << "PositionError_Max;"; + m_CurrentWriteFile << "PositionError_Min;"; + } + + if (m_Controls->m_settingEulerMean->isChecked()) + { + m_CurrentWriteFile << "Euler_tx;"; + m_CurrentWriteFile << "Euler_ty;"; + m_CurrentWriteFile << "Euler_tz;"; + } + + if (m_Controls->m_settingEulerRMS->isChecked()) + { + m_CurrentWriteFile << "EulerErrorRMS (rad);"; + m_CurrentWriteFile << "EulerErrorRMS (grad);"; + } + + m_CurrentWriteFile << "\n"; +} + +void QmitkIGTTrackingDataEvaluationView::WriteDataSet(mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter, std::string dataSetName) +{ + if (myEvaluationFilter->GetNumberOfOutputs() == 0) m_CurrentWriteFile << "Error: no input \n"; + else + { + m_CurrentWriteFile << dataSetName << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetNumberOfAnalysedNavigationData(0) << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetNumberOfInvalidSamples(0) << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPercentageOfInvalidSamples(0) << ";"; + + if (m_Controls->m_settingPosMean->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetPositionMean(0)[0] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPositionMean(0)[1] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPositionMean(0)[2] << ";"; + } + + if (m_Controls->m_settingPosStabw->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetPositionStandardDeviation(0)[0] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPositionStandardDeviation(0)[1] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPositionStandardDeviation(0)[2] << ";"; + } + + if (m_Controls->m_settingPosSampleStabw->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetPositionSampleStandardDeviation(0)[0] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPositionSampleStandardDeviation(0)[1] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPositionSampleStandardDeviation(0)[2] << ";"; + } + + if (m_Controls->m_settingQuaternionMean->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionMean(0).x() << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionMean(0).y() << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionMean(0).z() << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionMean(0).r() << ";"; + } + + if (m_Controls->m_settionQuaternionStabw->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionStandardDeviation(0).x() << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionStandardDeviation(0).y() << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionStandardDeviation(0).z() << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionStandardDeviation(0).r() << ";"; + } + + if (m_Controls->m_settingPosErrorMean->isChecked()) m_CurrentWriteFile << myEvaluationFilter->GetPositionErrorMean(0) << ";"; + if (m_Controls->m_settingPosErrorStabw->isChecked()) m_CurrentWriteFile << myEvaluationFilter->GetPositionErrorStandardDeviation(0) << ";"; + if (m_Controls->m_settingPosErrorSampleStabw->isChecked()) m_CurrentWriteFile << myEvaluationFilter->GetPositionErrorSampleStandardDeviation(0) << ";"; + if (m_Controls->m_settingPosErrorRMS->isChecked()) m_CurrentWriteFile << myEvaluationFilter->GetPositionErrorRMS(0) << ";"; + if (m_Controls->m_settingPosErrorMedian->isChecked()) m_CurrentWriteFile << myEvaluationFilter->GetPositionErrorMedian(0) << ";"; + if (m_Controls->m_settingPosErrorMinMax->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetPositionErrorMax(0) << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPositionErrorMin(0) << ";"; + } + + if (m_Controls->m_settingEulerMean->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetEulerAnglesMean(0)[0] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetEulerAnglesMean(0)[1] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetEulerAnglesMean(0)[2] << ";"; + } + + if (m_Controls->m_settingEulerRMS->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetEulerAnglesRMS(0) << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetEulerAnglesRMSDegree(0) << ";"; + } + + m_CurrentWriteFile << "\n"; + } +} + + +std::vector QmitkIGTTrackingDataEvaluationView::GetMeanOrientationsOfAllData(std::vector allData, bool useSLERP) +{ + std::vector returnValue; + + for (auto dataSet : allData) + { + if (useSLERP) returnValue.push_back(GetSLERPAverage(dataSet)); + else returnValue.push_back(dataSet->GetQuaternionMean(0)); + } + + return returnValue; +} + + +std::vector QmitkIGTTrackingDataEvaluationView::GetAllDataFromUIList() +{ + std::vector EvaluationDataCollection; + + //start loop and iterate through all files of list: store the evaluation data + for (int i = 0; i < m_FilenameVector.size(); i++) + { + //create navigation data player + mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = mitk::NavigationDataCSVSequentialPlayer::New(); + myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); + myPlayer->SetFileName(m_FilenameVector.at(i)); + + //create evaluation filter + mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); + + //check if the stream is valid and skip file if not + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + } + else + */ + { + //connect pipeline + for (int j = 0; j < myPlayer->GetNumberOfOutputs(); j++) { myEvaluationFilter->SetInput(j, myPlayer->GetOutput(j)); } + //update pipline until number of samlples is reached + for (int j = 0; j < m_Controls->m_NumberOfSamples->value(); j++) { myEvaluationFilter->Update(); } + } + + myEvaluationFilter->SetInput(NULL); + myPlayer = NULL; + EvaluationDataCollection.push_back(myEvaluationFilter); + } + + return EvaluationDataCollection; +} + +void QmitkIGTTrackingDataEvaluationView::CalculateDifferenceAngles() +{ + //Get all data from UI + std::vector EvaluationDataCollection = GetAllDataFromUIList(); + + //calculation and writing of output data + //open output file + m_CurrentAngleDifferencesWriteFile.open(std::string((m_Controls->m_OutputFilename->text() + ".angledifferences.csv").toUtf8()).c_str(), std::ios::out); + if (m_CurrentAngleDifferencesWriteFile.bad()) + { + MessageBox("Error: Can't open output file for angle differences calculation!"); + return; + } + //write header + WriteDifferenceAnglesHeader(); + //compute angle differences + QString pos1 = "invalid"; + QString pos2 = "invalid"; + //now iterate through all evaluation data and calculate the angles + for (int i = 0; i < m_FilenameVector.size(); i++) + { + pos1 = QString::fromStdString(itksys::SystemTools::GetFilenameWithoutLastExtension(m_FilenameVector.at(i))); + for (int j = 0; j < m_FilenameVector.size(); j++) + { + pos2 = QString::fromStdString(itksys::SystemTools::GetFilenameWithoutLastExtension(m_FilenameVector.at(j))); + + mitk::Quaternion q1; + mitk::Quaternion q2; + + if (m_Controls->m_DifferencesSLERP->isChecked()) + { + //compute slerp average + q1 = GetSLERPAverage(EvaluationDataCollection.at(i)); + q2 = GetSLERPAverage(EvaluationDataCollection.at(j)); + } + else + { + //compute arithmetic average + q1 = EvaluationDataCollection.at(i)->GetQuaternionMean(0); + q2 = EvaluationDataCollection.at(j)->GetQuaternionMean(0); + } + + itk::Vector rotationVec; + //adapt for Aurora 5D tools: [0,0,1000] + rotationVec[0] = 10000; //X + rotationVec[1] = 0; //Y + rotationVec[2] = 0; //Z + double AngleBetweenTwoQuaternions = mitk::StaticIGTHelperFunctions::GetAngleBetweenTwoQuaterions(q1, q2, rotationVec); + + //write data set + WriteDifferenceAnglesDataSet(pos1.toStdString(), pos2.toStdString(), i, j, AngleBetweenTwoQuaternions); + } + } + + //close output file + m_CurrentAngleDifferencesWriteFile.close(); +} + +void QmitkIGTTrackingDataEvaluationView::WriteDifferenceAnglesHeader() +{ + m_CurrentAngleDifferencesWriteFile << "Name;Idx1;Idx2;Angle [Degree]\n"; +} + +void QmitkIGTTrackingDataEvaluationView::WriteDifferenceAnglesDataSet(std::string pos1, std::string pos2, int idx1, int idx2, double angle) +{ + //double PI = 3.1415926535897932384626433832795; + //double angle_degree = angle * 180 / PI; + m_CurrentAngleDifferencesWriteFile << "Angle between " << pos1 << " and " << pos2 << ";" << idx1 << ";" << idx2 << ";" << angle << "\n";//<< ";" << angle_degree << "\n"; + MITK_INFO << "Angle: " << angle; +} + +std::vector QmitkIGTTrackingDataEvaluationView::GetNavigationDatasFromFile(std::string filename) +{ + std::vector returnValue = std::vector(); + std::vector fileContentLineByLine = GetFileContentLineByLine(filename); + for (int i = 1; i < fileContentLineByLine.size(); i++) //skip header so start at 1 + { + returnValue.push_back(GetNavigationDataOutOfOneLine(fileContentLineByLine.at(i))); + } + + return returnValue; +} + +std::vector QmitkIGTTrackingDataEvaluationView::GetFileContentLineByLine(std::string filename) +{ + std::vector readData = std::vector(); + + //save old locale + char * oldLocale; + oldLocale = setlocale(LC_ALL, 0); + + //define own locale + std::locale C("C"); + setlocale(LC_ALL, "C"); + + //read file + std::ifstream file; + file.open(filename.c_str(), std::ios::in); + if (file.good()) + { + //read out file + file.seekg(0L, std::ios::beg); // move to begin of file + while (!file.eof()) + { + std::string buffer; + std::getline(file, buffer); // read out file line by line + if (buffer.size() > 0) readData.push_back(buffer); + } + } + + file.close(); + + //switch back to old locale + setlocale(LC_ALL, oldLocale); + + return readData; +} + +mitk::NavigationData::Pointer QmitkIGTTrackingDataEvaluationView::GetNavigationDataOutOfOneLine(std::string line) +{ + mitk::NavigationData::Pointer returnValue = mitk::NavigationData::New(); + + QString myLine = QString(line.c_str()); + + QStringList myLineList = myLine.split(';'); + + mitk::Point3D position; + mitk::Quaternion orientation; + + double time = myLineList.at(1).toDouble(); + + bool valid = false; + if (myLineList.at(2).toStdString() == "1") valid = true; + + position[0] = myLineList.at(3).toDouble(); + position[1] = myLineList.at(4).toDouble(); + position[2] = myLineList.at(5).toDouble(); + + orientation[0] = myLineList.at(6).toDouble(); + orientation[1] = myLineList.at(7).toDouble(); + orientation[2] = myLineList.at(8).toDouble(); + orientation[3] = myLineList.at(9).toDouble(); + + //returnValue->SetTimeStamp(time); + returnValue->SetDataValid(valid); + returnValue->SetPosition(position); + returnValue->SetOrientation(orientation); + + return returnValue; +} + +mitk::Quaternion QmitkIGTTrackingDataEvaluationView::GetSLERPAverage(mitk::NavigationDataEvaluationFilter::Pointer evaluationFilter) +{ + mitk::Quaternion average; + + //build a vector of quaternions from the evaulation filter (caution always takes the first (0) input of the filter + std::vector quaternions = std::vector(); + for (int i = 0; i < evaluationFilter->GetNumberOfAnalysedNavigationData(0); i++) + { + mitk::Quaternion currentq = evaluationFilter->GetLoggedOrientation(i, 0); + + quaternions.push_back(currentq); + } + + //compute the slerp average using the quaternion averaging class + mitk::QuaternionAveraging::Pointer myAverager = mitk::QuaternionAveraging::New(); + average = myAverager->CalcAverage(quaternions); + + return average; +} + +void QmitkIGTTrackingDataEvaluationView::writeToFile(std::string filename, std::vector values) +{ + std::fstream currentFile; + currentFile.open(filename.c_str(), std::ios::out); + if (currentFile.bad()) { MITK_WARN << "Cannot open file, aborting!"; return; } + currentFile << "Description" << ";" << "Error[mm]" << "\n"; + for (auto currentError : values) + { + currentFile << currentError.description << ";" << currentError.distanceError << "\n"; + } + currentFile.close(); +} diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationView.h b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationView.h new file mode 100644 index 0000000000..f308daa9b5 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationView.h @@ -0,0 +1,138 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef QmitkIGTTrackingDataEvaluationView_h +#define QmitkIGTTrackingDataEvaluationView_h + +#include + +#include + +#include "ui_QmitkIGTTrackingDataEvaluationViewControls.h" +#include "mitkHummelProtocolEvaluation.h" + +#include + + + +/*! + \brief QmitkIGTTrackingDataEvaluationView + + \warning This application module is not yet documented. Use "svn blame/praise/annotate" and ask the author to provide basic documentation. + + \sa QmitkFunctionality + \ingroup Functionalities +*/ +class QmitkIGTTrackingDataEvaluationView : public QmitkFunctionality +{ + // this is needed for all Qt objects that should have a Qt meta-object + // (everything that derives from QObject and wants to have signal/slots) + Q_OBJECT + + public: + + static const std::string VIEW_ID; + + QmitkIGTTrackingDataEvaluationView(); + virtual ~QmitkIGTTrackingDataEvaluationView(); + + virtual void CreateQtPartControl(QWidget *parent); + + virtual void StdMultiWidgetAvailable (QmitkStdMultiWidget &stdMultiWidget); + virtual void StdMultiWidgetNotAvailable(); + + protected slots: + + void OnLoadFileList(); + void OnAddToCurrentList(); + void OnEvaluateData(); + void OnEvaluateDataAll(); + void OnGeneratePointSet(); + void OnGeneratePointSetsOfSinglePositions(); + void OnGenerateRotationLines(); + void OnGenerateGroundTruthPointSet(); + void OnConvertCSVtoXMLFile(); + void OnCSVtoXMLLoadInputList(); + void OnCSVtoXMLLoadOutputList(); + void OnPerfomGridMatching(); + void OnComputeRotation(); + + /** Reads in exactly three position files als reference. */ + void OnOrientationCalculation_CalcRef(); + /** Uses always three positions (1,2,3: first orientation; 4,5,6: second orientation; and so on) in every file to calcualte a orientation. */ + void OnOrientationCalculation_CalcOrientandWriteToFile(); + + + protected: + + Ui::QmitkIGTTrackingDataEvaluationViewControls* m_Controls; + + QmitkStdMultiWidget* m_MultiWidget; + + std::vector m_FilenameVector; + + void MessageBox(std::string s); + + std::fstream m_CurrentWriteFile; + void WriteHeader(); + void WriteDataSet(mitk::NavigationDataEvaluationFilter::Pointer evaluationFilter, std::string dataSetName); + + //members for orientation calculation + mitk::Point3D m_RefPoint1; + mitk::Point3D m_RefPoint2; + mitk::Point3D m_RefPoint3; + + double m_scalingfactor; //scaling factor for visualization, 1 by default + + //angle diffrences: seperated file + std::fstream m_CurrentAngleDifferencesWriteFile; + void CalculateDifferenceAngles(); + void WriteDifferenceAnglesHeader(); + void WriteDifferenceAnglesDataSet(std::string pos1, std::string pos2, int idx1, int idx2, double angle); + + void writeToFile(std::string filename, std::vector values); + + //different help methods to read a csv logging file + std::vector GetNavigationDatasFromFile(std::string filename); + std::vector GetFileContentLineByLine(std::string filename); + mitk::NavigationData::Pointer GetNavigationDataOutOfOneLine(std::string line); + + //CSV to XML members + std::vector m_CSVtoXMLInputFilenameVector; + std::vector m_CSVtoXMLOutputFilenameVector; + + //returns the number of converted lines + int ConvertOneFile(std::string inputFilename, std::string outputFilename); + + /** @brief calculates the angle in the plane perpendicular to the rotation axis of the two quaterions. */ + double GetAngleBetweenTwoQuaterions(mitk::Quaternion a, mitk::Quaternion b); + + /** @brief calculates the slerp average of a set of quaternions which is stored in the navigation data evaluation filter */ + mitk::Quaternion GetSLERPAverage(mitk::NavigationDataEvaluationFilter::Pointer); + + /** @brief Stores the mean positions of all evaluated data */ + mitk::PointSet::Pointer m_PointSetMeanPositions; + + /** @return returns the mean orientation of all given data */ + std::vector GetMeanOrientationsOfAllData(std::vector allData, bool useSLERP = false); + + /** @return returns all data read from the data list as NavigationDataEvaluationFilters */ + std::vector GetAllDataFromUIList(); +}; + + + +#endif // _QMITKIGTTRACKINGDATAEVALUATIONVIEW_H_INCLUDED diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationViewControls.ui b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationViewControls.ui new file mode 100644 index 0000000000..c081daf5b1 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationViewControls.ui @@ -0,0 +1,1242 @@ + + + QmitkIGTTrackingDataEvaluationViewControls + + + + 0 + 0 + 378 + 955 + + + + + 0 + 0 + + + + QmitkTemplate + + + + + + 0 + + + + Evaluation + + + + + + Input File List (recorded NavigationData / *.csv): + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 120 + 0 + + + + Load New List + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 120 + 0 + + + + Add To Current List + + + + + + + + + Qt::Horizontal + + + + + + + (1) - VISUALIZATION - of all data sets: + + + + + + + + + Prefix for Data Nodes: + + + + + + + + + + + + Generate PointSet of Mean Positions + + + + + + + Generate PointSets of Single Positions + + + + + + + Generate Lines for Rotation + + + + + + + Qt::Horizontal + + + + + + + (3) - JITTER - Evaluation per file / data set: + + + + + + + + + Result CSV Filename: + + + + + + + D:/tmp/output.csv + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 220 + 0 + + + + + 200 + 50 + + + + COMPUTE RESULTS PER DATA SET + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + (3) - ACCURACY - Evaluation of all data sets: + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 220 + 0 + + + + + 200 + 50 + + + + COMPUTE RESULTS OF ALL DATA + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + (4) - GRID MATCHING - Evaluation of all data sets: + + + + + + + + + Reference PointSet: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 150 + 0 + + + + + + + + + + + + Measurement PointSet: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 150 + 0 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 220 + 0 + + + + + 200 + 50 + + + + PERFOM GRID MATCHING + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + (5) - ROTATION - Evaluation of all data sets: + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 220 + 0 + + + + + 200 + 50 + + + + COMPUTE ROTATION ERRORS + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 220 + + + + + + + + + Settings + + + + + + General + + + + + + + + Number of samples to analyze: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 1000000 + + + 150 + + + + + + + + + + + Scaling Factor for Visualization: + + + + + + + Qt::Horizontal + + + + 38 + 20 + + + + + + + + 1.000000000000000 + + + + + + + + + Tracking Volume: + + + + + + + Standard Volume (10 X 9 Positions) + + + true + + + + + + + Small Volume (3 X 4 Positions) + + + + + + + + + + Output per data set + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-style:italic; text-decoration: underline;">Position</span></p></body></html> + + + + + + + Mean (x,y,z) + + + true + + + + + + + Standard Deviation (x,y,z) + + + + + + + Sample Standard Deviation (x,y,z) + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-style:italic; text-decoration: underline;">Orientation</span></p></body></html> + + + + + + + Quaternion Mean (qx,qy,qz,qr) + + + + + + + Quaternion Mean (SLERP) + + + + + + + Quaternion Standard Deviation (qx,qy,qz,qr) + + + + + + + Euler Mean (tx,ty,tz) + + + + + + + Difference Angles to all other Positions + + + + + + + Difference Angles to all other Positions (SLERP) + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-style:italic; text-decoration: underline;">Position Error</span></p></body></html> + + + + + + + Mean + + + + + + + Standard Deviation + + + + + + + Sample Standard Deviation + + + + + + + RMS + + + true + + + + + + + Median + + + + + + + Min/Max + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-style:italic; text-decoration: underline;">Orientation Error</span></p></body></html> + + + + + + + Euler RMS + + + + + + + + + + Qt::Vertical + + + + 20 + 344 + + + + + + + + + Tools + + + + + + Point Set Ground Truth Generator + + + + + + + + Generate + + + + + + + 1 + + + 999 + + + 10 + + + + + + + X + + + + + + + 1 + + + 999 + + + 9 + + + + + + + Point Set + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Inter Point Distance (in mm): + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 1 + + + 99999 + + + 50 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Generate + + + + + + + + + + + + Result CSV File to NavigationData Converter + + + + + + Convert Single File + + + true + + + + + + + Input CSV Logging File: + + + + + + + C:/Tools/test.csv + + + + + + + Output Navigation Data File: + + + + + + + C:/Tools/testoutput.xml + + + + + + + Qt::Horizontal + + + + + + + Convert File List + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-style:italic;">(use text files with a complete filename in every line)</span></p></body></html> + + + + + + + + + not loaded + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 100 + 0 + + + + Load Input List + + + + + + + + + + + not loaded + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 100 + 0 + + + + Load Output List + + + + + + + + + Qt::Horizontal + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Output Format + + + + + + XML + + + true + + + + + + + CSV + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Convert + + + + + + + + + + + + Orientation Calculation (out of three positions) + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Generate Reference From Current List + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Write Orientation Quaternions To File + + + + + + + + + + + + Qt::Vertical + + + + 20 + 632 + + + + + + + + + + + + + + QmitkDataStorageComboBox + QComboBox +
QmitkDataStorageComboBox.h
+
+
+ + +
diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementView.cpp b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementView.cpp new file mode 100644 index 0000000000..b75fa37887 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementView.cpp @@ -0,0 +1,627 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +// Blueberry +#include +#include + +// Qmitk +#include "QmitkIGTTrackingSemiAutomaticMeasurementView.h" +#include "QmitkStdMultiWidget.h" + +// Qt +#include +#include +#include +#include + +// MITK +#include +#include +#include +#include "mitkHummelProtocolEvaluation.h" + +// POCO +#include +#include + +const std::string QmitkIGTTrackingSemiAutomaticMeasurementView::VIEW_ID = "org.mitk.views.igttrackingsemiautomaticmeasurement"; + +QmitkIGTTrackingSemiAutomaticMeasurementView::QmitkIGTTrackingSemiAutomaticMeasurementView() + : QmitkFunctionality() + , m_Controls(0) + , m_MultiWidget(NULL) +{ + m_NextFile = 0; + m_FilenameVector = std::vector(); + m_Timer = new QTimer(this); + m_logging = false; + m_referenceValid = true; + m_tracking = false; + m_EvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); +} + +QmitkIGTTrackingSemiAutomaticMeasurementView::~QmitkIGTTrackingSemiAutomaticMeasurementView() +{ +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::CreateResults() +{ + QString LogFileName = m_Controls->m_OutputPath->text() + "_results.log"; + mitk::LoggingBackend::Unregister(); + mitk::LoggingBackend::SetLogFile(LogFileName.toStdString().c_str()); + mitk::LoggingBackend::Register(); + + double RMSmean = 0; + for (int i = 0; i < m_RMSValues.size(); i++) + { + MITK_INFO << "RMS at " << this->m_FilenameVector.at(i) << ": " << m_RMSValues.at(i); + RMSmean += m_RMSValues.at(i); + } + RMSmean /= m_RMSValues.size(); + MITK_INFO << "RMS mean over " << m_RMSValues.size() << " values: " << RMSmean; + + mitk::DataNode::Pointer newNode = mitk::DataNode::New(); + newNode->SetName("Tracking Results"); + newNode->SetData(this->m_MeanPoints); + this->GetDataStorage()->Add(newNode); + + std::vector results5cmDistances; + mitk::HummelProtocolEvaluation::Evaluate5cmDistances(m_MeanPoints, mitk::HummelProtocolEvaluation::small, results5cmDistances); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::CreateQtPartControl(QWidget *parent) +{ + // build up qt view, unless already done + if (!m_Controls) + { + // create GUI widgets from the Qt Designer's .ui file + m_Controls = new Ui::QmitkIGTTrackingSemiAutomaticMeasurementViewControls; + m_Controls->setupUi(parent); + + //buttons + connect(m_Controls->m_LoadMeasurementToolStorage, SIGNAL(clicked()), this, SLOT(OnLoadMeasurementStorage())); + connect(m_Controls->m_LoadReferenceToolStorage, SIGNAL(clicked()), this, SLOT(OnLoadReferenceStorage())); + connect(m_Controls->m_StartTracking, SIGNAL(clicked()), this, SLOT(OnStartTracking())); + connect(m_Controls->m_LoadList, SIGNAL(clicked()), this, SLOT(OnMeasurementLoadFile())); + connect(m_Controls->m_StartNextMeasurement, SIGNAL(clicked()), this, SLOT(StartNextMeasurement())); + connect(m_Controls->m_ReapeatLastMeasurement, SIGNAL(clicked()), this, SLOT(RepeatLastMeasurement())); + connect(m_Controls->m_SetReference, SIGNAL(clicked()), this, SLOT(OnSetReference())); + connect(m_Controls->m_UseReferenceTrackingSystem, SIGNAL(toggled(bool)), this, SLOT(OnUseReferenceToggled(bool))); + connect(m_Controls->m_CreateResults, SIGNAL(clicked()), this, SLOT(CreateResults())); + + //event filter + qApp->installEventFilter(this); + + //timers + connect(m_Timer, SIGNAL(timeout()), this, SLOT(UpdateTimer())); + } + + //initialize some view + m_Controls->m_StopTracking->setEnabled(false); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::StdMultiWidgetAvailable(QmitkStdMultiWidget &stdMultiWidget) +{ + m_MultiWidget = &stdMultiWidget; +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::OnUseReferenceToggled(bool state) +{ + if (state) + { + m_Controls->m_ReferenceBox->setEnabled(true); + m_Controls->m_SetReference->setEnabled(true); + } + + else + { + m_Controls->m_ReferenceBox->setEnabled(false); + m_Controls->m_SetReference->setEnabled(false); + } +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::StdMultiWidgetNotAvailable() +{ + m_MultiWidget = NULL; +} + +mitk::NavigationToolStorage::Pointer QmitkIGTTrackingSemiAutomaticMeasurementView::ReadStorage(std::string file) +{ + mitk::NavigationToolStorage::Pointer returnValue; + + //initialize tool storage + returnValue = mitk::NavigationToolStorage::New(); + + //read tool storage from disk + mitk::NavigationToolStorageDeserializer::Pointer myDeserializer = mitk::NavigationToolStorageDeserializer::New(GetDataStorage()); + returnValue = myDeserializer->Deserialize(file); + if (returnValue.IsNull()) + { + QMessageBox msgBox; + msgBox.setText(myDeserializer->GetErrorMessage().c_str()); + msgBox.exec(); + + returnValue = NULL; + } + + return returnValue; +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::OnSetReference() +{ + //initialize reference + m_ReferenceStartPositions = std::vector(); + m_ReferenceTrackingDeviceSource->Update(); + QString Label = "Positions At Start: "; + for (int i = 0; i < m_ReferenceTrackingDeviceSource->GetNumberOfOutputs(); i++) + { + mitk::Point3D position = m_ReferenceTrackingDeviceSource->GetOutput(i)->GetPosition(); + Label = Label + "Tool" + QString::number(i) + ":[" + QString::number(position[0]) + ":" + QString::number(position[1]) + ":" + QString::number(position[1]) + "] "; + m_ReferenceStartPositions.push_back(position); + } + m_Controls->m_ReferencePosAtStart->setText(Label); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::OnLoadMeasurementStorage() +{ + //read in filename + QString filename = QFileDialog::getOpenFileName(NULL, tr("Open Toolfile"), "/", tr("All Files (*.*)")); + if (filename.isNull()) return; + + m_MeasurementStorage = ReadStorage(filename.toStdString()); + + //update label + Poco::Path myPath = Poco::Path(filename.toStdString()); //use this to seperate filename from path + QString toolLabel = QString("Tool Storage: ") + QString::number(m_MeasurementStorage->GetToolCount()) + " Tools from " + myPath.getFileName().c_str(); + m_Controls->m_MeasurementToolStorageLabel->setText(toolLabel); + + //update status widget + m_Controls->m_ToolStatusWidget->RemoveStatusLabels(); + m_Controls->m_ToolStatusWidget->PreShowTools(m_MeasurementStorage); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::OnLoadReferenceStorage() +{ + //read in filename + static QString oldFile; + if (oldFile.isNull()) oldFile = "/"; + QString filename = QFileDialog::getOpenFileName(NULL, tr("Open Toolfile"), oldFile, tr("All Files (*.*)")); + if (filename.isNull()) return; + oldFile = filename; + + m_ReferenceStorage = ReadStorage(filename.toStdString()); + + //update label + Poco::Path myPath = Poco::Path(filename.toStdString()); //use this to seperate filename from path + QString toolLabel = QString("Tool Storage: ") + QString::number(m_ReferenceStorage->GetToolCount()) + " Tools from " + myPath.getFileName().c_str(); + m_Controls->m_ReferenceToolStorageLabel->setText(toolLabel); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::OnStartTracking() +{ + //check if everything is ready to start tracking + if (m_MeasurementStorage.IsNull()) + { + MessageBox("Error: No measurement tools loaded yet!"); + return; + } + else if (m_ReferenceStorage.IsNull() && m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + MessageBox("Error: No refernce tools loaded yet!"); + return; + } + else if (m_MeasurementStorage->GetToolCount() == 0) + { + MessageBox("Error: No way to track without tools!"); + return; + } + else if (m_Controls->m_UseReferenceTrackingSystem->isChecked() && (m_ReferenceStorage->GetToolCount() == 0)) + { + MessageBox("Error: No way to track without tools!"); + return; + } + + //build the first IGT pipeline (MEASUREMENT) + mitk::TrackingDeviceSourceConfigurator::Pointer myTrackingDeviceSourceFactory1 = mitk::TrackingDeviceSourceConfigurator::New(this->m_MeasurementStorage, this->m_Controls->m_MeasurementTrackingDeviceConfigurationWidget->GetTrackingDevice()); + m_MeasurementTrackingDeviceSource = myTrackingDeviceSourceFactory1->CreateTrackingDeviceSource(this->m_MeasurementToolVisualizationFilter); + if (m_MeasurementTrackingDeviceSource.IsNull()) + { + MessageBox(myTrackingDeviceSourceFactory1->GetErrorMessage()); + return; + } + //connect the tool visualization widget + for (int i = 0; i < m_MeasurementTrackingDeviceSource->GetNumberOfOutputs(); i++) + { + m_Controls->m_ToolStatusWidget->AddNavigationData(m_MeasurementTrackingDeviceSource->GetOutput(i)); + m_EvaluationFilter->SetInput(i, m_MeasurementTrackingDeviceSource->GetOutput(i)); + } + m_Controls->m_ToolStatusWidget->ShowStatusLabels(); + m_Controls->m_ToolStatusWidget->SetShowPositions(true); + m_Controls->m_ToolStatusWidget->SetShowQuaternions(true); + + //build the second IGT pipeline (REFERENCE) + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + mitk::TrackingDeviceSourceConfigurator::Pointer myTrackingDeviceSourceFactory2 = mitk::TrackingDeviceSourceConfigurator::New(this->m_ReferenceStorage, this->m_Controls->m_ReferenceDeviceConfigurationWidget->GetTrackingDevice()); + m_ReferenceTrackingDeviceSource = myTrackingDeviceSourceFactory2->CreateTrackingDeviceSource(); + if (m_ReferenceTrackingDeviceSource.IsNull()) + { + MessageBox(myTrackingDeviceSourceFactory2->GetErrorMessage()); + return; + } + } + + //initialize tracking + try + { + m_MeasurementTrackingDeviceSource->Connect(); + m_MeasurementTrackingDeviceSource->StartTracking(); + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + m_ReferenceTrackingDeviceSource->Connect(); + m_ReferenceTrackingDeviceSource->StartTracking(); + } + } + catch (...) + { + MessageBox("Error while starting the tracking device!"); + return; + } + + //set reference + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) OnSetReference(); + + //start timer + m_Timer->start(1000 / (m_Controls->m_SamplingRate->value())); + + m_Controls->m_StartTracking->setEnabled(false); + m_Controls->m_StartTracking->setEnabled(true); + + m_tracking = true; +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::OnStopTracking() +{ + if (this->m_logging) FinishMeasurement(); + m_MeasurementTrackingDeviceSource->Disconnect(); + m_MeasurementTrackingDeviceSource->StopTracking(); + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + m_ReferenceTrackingDeviceSource->Disconnect(); + m_ReferenceTrackingDeviceSource->StopTracking(); + } + m_Timer->stop(); + m_Controls->m_StartTracking->setEnabled(true); + m_Controls->m_StartTracking->setEnabled(false); + m_tracking = false; +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::OnMeasurementLoadFile() +{ + m_FilenameVector = std::vector(); + m_FilenameVector.clear(); + m_NextFile = 0; + + //read in filename + QString filename = QFileDialog::getOpenFileName(NULL, tr("Open Measurement Filename List"), "/", tr("All Files (*.*)")); + if (filename.isNull()) return; + + //save old locale + char * oldLocale; + oldLocale = setlocale(LC_ALL, 0); + + //define own locale + std::locale C("C"); + setlocale(LC_ALL, "C"); + + //read file + std::ifstream file; + file.open(filename.toStdString().c_str(), std::ios::in); + if (file.good()) + { + //read out file + file.seekg(0L, std::ios::beg); // move to begin of file + while (!file.eof()) + { + std::string buffer; + std::getline(file, buffer); // read out file line by line + if (buffer.size() > 0) m_FilenameVector.push_back(buffer); + } + } + + //fill list at GUI + m_Controls->m_MeasurementList->clear(); + for (unsigned int i = 0; i < m_FilenameVector.size(); i++) { new QListWidgetItem(tr(m_FilenameVector.at(i).c_str()), m_Controls->m_MeasurementList); } + + //update label next measurement + std::stringstream label; + label << "Next Measurement: " << m_FilenameVector.at(0); + m_Controls->m_NextMeasurement->setText(label.str().c_str()); + + //reset results files + m_MeanPoints = mitk::PointSet::New(); + m_RMSValues = std::vector(); + m_EvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); + if (m_MeasurementToolVisualizationFilter.IsNotNull()) m_EvaluationFilter->SetInput(0, m_MeasurementToolVisualizationFilter->GetOutput(0)); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::UpdateTimer() +{ + if (m_EvaluationFilter.IsNotNull() && m_logging) m_EvaluationFilter->Update(); + else m_MeasurementToolVisualizationFilter->Update(); + + m_Controls->m_ToolStatusWidget->Refresh(); + + //update reference + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + m_ReferenceTrackingDeviceSource->Update(); + QString Label = "Current Positions: "; + bool distanceThresholdExceeded = false; + for (int i = 0; i < m_ReferenceTrackingDeviceSource->GetNumberOfOutputs(); i++) + { + mitk::Point3D position = m_ReferenceTrackingDeviceSource->GetOutput(i)->GetPosition(); + Label = Label + "Tool" + QString::number(i) + ":[" + QString::number(position[0]) + ":" + QString::number(position[1]) + ":" + QString::number(position[1]) + "] "; + if (position.EuclideanDistanceTo(m_ReferenceStartPositions.at(i)) > m_Controls->m_ReferenceThreshold->value()) distanceThresholdExceeded = true; + } + m_Controls->m_ReferenceCurrentPos->setText(Label); + if (distanceThresholdExceeded) + { + m_Controls->m_ReferenceOK->setText("NOT OK!"); + m_referenceValid = false; + } + else + { + m_Controls->m_ReferenceOK->setText("OK"); + m_referenceValid = true; + } + } + + //update logging + if (m_logging) + { + //check for missing objects + if (m_MeasurementLoggingFilterXML.IsNull() || + m_MeasurementLoggingFilterCSV.IsNull() + ) + { + return; + } + + //log/measure + m_MeasurementLoggingFilterXML->Update(); + m_MeasurementLoggingFilterCSV->Update(); + + if (m_Controls->m_UseReferenceTrackingSystem->isChecked() && + m_ReferenceLoggingFilterXML.IsNotNull() && + m_ReferenceLoggingFilterCSV.IsNotNull()) + { + m_ReferenceLoggingFilterXML->Update(); + m_ReferenceLoggingFilterCSV->Update(); + } + m_loggedFrames++; + LogAdditionalCSVFile(); + + //check if all frames are logged ... if yes finish the measurement + if (m_loggedFrames > m_Controls->m_SamplesPerMeasurement->value()) { FinishMeasurement(); } + + //update logging label + QString loggingLabel = "Collected Samples: " + QString::number(m_loggedFrames); + m_Controls->m_CollectedSamples->setText(loggingLabel); + } +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::StartNextMeasurement() +{ + if (this->m_NextFile >= m_FilenameVector.size()) + { + MessageBox("Last Measurement reached!"); + return; + } + + m_loggedFrames = 0; + m_logging = true; + + //check if directory exists, if not create one + Poco::File myPath(std::string(m_Controls->m_OutputPath->text().toUtf8()).c_str()); + if (!myPath.exists()) myPath.createDirectory(); + + QString LogFileName = m_Controls->m_OutputPath->text() + QString(m_FilenameVector.at(m_NextFile).c_str()) + ".log"; + mitk::LoggingBackend::Unregister(); + mitk::LoggingBackend::SetLogFile(LogFileName.toStdString().c_str()); + mitk::LoggingBackend::Register(); + + //initialize logging filters + m_MeasurementLoggingFilterXML = mitk::NavigationDataRecorderDeprecated::New(); + m_MeasurementLoggingFilterXML->SetRecordingMode(mitk::NavigationDataRecorderDeprecated::NormalFile); + m_MeasurementLoggingFilterCSV = mitk::NavigationDataRecorderDeprecated::New(); + m_MeasurementLoggingFilterCSV->SetRecordingMode(mitk::NavigationDataRecorderDeprecated::NormalFile); + m_MeasurementLoggingFilterXML->SetOutputFormat(mitk::NavigationDataRecorderDeprecated::xml); + m_MeasurementLoggingFilterCSV->SetOutputFormat(mitk::NavigationDataRecorderDeprecated::csv); + QString MeasurementFilenameXML = m_Controls->m_OutputPath->text() + QString(m_FilenameVector.at(m_NextFile).c_str()) + ".xml"; + QString MeasurementFilenameCSV = m_Controls->m_OutputPath->text() + QString(m_FilenameVector.at(m_NextFile).c_str()) + ".csv"; + m_MeasurementLoggingFilterXML->SetFileName(MeasurementFilenameXML.toStdString()); + m_MeasurementLoggingFilterCSV->SetFileName(MeasurementFilenameCSV.toStdString()); + m_MeasurementLoggingFilterXML->SetRecordCountLimit(m_Controls->m_SamplesPerMeasurement->value()); + m_MeasurementLoggingFilterCSV->SetRecordCountLimit(m_Controls->m_SamplesPerMeasurement->value()); + + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + m_ReferenceLoggingFilterXML = mitk::NavigationDataRecorderDeprecated::New(); + m_ReferenceLoggingFilterXML->SetRecordingMode(mitk::NavigationDataRecorderDeprecated::NormalFile); + m_ReferenceLoggingFilterCSV = mitk::NavigationDataRecorderDeprecated::New(); + m_ReferenceLoggingFilterCSV->SetRecordingMode(mitk::NavigationDataRecorderDeprecated::NormalFile); + m_ReferenceLoggingFilterXML->SetOutputFormat(mitk::NavigationDataRecorderDeprecated::xml); + m_ReferenceLoggingFilterCSV->SetOutputFormat(mitk::NavigationDataRecorderDeprecated::csv); + QString ReferenceFilenameXML = m_Controls->m_OutputPath->text() + "Reference_" + QString(m_FilenameVector.at(m_NextFile).c_str()) + ".xml"; + QString ReferenceFilenameCSV = m_Controls->m_OutputPath->text() + "Reference_" + QString(m_FilenameVector.at(m_NextFile).c_str()) + ".csv"; + m_ReferenceLoggingFilterXML->SetFileName(ReferenceFilenameXML.toStdString()); + m_ReferenceLoggingFilterCSV->SetFileName(ReferenceFilenameCSV.toStdString()); + m_ReferenceLoggingFilterXML->SetRecordCountLimit(m_Controls->m_SamplesPerMeasurement->value()); + m_ReferenceLoggingFilterCSV->SetRecordCountLimit(m_Controls->m_SamplesPerMeasurement->value()); + } + + //start additional csv logging + StartLoggingAdditionalCSVFile(m_FilenameVector.at(m_NextFile)); + + //connect filter + for (int i = 0; i < m_MeasurementToolVisualizationFilter->GetNumberOfOutputs(); i++) + { + m_MeasurementLoggingFilterXML->AddNavigationData(m_MeasurementToolVisualizationFilter->GetOutput(i)); + m_MeasurementLoggingFilterCSV->AddNavigationData(m_MeasurementToolVisualizationFilter->GetOutput(i)); + } + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) for (int i = 0; i < m_ReferenceTrackingDeviceSource->GetNumberOfOutputs(); i++) + { + m_ReferenceLoggingFilterXML->AddNavigationData(m_ReferenceTrackingDeviceSource->GetOutput(i)); + m_ReferenceLoggingFilterCSV->AddNavigationData(m_ReferenceTrackingDeviceSource->GetOutput(i)); + } + + //start filter + m_MeasurementLoggingFilterXML->StartRecording(); + m_MeasurementLoggingFilterCSV->StartRecording(); + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + m_ReferenceLoggingFilterXML->StartRecording(); + m_ReferenceLoggingFilterCSV->StartRecording(); + } + + //disable all buttons + DisableAllButtons(); + + //update label next measurement + std::stringstream label; + if ((m_NextFile + 1) >= m_FilenameVector.size()) label << "Next Measurement: "; + else label << "Next Measurement: " << m_FilenameVector.at(m_NextFile + 1); + m_Controls->m_NextMeasurement->setText(label.str().c_str()); + + //update label last measurement + std::stringstream label2; + label2 << "Last Measurement: " << m_FilenameVector.at(m_NextFile); + m_Controls->m_LastMeasurement->setText(label2.str().c_str()); + + m_NextFile++; +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::RepeatLastMeasurement() +{ + m_NextFile--; + StartNextMeasurement(); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::MessageBox(std::string s) +{ + QMessageBox msgBox; + msgBox.setText(s.c_str()); + msgBox.exec(); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::DisableAllButtons() +{ + m_Controls->m_LoadList->setEnabled(false); + m_Controls->m_StartNextMeasurement->setEnabled(false); + m_Controls->m_ReapeatLastMeasurement->setEnabled(false); + m_Controls->m_SamplingRate->setEnabled(false); + m_Controls->m_SamplesPerMeasurement->setEnabled(false); + m_Controls->m_ReferenceThreshold->setEnabled(false); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::EnableAllButtons() +{ + m_Controls->m_LoadList->setEnabled(true); + m_Controls->m_StartNextMeasurement->setEnabled(true); + m_Controls->m_ReapeatLastMeasurement->setEnabled(true); + m_Controls->m_SamplingRate->setEnabled(true); + m_Controls->m_SamplesPerMeasurement->setEnabled(true); + m_Controls->m_ReferenceThreshold->setEnabled(true); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::FinishMeasurement() +{ + m_logging = false; + + m_MeasurementLoggingFilterXML->StopRecording(); + m_MeasurementLoggingFilterCSV->StopRecording(); + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + m_ReferenceLoggingFilterXML->StopRecording(); + m_ReferenceLoggingFilterCSV->StopRecording(); + } + StopLoggingAdditionalCSVFile(); + + int id = m_NextFile - 1; + mitk::Point3D positionMean = m_EvaluationFilter->GetPositionMean(0); + MITK_INFO << "Evaluated " << m_EvaluationFilter->GetNumberOfAnalysedNavigationData(0) << " samples."; + double rms = m_EvaluationFilter->GetPositionErrorRMS(0); + MITK_INFO << "RMS: " << rms; + MITK_INFO << "Position Mean: " << positionMean; + m_MeanPoints->SetPoint(id, positionMean); + if (m_RMSValues.size() <= id) m_RMSValues.push_back(rms); + else m_RMSValues[id] = rms; + + m_EvaluationFilter->ResetStatistic(); + + EnableAllButtons(); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::StartLoggingAdditionalCSVFile(std::string filePostfix) +{ + //write logfile header + QString header = "Nr;MITK_Time;Valid_Reference;"; + QString tool = QString("MeasureTool_") + QString(m_MeasurementTrackingDeviceSource->GetOutput(0)->GetName()); + header = header + tool + "[x];" + tool + "[y];" + tool + "[z];" + tool + "[qx];" + tool + "[qy];" + tool + "[qz];" + tool + "[qr]\n"; + + //open logfile and write header + m_logFileCSV.open(std::string(m_Controls->m_OutputPath->text().toUtf8()).append("/LogFileCombined").append(filePostfix.c_str()).append(".csv").c_str(), std::ios::out); + m_logFileCSV << header.toStdString().c_str(); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::LogAdditionalCSVFile() +{ + mitk::Point3D pos = m_MeasurementTrackingDeviceSource->GetOutput(0)->GetPosition(); + mitk::Quaternion rot = m_MeasurementTrackingDeviceSource->GetOutput(0)->GetOrientation(); + std::string valid = ""; + if (m_referenceValid) valid = "true"; + else valid = "false"; + std::stringstream timestamp; + timestamp << m_MeasurementTrackingDeviceSource->GetOutput(0)->GetTimeStamp(); + QString dataSet = QString::number(m_loggedFrames) + ";" + QString(timestamp.str().c_str()) + ";" + QString(valid.c_str()) + ";" + QString::number(pos[0]) + ";" + QString::number(pos[1]) + ";" + QString::number(pos[2]) + ";" + QString::number(rot.x()) + ";" + QString::number(rot.y()) + ";" + QString::number(rot.z()) + ";" + QString::number(rot.r()) + "\n"; + m_logFileCSV << dataSet.toStdString(); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::StopLoggingAdditionalCSVFile() +{ + m_logFileCSV.close(); +} + +bool QmitkIGTTrackingSemiAutomaticMeasurementView::eventFilter(QObject *obj, QEvent *ev) +{ + if (ev->type() == QEvent::KeyPress) + { + QKeyEvent *k = (QKeyEvent *)ev; + bool up = false; + bool down = false; + if (k->key() == 16777238) up = true; //page up + else if (k->key() == 16777239) down = true; //page down + + if (down && m_tracking && !m_logging) + { + StartNextMeasurement(); + } + } + + return false; +} diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementView.h b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementView.h new file mode 100644 index 0000000000..3ec59713ba --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementView.h @@ -0,0 +1,132 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef QmitkIGTTrackingSemiAutomaticMeasurementView_h +#define QmitkIGTTrackingSemiAutomaticMeasurementView_h + +#include + +#include + +//QT +#include + +//MITK +#include +#include +#include +#include +#include + +#include "ui_QmitkIGTTrackingSemiAutomaticMeasurementViewControls.h" + +/*! + \brief QmitkIGTTrackingSemiAutomaticMeasurementView + + \warning This application module is not yet documented. Use "svn blame/praise/annotate" and ask the author to provide basic documentation. + + \sa QmitkFunctionality + \ingroup Functionalities + */ +class QmitkIGTTrackingSemiAutomaticMeasurementView : public QmitkFunctionality +{ + // this is needed for all Qt objects that should have a Qt meta-object + // (everything that derives from QObject and wants to have signal/slots) + Q_OBJECT + +public: + + static const std::string VIEW_ID; + + QmitkIGTTrackingSemiAutomaticMeasurementView(); + virtual ~QmitkIGTTrackingSemiAutomaticMeasurementView(); + + virtual void CreateQtPartControl(QWidget *parent); + + virtual void StdMultiWidgetAvailable(QmitkStdMultiWidget &stdMultiWidget); + virtual void StdMultiWidgetNotAvailable(); + + protected slots: + + void OnLoadMeasurementStorage(); + void OnLoadReferenceStorage(); + void OnStartTracking(); + void OnStopTracking(); + void OnMeasurementLoadFile(); + void OnSetReference(); + void StartNextMeasurement(); + void RepeatLastMeasurement(); + void UpdateTimer(); + void CreateResults(); + void OnUseReferenceToggled(bool state); + +protected: + + Ui::QmitkIGTTrackingSemiAutomaticMeasurementViewControls* m_Controls; + + QmitkStdMultiWidget* m_MultiWidget; + + //the tool storages + mitk::NavigationToolStorage::Pointer m_MeasurementStorage; + mitk::NavigationToolStorage::Pointer m_ReferenceStorage; + + //members for the filter pipeline + mitk::TrackingDeviceSource::Pointer m_MeasurementTrackingDeviceSource; + mitk::NavigationDataObjectVisualizationFilter::Pointer m_MeasurementToolVisualizationFilter; + mitk::NavigationDataRecorderDeprecated::Pointer m_MeasurementLoggingFilterXML; + mitk::NavigationDataRecorderDeprecated::Pointer m_MeasurementLoggingFilterCSV; + mitk::TrackingDeviceSource::Pointer m_ReferenceTrackingDeviceSource; + mitk::NavigationDataRecorderDeprecated::Pointer m_ReferenceLoggingFilterXML; + mitk::NavigationDataRecorderDeprecated::Pointer m_ReferenceLoggingFilterCSV; + + //members for file name list + std::vector m_FilenameVector; + int m_NextFile; + + //help methods + mitk::NavigationToolStorage::Pointer ReadStorage(std::string file); + void MessageBox(std::string s); + void DisableAllButtons(); + void EnableAllButtons(); + void FinishMeasurement(); + void StartLoggingAdditionalCSVFile(std::string filePostfix); + void LogAdditionalCSVFile(); + void StopLoggingAdditionalCSVFile(); + + //timer + QTimer* m_Timer; + + //memebers for reference checking + std::vector m_ReferenceStartPositions; + bool m_referenceValid; + + //logging members + int m_loggedFrames; + bool m_logging; + std::fstream m_logFileCSV; + + //event filter for key presses + bool eventFilter(QObject *obj, QEvent *ev); + + //results members + mitk::PointSet::Pointer m_MeanPoints; + std::vector m_RMSValues; + mitk::NavigationDataEvaluationFilter::Pointer m_EvaluationFilter; + + bool m_tracking; +}; + +#endif // _QMITKIGTTRACKINGSEMIAUTOMATICMEASUREMENTVIEW_H_INCLUDED diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementViewControls.ui b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementViewControls.ui new file mode 100644 index 0000000000..50168cb767 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementViewControls.ui @@ -0,0 +1,555 @@ + + + QmitkIGTTrackingSemiAutomaticMeasurementViewControls + + + + 0 + 0 + 419 + 931 + + + + + 0 + 0 + + + + QmitkTemplate + + + + + + 0 + + + + Tracking Initialization + + + + + + Measurement Tracking System + + + + + + + + + + + Tool Storage: <none> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Load Tool Storage + + + + + + + + + + + + Reference Trackingsystem + + + + + + + + + + + Tool Storage: <none> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Load Tool Storage + + + + + + + + + + + + Start Tracking + + + + + + + Stop Tracking + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Measurement + + + + + + Measurement List: + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Load List + + + + + + + + + + + Output Path: + + + + + + + C:/temp/ + + + + + + + + + Last Measurement: <none> + + + + + + + Next Measurement: <none> + + + + + + + Collected Samples: <none> + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 200 + 0 + + + + Start Next Measurement + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 200 + 0 + + + + Repeat Last Measurement + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 200 + 0 + + + + Create Results + + + + + + + + + Qt::Vertical + + + + 20 + 281 + + + + + + + + Qt::Horizontal + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600; text-decoration: underline;">Measurement Sensors:</span></p></body></html> + + + + + + + + + + Qt::Horizontal + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600; text-decoration: underline;">Reference Sensors:</span></p></body></html> + + + + + + + Set Reference + + + + + + + Reference Postion: <none> + + + + + + + Current Positions: <none> + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:28pt; font-weight:600;">OK</span></p></body></html> + + + Qt::AlignCenter + + + + + + + + Settings + + + + + + + + Sampling Rate (Times Per Second): + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 999.000000000000000 + + + 15.000000000000000 + + + + + + + + + + + Samples Per Measurement: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 1000 + + + 150 + + + + + + + + + + + Threshold For Reference Tools: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 1.000000000000000 + + + + + + + + + Use Reference Tracking System + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + QmitkToolTrackingStatusWidget + QWidget +
QmitkToolTrackingStatusWidget.h
+ 1 +
+ + QmitkTrackingDeviceConfigurationWidget + QWidget +
QmitkTrackingDeviceConfigurationWidget.h
+ 1 +
+
+ + +
diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkHummelProtocolEvaluation.cpp b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkHummelProtocolEvaluation.cpp new file mode 100644 index 0000000000..d37fb8f074 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkHummelProtocolEvaluation.cpp @@ -0,0 +1,384 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ +#define _USE_MATH_DEFINES +#include "mitkHummelProtocolEvaluation.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +bool mitk::HummelProtocolEvaluation::Evaluate15cmDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results) +{ + if (m != mitk::HummelProtocolEvaluation::standard) { MITK_WARN << "15 cm distances are only evaluated for standard volumes, aborting!"; return false; } + + MITK_INFO << "########### 15 cm distance errors #############"; + + //convert measurements to matrix + itk::Matrix, 9, 10> matrix = ParseMatrixStandardVolume(p); + + //these are the variables for the results: + std::vector distances; + std::vector descriptions; + + //evaluation of rows + int distanceCounter = 0; + for (int row = 0; row < 9; row++) //rows + for (int distance = 0; distance < 7; distance++) + { + distanceCounter++; + mitk::Point3D point1 = p->GetPoint(row * 10 + distance); + mitk::Point3D point2 = p->GetPoint(row * 10 + distance + 3); + distances.push_back(point1.EuclideanDistanceTo(point2)); + std::stringstream description; + description << "Distance(" << distanceCounter << ") " << (row + 1) << "/" << (distance + 1) << " to " << (row + 1) << "/" << (distance + 4); + descriptions.push_back(description.str()); + } + + //evaluation of columns + for (int column = 0; column < 10; column++) + for (int row = 0; row < 6; row++) + { + distanceCounter++; + mitk::Point3D point1 = matrix[row][column]; + mitk::Point3D point2 = matrix[row + 3][column]; + distances.push_back(point1.EuclideanDistanceTo(point2)); + std::stringstream description; + description << "Distance(" << distanceCounter << ") " << (row + 1) << "/" << (column + 1) << " to " << (row + 4) << "/" << (column + 1); + descriptions.push_back(description.str()); + } + + //compute all errors + for (int i = 0; i < distances.size(); i++) + { + HummelProtocolDistanceError currentError; + currentError.distanceError = abs(distances.at(i) - (double)150.0); + currentError.description = descriptions.at(i); + Results.push_back(currentError); + MITK_INFO << "Error " << currentError.description << " : " << currentError.distanceError; + } + + //compute statistics + std::vector statistics = mitk::HummelProtocolEvaluation::ComputeStatistics(Results); + for (auto currentError : statistics) + { + Results.push_back(currentError); + MITK_INFO << currentError.description << " : " << currentError.distanceError; + } + + return true; +} + +bool mitk::HummelProtocolEvaluation::Evaluate30cmDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results) +{ + if (m != mitk::HummelProtocolEvaluation::standard) { MITK_WARN << "30 cm distances are only evaluated for standard volumes, aborting!"; return false; } + MITK_INFO << "########### 30 cm distance errors #############"; + + //convert measurements to matrix + itk::Matrix, 9, 10> matrix = ParseMatrixStandardVolume(p); + + //these are the variables for the results: + std::vector distances; + std::vector descriptions; + + //evaluation of rows + int distanceCounter = 0; + for (int row = 0; row < 9; row++) //rows + for (int distance = 0; distance < 4; distance++) + { + distanceCounter++; + mitk::Point3D point1 = p->GetPoint(row * 10 + distance); + mitk::Point3D point2 = p->GetPoint(row * 10 + distance + 6); + distances.push_back(point1.EuclideanDistanceTo(point2)); + std::stringstream description; + description << "Distance(" << distanceCounter << ") " << (row + 1) << "/" << (distance + 1) << " to " << (row + 1) << "/" << (distance + 7); + descriptions.push_back(description.str()); + } + + //evaluation of columns + for (int column = 0; column < 10; column++) + for (int row = 0; row < 3; row++) + { + distanceCounter++; + mitk::Point3D point1 = matrix[row][column]; + mitk::Point3D point2 = matrix[row + 6][column]; + distances.push_back(point1.EuclideanDistanceTo(point2)); + std::stringstream description; + description << "Distance(" << distanceCounter << ") " << (row + 1) << "/" << (column + 1) << " to " << (row + 7) << "/" << (column + 1); + descriptions.push_back(description.str()); + } + + //compute all errors + for (int i = 0; i < distances.size(); i++) + { + HummelProtocolDistanceError currentError; + currentError.distanceError = abs(distances.at(i) - (double)300.0); + currentError.description = descriptions.at(i); + Results.push_back(currentError); + MITK_INFO << "Error " << currentError.description << " : " << currentError.distanceError; + } + + //compute statistics + std::vector statistics = mitk::HummelProtocolEvaluation::ComputeStatistics(Results); + for (auto currentError : statistics) + { + Results.push_back(currentError); + MITK_INFO << currentError.description << " : " << currentError.distanceError; + } + + return true; +} + +bool mitk::HummelProtocolEvaluation::EvaluateAccumulatedDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results) +{ + if (m != mitk::HummelProtocolEvaluation::standard) { MITK_WARN << "Accumulated distances are only evaluated for standard volumes, aborting!"; return false; } + //convert measurements to matrix + itk::Matrix, 9, 10> matrix = ParseMatrixStandardVolume(p); + + MITK_INFO << "########### accumulated distance errors #############"; + + int distanceCounter = 0; + + //evaluation of rows + for (int row = 0; row < 9; row++) //rows + for (int distance = 0; distance < 9; distance++) + { + distanceCounter++; + mitk::Point3D point1 = p->GetPoint(row * 10); + mitk::Point3D point2 = p->GetPoint(row * 10 + distance + 1); + std::stringstream description; + description << "Distance(" << distanceCounter << ") " << (row + 1) << "/1 to " << (row + 1) << "/" << (distance + 2); + //compute error + HummelProtocolDistanceError currentError; + currentError.distanceError = abs(point1.EuclideanDistanceTo(point2) - (double)(50.0*(distance+1))); + currentError.description = description.str(); + Results.push_back(currentError); + MITK_INFO << "Error " << currentError.description << " : " << currentError.distanceError; + } + + + + //compute statistics + std::vector statistics = mitk::HummelProtocolEvaluation::ComputeStatistics(Results); + for (auto currentError : statistics) + { + Results.push_back(currentError); + MITK_INFO << currentError.description << " : " << currentError.distanceError; + } + +return true; +} + + + +bool mitk::HummelProtocolEvaluation::Evaluate5cmDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results) +{ +MITK_INFO << "########### 5 cm distance errors #############"; +std::vector distances; +std::vector descriptions; +switch (m) +{ +case small: +if (p->GetSize() != 12) { +MITK_WARN << "Wrong number of points: " << p->GetSize() << " (expected 12), aborting"; +return false; +} +MITK_INFO << "Computing Hummel protocol distance errors for small measurement volumes (12 points)..."; + +//row 1 +distances.push_back(p->GetPoint(0).EuclideanDistanceTo(p->GetPoint(1))); //0 +descriptions.push_back("Distance 4/4 to 4/5"); +distances.push_back(p->GetPoint(1).EuclideanDistanceTo(p->GetPoint(2))); //1 +descriptions.push_back("Distance 4/5 to 4/6"); +distances.push_back(p->GetPoint(2).EuclideanDistanceTo(p->GetPoint(3))); //2 +descriptions.push_back("Distance 4/6 to 4/7"); +//row 2 +distances.push_back(p->GetPoint(4).EuclideanDistanceTo(p->GetPoint(5))); //3 +descriptions.push_back("Distance 5/4 to 5/5"); +distances.push_back(p->GetPoint(5).EuclideanDistanceTo(p->GetPoint(6))); //4 +descriptions.push_back("Distance 5/5 to 5/6"); +distances.push_back(p->GetPoint(6).EuclideanDistanceTo(p->GetPoint(7))); //5 +descriptions.push_back("Distance 5/6 to 5/7"); +//row 3 +distances.push_back(p->GetPoint(8).EuclideanDistanceTo(p->GetPoint(9))); //6 +descriptions.push_back("Distance 6/4 to 6/5"); +distances.push_back(p->GetPoint(9).EuclideanDistanceTo(p->GetPoint(10))); //7 +descriptions.push_back("Distance 6/5 to 6/6"); +distances.push_back(p->GetPoint(10).EuclideanDistanceTo(p->GetPoint(11))); //8 +descriptions.push_back("Distance 6/6 to 6/7"); +//column 1 +distances.push_back(p->GetPoint(0).EuclideanDistanceTo(p->GetPoint(4))); //9 +descriptions.push_back("Distance 4/4 to 5/4"); +distances.push_back(p->GetPoint(4).EuclideanDistanceTo(p->GetPoint(8))); //10 +descriptions.push_back("Distance 5/4 to 6/4"); +//column 2 +distances.push_back(p->GetPoint(1).EuclideanDistanceTo(p->GetPoint(5))); //11 +descriptions.push_back("Distance 4/5 to 5/5"); +distances.push_back(p->GetPoint(5).EuclideanDistanceTo(p->GetPoint(9))); //12 +descriptions.push_back("Distance 5/5 to 6/5"); +//column 3 +distances.push_back(p->GetPoint(2).EuclideanDistanceTo(p->GetPoint(6))); //13 +descriptions.push_back("Distance 4/6 to 5/6"); +distances.push_back(p->GetPoint(6).EuclideanDistanceTo(p->GetPoint(10))); //14 +descriptions.push_back("Distance 5/6 to 6/6"); +//column 4 +distances.push_back(p->GetPoint(3).EuclideanDistanceTo(p->GetPoint(7))); //15 +descriptions.push_back("Distance 4/7 to 5/7"); +distances.push_back(p->GetPoint(7).EuclideanDistanceTo(p->GetPoint(11))); //16 +descriptions.push_back("Distance 5/7 to 6/7"); + +break; + +case standard: +if (p->GetSize() != 90) { +MITK_WARN << "Wrong number of points (expected 90), aborting"; +return false; +} +MITK_INFO << "Computing Hummel protocol distance errors for standard measurement volumes (90 points)..."; + +int distanceCounter = 0; + +//convert measurements to matrix +itk::Matrix, 9, 10> matrix = ParseMatrixStandardVolume(p); + +//evaluation of rows +for (int row = 0; row < 9; row++) //rows + for (int distance = 0; distance < 9; distance++) + { + distanceCounter++; + mitk::Point3D point1 = p->GetPoint(row*10 + distance); + mitk::Point3D point2 = p->GetPoint(row*10 + distance+1); + distances.push_back(point1.EuclideanDistanceTo(point2)); + std::stringstream description; + description << "Distance(" << distanceCounter << ") " << (row + 1) << "/" << (distance + 1) << " to " << (row + 1) << "/" << (distance + 2); + descriptions.push_back(description.str()); + } + +//evaluation of columns +for (int column = 0; column < 10; column++) + for (int row = 0; row < 8; row++) + { + distanceCounter++; + mitk::Point3D point1 = matrix[row][column]; + mitk::Point3D point2 = matrix[row+1][column]; + distances.push_back(point1.EuclideanDistanceTo(point2)); + std::stringstream description; + description << "Distance(" << distanceCounter << ") " << (row+1 )<< "/" << (column+1) << " to " << (row + 2) << "/" << (column + 1); + descriptions.push_back(description.str()); + } + +break; +} + +//compute all errors +for (int i = 0; i < distances.size(); i++) +{ +HummelProtocolDistanceError currentError; +currentError.distanceError = abs(distances.at(i) - (double)50.0); +currentError.description = descriptions.at(i); +Results.push_back(currentError); +MITK_INFO << "Error " << currentError.description << " : " << currentError.distanceError; +} + +//compute statistics +std::vector statistics = mitk::HummelProtocolEvaluation::ComputeStatistics(Results); +for (auto currentError : statistics) +{ + Results.push_back(currentError); + MITK_INFO << currentError.description << " : " << currentError.distanceError; +} + +return true; +} + +itk::Matrix, 9, 10> mitk::HummelProtocolEvaluation::ParseMatrixStandardVolume(mitk::PointSet::Pointer p) +{ + itk::Matrix, 9, 10> returnValue = itk::Matrix, 9, 10>(); + if (p->GetSize() != 90) + { + MITK_WARN << "PointSet does not have the right size. Expected 90 got " << p->GetSize() << " ... aborting!"; + return returnValue; + } + for (int row = 0; row < 9; row++) + for (int column = 0; column < 10; column++) + returnValue[row][column] = p->GetPoint(row * 10 + column); +} + +std::vector mitk::HummelProtocolEvaluation::ComputeStatistics(std::vector values) +{ + std::vector returnValue; + + //convert input values to boost / using boost accumulators for statistics + boost::accumulators::accumulator_set + > > acc; + for (mitk::HummelProtocolEvaluation::HummelProtocolDistanceError each : values) + { + acc(each.distanceError); + } + + returnValue.push_back({ values.size(), "N" }); + returnValue.push_back({ boost::accumulators::mean(acc), "Mean" }); + //double quantile25th = boost::accumulators::quantile(acc, boost::accumulators::quantile_probability = 0.25); + //returnValue.push_back({ boost::accumulators::median(acc), "Median" }); + //returnValue.push_back({ boost::accumulators::variance(acc), "Variance" }); + returnValue.push_back({ boost::accumulators::min(acc), "Min" }); + returnValue.push_back({ boost::accumulators::max(acc), "Max" }); + + //don't get the boost stuff working correctly, so computing the quantiles, median and standard deviation by myself: + std::vector quantile; + for (mitk::HummelProtocolEvaluation::HummelProtocolDistanceError each : values) + {quantile.push_back(each.distanceError);} + + auto const Q1 = quantile.size() / 4; + auto const Q2 = quantile.size() / 2; + auto const Q3 = Q1 + Q2; + + std::sort(quantile.begin(),quantile.end()); + + returnValue.push_back({ quantile[Q1], "Quartile 1" }); + returnValue.push_back({ quantile[Q2], "Median" }); + returnValue.push_back({ quantile[Q3], "Quartile 3" }); + + double mean = boost::accumulators::mean(acc); + double errorSum = 0; + for (mitk::HummelProtocolEvaluation::HummelProtocolDistanceError each : values) + { + double error = pow((each.distanceError - mean),2); + errorSum += error; + } + double variance = errorSum / values.size(); + double sampleVariance = errorSum / (values.size()-1); + double standardDev = sqrt(variance); + double sampleStandardDev = sqrt(sampleVariance); + + returnValue.push_back({ variance, "Variance" }); + returnValue.push_back({ sampleVariance, "Sample Variance" }); + returnValue.push_back({ standardDev, "Standard Deviation" }); + returnValue.push_back({ sampleStandardDev, "Sample Standard Deviation" }); + + + return returnValue; + +} diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkHummelProtocolEvaluation.h b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkHummelProtocolEvaluation.h new file mode 100644 index 0000000000..9f20434e89 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkHummelProtocolEvaluation.h @@ -0,0 +1,86 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + + +#ifndef MITKHummelProtocolEvaluation_H_HEADER_INCLUDED_ +#define MITKHummelProtocolEvaluation_H_HEADER_INCLUDED_ + +#include +#include + + + +namespace mitk +{ + + /**Documentation + * \brief Static methods for evaluations according to the assessment protocol + * for EM trackers published by Hummel et al. 2005 [1]. + * + * [1] Hummel, J. et al. - Design and application of an assessment protocol for electromagnetic tracking systems. Med Phys 32(7), July 2005 + * + * \ingroup IGT + */ + class HummelProtocolEvaluation + + { + public: + /** Distance error with description. */ + struct HummelProtocolDistanceError {double distanceError; std::string description;}; + /** Tracking volumes for evaluation. + * standard: The standard volume of 9 x 10 measurment points as described in [1] + * small: A small volume in the center 3 x 4 measurement points, for smaller field generators [2] + * [2] Maier-Hein, L. et al. - Standardized assessment of new electromagnetic field generators in an interventional radiology setting. Med Phys 39(6), June 2012 + */ + enum HummelProtocolMeasurementVolume { small, standard }; + /** Evaluates the 5 cm distances as defined by the Hummel protocol [1,2]. + * @return Returns true if evaluation was successfull, false if not. + * @param[out] Results Please give an empty vector. The results will be added to this vector. + */ + static bool Evaluate5cmDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results); + + /** Evaluates the 15 cm distances as defined by the Hummel protocol [1,2]. + * @return Returns true if evaluation was successfull, false if not. + * @param[out] Results Please give an empty vector. The results will be added to this vector. + */ + static bool Evaluate15cmDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results); + + /** Evaluates the 30 cm distances as defined by the Hummel protocol [1,2]. + * @return Returns true if evaluation was successfull, false if not. + * @param[out] Results Please give an empty vector. The results will be added to this vector. + */ + static bool Evaluate30cmDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results); + + /** Evaluates the accumulated distances as defined by the Hummel protocol [1,2]. + * @return Returns true if evaluation was successfull, false if not. + * @param[out] Results Please give an empty vector. The results will be added to this vector. + */ + static bool EvaluateAccumulatedDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results); + + /** Computes statistics (as mean, standard deviation, quantiles, min, max, etc.) on the given values. + * The results are stored inside the return value. + */ + static std::vector ComputeStatistics(std::vector values); + + protected: + /** Converts a pointset holding all measurement points of the hummel protocol in line-by-line order + * to an array representing the hummel board. + */ + static itk::Matrix, 9, 10> ParseMatrixStandardVolume(mitk::PointSet::Pointer p); + }; +} // namespace mitk + +#endif /* MITKHummelProtocolEvaluation_H_HEADER_INCLUDED_ */ diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkNavigationDataCSVSequentialPlayer.cpp b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkNavigationDataCSVSequentialPlayer.cpp new file mode 100644 index 0000000000..d03d7afcf1 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkNavigationDataCSVSequentialPlayer.cpp @@ -0,0 +1,325 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ +#define _USE_MATH_DEFINES +#include "mitkNavigationDataCSVSequentialPlayer.h" +#include +#include +#include +#include +#include + +mitk::NavigationDataCSVSequentialPlayer::NavigationDataCSVSequentialPlayer() + : mitk::NavigationDataPlayerBase() +{ + m_NavigationDatas = std::vector(); + m_CurrentPos = 0; + m_Filetype = mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV; +} + +mitk::NavigationDataCSVSequentialPlayer::~NavigationDataCSVSequentialPlayer() +{ +} + +bool mitk::NavigationDataCSVSequentialPlayer::IsAtEnd() +{ + if (m_CurrentPos >= m_NavigationDatas.size()) return true; + else return false; +} + +void mitk::NavigationDataCSVSequentialPlayer:: +SetFileName(const std::string& fileName) +{ + this->SetNumberOfOutputs(1); + FillOutputEmpty(0); + + MITK_INFO << "Reading file: " << fileName; + m_NavigationDatas = GetNavigationDatasFromFile(fileName); + + this->Modified(); +} + +void mitk::NavigationDataCSVSequentialPlayer::FillOutputEmpty(int number) +{ + this->SetNthOutput(number, GetEmptyNavigationData()); +} + +mitk::NavigationData::Pointer mitk::NavigationDataCSVSequentialPlayer::GetEmptyNavigationData() +{ + mitk::NavigationData::Pointer emptyNd = mitk::NavigationData::New(); + mitk::NavigationData::PositionType position; + mitk::NavigationData::OrientationType orientation(0.0, 0.0, 0.0, 0.0); + position.Fill(0.0); + + emptyNd->SetPosition(position); + emptyNd->SetOrientation(orientation); + emptyNd->SetDataValid(false); + return emptyNd; +} +int mitk::NavigationDataCSVSequentialPlayer::GetNumberOfSnapshots() +{ + return m_NavigationDatas.size(); +} +void mitk::NavigationDataCSVSequentialPlayer::GenerateData() +{ + for (unsigned int index = 0; index < this->GetNumberOfOutputs(); index++) + { + mitk::NavigationData* output = this->GetOutput(index); + + if (m_CurrentPos > m_NavigationDatas.size()) + { + FillOutputEmpty(index); + return; + } + + output->Graft(this->m_NavigationDatas.at(m_CurrentPos)); + m_CurrentPos++; + } +} + +void mitk::NavigationDataCSVSequentialPlayer::UpdateOutputInformation() +{ + this->Modified(); // make sure that we need to be updated + Superclass::UpdateOutputInformation(); +} + +std::vector mitk::NavigationDataCSVSequentialPlayer::GetNavigationDatasFromFile(std::string filename) +{ + std::vector returnValue = std::vector(); + std::vector fileContentLineByLine = GetFileContentLineByLine(filename); + for (int i = 1; (i < fileContentLineByLine.size()); i++) //skip header so start at 1 + { + returnValue.push_back(GetNavigationDataOutOfOneLine(fileContentLineByLine.at(i))); + } + + return returnValue; +} + +std::vector mitk::NavigationDataCSVSequentialPlayer::GetFileContentLineByLine(std::string filename) +{ + std::vector readData = std::vector(); + + //save old locale + char * oldLocale; + oldLocale = setlocale(LC_ALL, 0); + + //define own locale + std::locale C("C"); + setlocale(LC_ALL, "C"); + + //read file + std::ifstream file; + file.open(filename.c_str(), std::ios::in); + if (file.good()) + { + //read out file + file.seekg(0L, std::ios::beg); // move to begin of file + + int count = 0; + //int count2 = 0; + while (!file.eof()) + { + std::string buffer; + std::getline(file, buffer); // read out file line by line + + //for Polhemus tracker: just take every 24th sample + if (count == 0) if (buffer.size() > 0) + { + //MITK_INFO << "read(" << count2 << "): " << buffer.substr(0,30); + //count2++; + readData.push_back(buffer); + } + + count++; if (count == 24) count = 0; + } + } + + file.close(); + + //switch back to old locale + setlocale(LC_ALL, oldLocale); + + return readData; +} + +mitk::NavigationData::Pointer mitk::NavigationDataCSVSequentialPlayer::GetNavigationDataOutOfOneLine(std::string line) +{ + mitk::NavigationData::Pointer returnValue = mitk::NavigationData::New(); + + QString myLine = QString(line.c_str()); + + QStringList myLineList = myLine.split(','); + + mitk::Point3D position; + mitk::Quaternion orientation; + bool valid = false; + double time; + + //this is for custom csv files. You have adapt the column numbers to correctly + //interpret your csv file. + if (m_Filetype = mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV) + { + if (myLineList.size() < 10) + { + MITK_ERROR << "Error: cannot read line: only found " << myLineList.size() << " fields. Last field: " << myLineList.at(myLineList.size() - 1).toStdString(); + returnValue = GetEmptyNavigationData(); + return returnValue; + } + + valid = true; //if no valid flag is given: simply set to true + + + //############# Variant for the Aurora measurements ############### + //############# (CUSTOM .csv files from MITK) ############### + /* + position[0] = myLineList.at(3).toDouble(); + position[1] = myLineList.at(4).toDouble(); + position[2] = myLineList.at(5).toDouble(); + + orientation[0] = myLineList.at(6).toDouble(); //qx + orientation[1] = myLineList.at(7).toDouble(); //qy + orientation[2] = myLineList.at(8).toDouble(); //qz + orientation[3] = myLineList.at(9).toDouble(); //qr + */ + + //Variant for the polhemus measurements in August 2016 + //(.csv files from the polhemus software) + + //Important: due to the documentation, Polhemus uses + //a left handed coordinate system while MITK uses a + //right handed. A conversion is not included in this + //read in method yet, because this is not required + //for this special rotation evaliation (no matter + //if it turns 11.25 degree to left or right). For + //other usage this might be important to adapt! + + + position[0] = myLineList.at(4).toDouble(); + position[1] = myLineList.at(5).toDouble(); + position[2] = myLineList.at(6).toDouble(); + + + //Doesn't work... don't know how to interpret the + //Polhemus quaternions. They are seem to different + //different to other quaternions (NDI, Claron, etc.) + //http://www.mathepedia.de/Quaternionen.aspx + + /* + double qr = myLineList.at(7).toDouble(); + double qx = myLineList.at(8).toDouble(); + double qy = myLineList.at(9).toDouble(); + double qz = myLineList.at(10).toDouble(); + + vnl_quaternion newQuat(qx, qy, qz, qr); + + orientation = newQuat; + orientation.normalize();*/ + + /* + orientation[3] = qr; //qr + orientation[0] = qx; //qx + orientation[1] = qy; //qy + orientation[2] = qz; //qz + + orientation.normalize(); + */ + + + + //Using Euler angles instead does work + //azimuth: rotation about Z axis of reference frame + double azimuthAngle = (myLineList.at(11).toDouble() / 180 * M_PI); + //elevation: rotation about Y' axis (transformed Y axis of sonsor frame) + double elevationAngle = (myLineList.at(12).toDouble() / 180 * M_PI); + //roll: rotation about X axis of sensor frame + double rollAngle = (myLineList.at(13).toDouble() / 180 * M_PI); + vnl_quaternion eulerQuat(rollAngle, elevationAngle, azimuthAngle); + orientation = eulerQuat; + + + /* + //code block for conversion from axis-angular representation + double rotationAngle = myLineList.at(7).toDouble(); + double rotationAxis[3]; + rotationAxis[0] = myLineList.at(8).toDouble(); + rotationAxis[1] = myLineList.at(9).toDouble(); + rotationAxis[2] = myLineList.at(10).toDouble(); + + double betragRotationAxis = sqrt(pow(rotationAxis[0], 2) + pow(rotationAxis[1], 2) + pow(rotationAxis[2], 2)); + rotationAngle /= betragRotationAxis; + rotationAxis[0] /= betragRotationAxis; + rotationAxis[1] /= betragRotationAxis; + rotationAxis[2] /= betragRotationAxis; + + + double qr = cos(rotationAngle/2); + double qx = rotationAxis[0] * sin(rotationAngle/2); + double qy = rotationAxis[1] * sin(rotationAngle/2); + double qz = rotationAxis[1] * sin(rotationAngle/2); + */ + + + + /* + //code block for conversion from left-handed to right-handed + mitk::Quaternion linksZuRechtsdrehend; + double rotationAngle = -M_PI; + double rotationAxis[3]; + rotationAxis[0] = 0; + rotationAxis[1] = 0; + rotationAxis[2] = 1; + + linksZuRechtsdrehend[3] = cos(rotationAngle / 2); + linksZuRechtsdrehend[0] = rotationAxis[0] * sin(rotationAngle / 2); + linksZuRechtsdrehend[1] = rotationAxis[1] * sin(rotationAngle / 2); + linksZuRechtsdrehend[2] = rotationAxis[2] * sin(rotationAngle / 2); + + orientation = orientation * linksZuRechtsdrehend; + */ + + } + //this is for MITK csv files that have been recorded with the MITK + //navigation data recorder. You can also use the navigation data player + //class from the MITK-IGT module instead. + else if (m_Filetype = mitk::NavigationDataCSVSequentialPlayer::NavigationDataCSV) + { + if (myLineList.size() < 8) + { + MITK_ERROR << "Error: cannot read line: only found " << myLineList.size() << " fields. Last field: " << myLineList.at(myLineList.size() - 1).toStdString(); + returnValue = GetEmptyNavigationData(); + return returnValue; + } + + time = myLineList.at(2).toDouble(); + + if (myLineList.at(3).toStdString() == "1") valid = true; + + position[0] = myLineList.at(2).toDouble(); + position[1] = myLineList.at(3).toDouble(); + position[2] = myLineList.at(4).toDouble(); + + orientation[0] = myLineList.at(5).toDouble(); //qx + orientation[1] = myLineList.at(6).toDouble(); //qy + orientation[2] = myLineList.at(7).toDouble(); //qz + orientation[3] = myLineList.at(8).toDouble(); //qr + } + + //returnValue->SetTimeStamp(time); //DOES NOT WORK ANY MORE... CANNOT SET TIME TO itk::timestamp CLASS + returnValue->SetDataValid(valid); + returnValue->SetPosition(position); + returnValue->SetOrientation(orientation); + + return returnValue; +} diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkNavigationDataCSVSequentialPlayer.h b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkNavigationDataCSVSequentialPlayer.h new file mode 100644 index 0000000000..011e75f06b --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkNavigationDataCSVSequentialPlayer.h @@ -0,0 +1,114 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + + +#ifndef MITKNavigationDataCSVSequentialPlayer_H_HEADER_INCLUDED_ +#define MITKNavigationDataCSVSequentialPlayer_H_HEADER_INCLUDED_ + +#include +#include "tinyxml.h" + + +namespace mitk +{ + + /**Documentation + * \brief This class is a NavigationDataPlayer which can play CSV formatted + * files in sequential order, which means it doesn't care about timestamps and just + * outputs the navigationdatas in their sequential order. + * + * It is thought to interpret custom csv files. To do so please adapt the column + * numbers of position and orientation in the internal method GetNavigationDataOutOfOneLine(). + * + * So far only one (the first) tool is read in as required for the hummel protocol measurements. + * + * This class can also interpret MITK style csv files (set filetype to NavigationDataCSV), but + * you can also use the MITK navigation data player class inside the MITK-IGT module which + * is newer and better maintained. + * + * \ingroup IGT + */ + class NavigationDataCSVSequentialPlayer + : public NavigationDataPlayerBase + { + public: + + mitkClassMacro(NavigationDataCSVSequentialPlayer, NavigationDataPlayerBase); + itkNewMacro(Self); + + /** + * \brief sets the file name and path (if XMLString is set, this is neglected) + */ + void SetFileName(const std::string& _FileName); + + /** + * \brief returns the file name and path + */ + itkGetStringMacro(FileName); + + enum Filetype + { + NavigationDataCSV, //for csv files from the MITK navigation data player + ManualLoggingCSV //for custum csv files + }; + /** + * \brief Sets the file type. ManualLoggingCSV is default and is thought for your + * custom csv files. You can also set it to NavigationDataCSV, then this + * player interprets MITK style csv files. + */ + itkSetMacro(Filetype, Filetype); + + /** + * \return Returns true if the player reached the end of the file. + */ + bool IsAtEnd(); + + /** + * \brief Used for pipeline update just to tell the pipeline + * that we always have to update + */ + virtual void UpdateOutputInformation(); + + int mitk::NavigationDataCSVSequentialPlayer::GetNumberOfSnapshots(); + + protected: + NavigationDataCSVSequentialPlayer(); + virtual ~NavigationDataCSVSequentialPlayer(); + + /// + /// do the work here + /// + virtual void GenerateData(); + + std::string m_FileName; + + int m_CurrentPos; + Filetype m_Filetype; + + //member for the navigation datas which were read (only one output is supported at the moment) + std::vector m_NavigationDatas; + + std::vector GetNavigationDatasFromFile(std::string filename); + std::vector GetFileContentLineByLine(std::string filename); + mitk::NavigationData::Pointer GetNavigationDataOutOfOneLine(std::string line); + + void FillOutputEmpty(int number); + mitk::NavigationData::Pointer GetEmptyNavigationData(); + + }; +} // namespace mitk + +#endif /* MITKNavigationDataCSVSequentialPlayer_H_HEADER_INCLUDED_ */ diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkPluginActivator.cpp b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkPluginActivator.cpp new file mode 100644 index 0000000000..daf9692487 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkPluginActivator.cpp @@ -0,0 +1,39 @@ +/*========================================================================= + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +=========================================================================*/ + +#include "mitkPluginActivator.h" + +#include + +#include "QmitkIGTTrackingSemiAutomaticMeasurementView.h" +#include "QmitkIGTTrackingDataEvaluationView.h" + +namespace mitk { + void PluginActivator::start(ctkPluginContext* context) + { + BERRY_REGISTER_EXTENSION_CLASS(QmitkIGTTrackingSemiAutomaticMeasurementView, context) + BERRY_REGISTER_EXTENSION_CLASS(QmitkIGTTrackingDataEvaluationView, context) + } + + void PluginActivator::stop(ctkPluginContext* context) + { + Q_UNUSED(context) + } +} + +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +Q_EXPORT_PLUGIN2(org_mitk_gui_qt_igttrackingsemiautomaticmeasurement, mitk::PluginActivator) +#endif diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkPluginActivator.h b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkPluginActivator.h new file mode 100644 index 0000000000..08f307e628 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkPluginActivator.h @@ -0,0 +1,43 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + + +#ifndef MITKPLUGINACTIVATOR_H +#define MITKPLUGINACTIVATOR_H + +#include + +namespace mitk { + +class PluginActivator : + public QObject, public ctkPluginActivator +{ + Q_OBJECT + #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + Q_PLUGIN_METADATA(IID "org_mitk_gui_qt_igtapphummelprotocolmeasurements") + #endif + Q_INTERFACES(ctkPluginActivator) + +public: + + void start(ctkPluginContext* context); + void stop(ctkPluginContext* context); + +}; // PluginActivator + +} + +#endif // MITKPLUGINACTIVATOR_H