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 index 12da728a93..d148ecc33f 100644 --- 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 @@ -1,1169 +1,1174 @@ /*========================================================================= 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 <algorithm> // Blueberry #include <berryISelectionService.h> #include <berryIWorkbenchWindow.h> // Qmitk #include "QmitkIGTTrackingDataEvaluationView.h" #include "QmitkStdMultiWidget.h" // Qt #include <QMessageBox> #include <qfiledialog.h> #include <qstringlist.h> // MITK #include "mitkNavigationDataCSVSequentialPlayer.h" #include <mitkNavigationDataRecorderDeprecated.h> #include <mitkQuaternionAveraging.h> #include <mitkTransform.h> #include <mitkStaticIGTHelperFunctions.h> #include <mitkNodePredicateDataType.h> //ITK #include <itksys/SystemTools.hxx> //VNL #include <vnl/vnl_vector.h> //vtk headers #include <vtkPoints.h> #include <vtkSmartPointer.h> #include <vtkLandmarkTransform.h> const std::string QmitkIGTTrackingDataEvaluationView::VIEW_ID = "org.mitk.views.igttrackingdataevaluation"; QmitkIGTTrackingDataEvaluationView::QmitkIGTTrackingDataEvaluationView() : QmitkFunctionality() , m_Controls(0) , m_MultiWidget(nullptr) , m_scalingfactor(1) { m_CSVtoXMLInputFilenameVector = std::vector<std::string>(); m_CSVtoXMLOutputFilenameVector = std::vector<std::string>(); } 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<double> rotationVec; //adapt for Aurora 5D tools: [0,0,1000] rotationVec[0] = m_Controls->m_rotVecX->value(); //X rotationVec[1] = m_Controls->m_rotVecY->value(); //Y rotationVec[2] = m_Controls->m_rotVecZ->value(); //Z std::vector<mitk::HummelProtocolEvaluation::HummelProtocolDistanceError> allOrientationErrors; for (std::vector<mitk::HummelProtocolEvaluation::HummelProtocolDistanceError>::size_type i = 0; i < OrientationVector.size() - 1; ++i) { double AngleBetweenTwoQuaternions = mitk::StaticIGTHelperFunctions::GetAngleBetweenTwoQuaterions(OrientationVector.at(i), OrientationVector.at(i+1), rotationVec); double AngularError = fabs(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<mitk::HummelProtocolEvaluation::HummelProtocolDistanceError> 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<mitk::PointSet*>(m_Controls->m_ReferencePointSetComboBox->GetSelectedNode()->GetData()); mitk::PointSet::Pointer measurement = dynamic_cast<mitk::PointSet*>(m_Controls->m_MeasurementPointSetComboBox->GetSelectedNode()->GetData()); //convert point sets to vtk poly data vtkSmartPointer<vtkPoints> sourcePoints = vtkSmartPointer<vtkPoints>::New(); vtkSmartPointer<vtkPoints> targetPoints = vtkSmartPointer<vtkPoints>::New(); for (int i = 0; i<reference->GetSize(); 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<vtkLandmarkTransform> transform = vtkSmartPointer<vtkLandmarkTransform>::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<float, 3, 3> rotationFloat = itk::Matrix<float, 3, 3>(); itk::Vector<float, 3> translationFloat = itk::Vector<float, 3>(); itk::Matrix<double, 3, 3> rotationDouble = itk::Matrix<double, 3, 3>(); itk::Vector<double, 3> translationDouble = itk::Vector<double, 3>(); vtkSmartPointer<vtkMatrix4x4> 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<mitk::HummelProtocolEvaluation::HummelProtocolDistanceError> 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 (std::size_t i = 0; i < m_FilenameVector.size(); ++i) { //create navigation data player mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = ConstructNewNavigationDataPlayer(); myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); myPlayer->SetFileName(m_FilenameVector[i]); //check if the stream is valid and skip file if not //create evaluation filter mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); //connect pipeline for (unsigned int j = 0; j < myPlayer->GetNumberOfOutputs(); ++j) myEvaluationFilter->SetInput(j, myPlayer->GetOutput(j)); //update pipline until number of samples 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 (std::size_t i = 0; i < m_FilenameVector.size(); ++i) { //create navigation data player mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = ConstructNewNavigationDataPlayer(); myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); myPlayer->SetFileName(m_FilenameVector.at(i)); //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 { vtkSmartPointer<vtkLandmarkTransform> transform = vtkSmartPointer<vtkLandmarkTransform>::New(); vtkSmartPointer<vtkPoints> sourcePoints = vtkSmartPointer<vtkPoints>::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<vtkPoints> targetPoints = vtkSmartPointer<vtkPoints>::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 = nullptr; } void QmitkIGTTrackingDataEvaluationView::OnAddToCurrentList() { //read in files QStringList files = QFileDialog::getOpenFileNames(nullptr, "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); } //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<std::string>(); m_FilenameVector.clear(); OnAddToCurrentList(); } void QmitkIGTTrackingDataEvaluationView::OnEvaluateDataAll() { std::vector<mitk::HummelProtocolEvaluation::HummelProtocolDistanceError> 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 if (m_Controls->m_smallVolume->isChecked()) { volume = mitk::HummelProtocolEvaluation::small; mitk::HummelProtocolEvaluation::Evaluate5cmDistances(m_PointSetMeanPositions, volume, results5cm); } else if (m_Controls->m_mediumVolume->isChecked()) { volume = mitk::HummelProtocolEvaluation::medium; mitk::HummelProtocolEvaluation::Evaluate5cmDistances(m_PointSetMeanPositions, volume, results5cm); } + else if (m_Controls->m_medium5x6Volume->isChecked()) + { + volume = mitk::HummelProtocolEvaluation::medium5x6; + 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<mitk::HummelProtocolEvaluation::HummelProtocolDistanceError> jitterValues; //write output file header WriteHeader(); //start loop and iterate through all files of list for (std::size_t i = 0; i < m_FilenameVector.size(); ++i) { //create navigation data player mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = ConstructNewNavigationDataPlayer(); myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); myPlayer->SetFileName(m_FilenameVector.at(i)); //create evaluation filter mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); //connect pipeline for (unsigned int j = 0; j < myPlayer->GetNumberOfOutputs(); ++i) { 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(); //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<mitk::HummelProtocolEvaluation::HummelProtocolDistanceError> 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 (std::size_t 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 = ConstructNewNavigationDataPlayer(); myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); myPlayer->SetFileName(m_FilenameVector.at(i)); //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 (std::size_t i = 0; i < m_FilenameVector.size(); ++i) { //create navigation data player mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = ConstructNewNavigationDataPlayer(); myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); myPlayer->SetFileName(m_FilenameVector.at(i)); //create evaluation filter mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); //connect pipeline for (unsigned 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 (std::size_t i = 0; i < m_FilenameVector.size(); ++i) { //create navigation data player mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = ConstructNewNavigationDataPlayer(); myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); myPlayer->SetFileName(m_FilenameVector.at(i)); //create evaluation filter mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); //connect pipeline for (unsigned 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(); //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<mitk::ScalarType> secondPointTransformed = myEvaluationFilter->GetQuaternionMean(0).rotation_matrix_transpose().transpose() * secondPoint.GetVnlVector() + meanPos.GetVnlVector(); mitk::Point3D secondPointTransformedMITK; mitk::FillVector3D(secondPointTransformedMITK, secondPointTransformed[0], secondPointTransformed[1], secondPointTransformed[2]); mitk::FillVector3D(thirdPoint, 0, 4, 0); //Y vnl_vector<mitk::ScalarType> thirdPointTransformed = myEvaluationFilter->GetQuaternionMean(0).rotation_matrix_transpose().transpose() * thirdPoint.GetVnlVector() + meanPos.GetVnlVector(); mitk::Point3D thirdPointTransformedMITK; mitk::FillVector3D(thirdPointTransformedMITK, thirdPointTransformed[0], thirdPointTransformed[1], thirdPointTransformed[2]); mitk::FillVector3D(fourthPoint, 0, 0, 6); //Z vnl_vector<mitk::ScalarType> fourthPointTransformed = myEvaluationFilter->GetQuaternionMean(0).rotation_matrix_transpose().transpose() * fourthPoint.GetVnlVector() + meanPos.GetVnlVector(); 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 (std::size_t i = 0; i < m_CSVtoXMLInputFilenameVector.size(); ++i) { 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<mitk::NavigationData::Pointer> 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 (std::size_t 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(nullptr, 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(nullptr, 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<mitk::Quaternion> QmitkIGTTrackingDataEvaluationView::GetMeanOrientationsOfAllData(std::vector<mitk::NavigationDataEvaluationFilter::Pointer> allData, bool useSLERP) { std::vector<mitk::Quaternion> returnValue; for (auto dataSet : allData) { if (useSLERP) returnValue.push_back(GetSLERPAverage(dataSet)); else returnValue.push_back(dataSet->GetQuaternionMean(0)); } return returnValue; } std::vector<mitk::NavigationDataEvaluationFilter::Pointer> QmitkIGTTrackingDataEvaluationView::GetAllDataFromUIList() { std::vector<mitk::NavigationDataEvaluationFilter::Pointer> EvaluationDataCollection; //start loop and iterate through all files of list: store the evaluation data for (std::size_t i = 0; i < m_FilenameVector.size(); ++i) { //create navigation data player mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = ConstructNewNavigationDataPlayer(); myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); myPlayer->SetFileName(m_FilenameVector.at(i)); //create evaluation filter mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); //connect pipeline for (unsigned 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(nullptr); myPlayer = nullptr; EvaluationDataCollection.push_back(myEvaluationFilter); } return EvaluationDataCollection; } void QmitkIGTTrackingDataEvaluationView::CalculateDifferenceAngles() { //Get all data from UI std::vector<mitk::NavigationDataEvaluationFilter::Pointer> 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 (std::size_t i = 0; i < m_FilenameVector.size(); ++i) { pos1 = QString::fromStdString(itksys::SystemTools::GetFilenameWithoutLastExtension(m_FilenameVector.at(i))); for (std::size_t 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<double> 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) { m_CurrentAngleDifferencesWriteFile << "Angle between " << pos1 << " and " << pos2 << ";" << idx1 << ";" << idx2 << ";" << angle << "\n"; MITK_INFO << "Angle: " << angle; } std::vector<mitk::NavigationData::Pointer> QmitkIGTTrackingDataEvaluationView::GetNavigationDatasFromFile(std::string filename) { std::vector<mitk::NavigationData::Pointer> returnValue = std::vector<mitk::NavigationData::Pointer>(); std::vector<std::string> fileContentLineByLine = GetFileContentLineByLine(filename); for (std::size_t i = 1; i < fileContentLineByLine.size(); ++i) //skip header so start at 1 { returnValue.push_back(GetNavigationDataOutOfOneLine(fileContentLineByLine.at(i))); } return returnValue; } std::vector<std::string> QmitkIGTTrackingDataEvaluationView::GetFileContentLineByLine(std::string filename) { std::vector<std::string> readData = std::vector<std::string>(); //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; 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->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<mitk::Quaternion> quaternions = std::vector<mitk::Quaternion>(); 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<mitk::HummelProtocolEvaluation::HummelProtocolDistanceError> 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(); } mitk::NavigationDataCSVSequentialPlayer::Pointer QmitkIGTTrackingDataEvaluationView::ConstructNewNavigationDataPlayer() { bool rightHanded = m_Controls->m_RigthHanded->isChecked(); QString separator = m_Controls->m_SeparatorSign->text(); QChar sepaSign = separator.at(0); //char separatorSign; char separatorSign = sepaSign.toLatin1(); //std::string separatorSign = m_Controls->m_SeparatorSign->text().toStdString(); int sampleCount = m_Controls->m_SampleCount->value(); bool headerRow = m_Controls->m_HeaderRow->isChecked(); int xPos = m_Controls->m_XPos->value(); int yPos = m_Controls->m_YPos->value(); int zPos = m_Controls->m_ZPos->value(); bool useQuats = m_Controls->m_UseQuats->isChecked(); int qx = m_Controls->m_Qx->value(); int qy = m_Controls->m_Qy->value(); int qz = m_Controls->m_Qz->value(); int qr = m_Controls->m_Qr->value(); int azimuth = m_Controls->m_Azimuth->value(); int elevation = m_Controls->m_Elevation->value(); int roll = m_Controls->m_Roll->value(); bool eulersInRad = m_Controls->m_Radiants->isChecked(); //need to find the biggest column number to determine the minimal number of columns the .csv file has to have int allInts[] = {xPos, yPos, zPos, qx, qy, qr, azimuth, elevation, roll}; int minNumberOfColumns = (*std::max_element(allInts, allInts+9)+1); //size needs to be +1 because columns start at 0 but size at 1 mitk::NavigationDataCSVSequentialPlayer::Pointer navDataPlayer = mitk::NavigationDataCSVSequentialPlayer::New(); navDataPlayer->SetOptions(rightHanded, separatorSign, sampleCount, headerRow, xPos, yPos, zPos, useQuats, qx, qy, qz, qr, azimuth, elevation, roll, eulersInRad, minNumberOfColumns); return navDataPlayer; } 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 index 2dd08f80f3..78393e3591 100644 --- 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 @@ -1,1780 +1,1787 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>QmitkIGTTrackingDataEvaluationViewControls</class> <widget class="QWidget" name="QmitkIGTTrackingDataEvaluationViewControls"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>448</width> <height>955</height> </rect> </property> <property name="minimumSize"> <size> <width>0</width> <height>0</height> </size> </property> <property name="windowTitle"> <string>QmitkTemplate</string> </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <item> <widget class="QTabWidget" name="tabWidget"> <property name="currentIndex"> <number>0</number> </property> <widget class="QWidget" name="tab"> <attribute name="title"> <string>Evaluation</string> </attribute> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QLabel" name="label"> <property name="text"> <string>Input File List (recorded NavigationData / *.csv):</string> </property> </widget> </item> <item> <widget class="QListWidget" name="m_FileList"/> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <spacer name="horizontalSpacer"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="m_LoadInputFileList"> <property name="minimumSize"> <size> <width>120</width> <height>0</height> </size> </property> <property name="text"> <string>Load New List</string> </property> </widget> </item> </layout> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_6"> <item> <spacer name="horizontalSpacer_4"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="m_AddToCurrentList"> <property name="minimumSize"> <size> <width>120</width> <height>0</height> </size> </property> <property name="text"> <string>Add To Current List</string> </property> </widget> </item> </layout> </item> <item> <widget class="Line" name="line_3"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> <item> <widget class="QLabel" name="label_17"> <property name="text"> <string>(1) - VISUALIZATION - of all data sets:</string> </property> </widget> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_17"> <item> <widget class="QLabel" name="label_18"> <property name="text"> <string>Prefix for Data Nodes:</string> </property> </widget> </item> <item> <widget class="QLineEdit" name="m_prefix"/> </item> </layout> </item> <item> <widget class="QPushButton" name="m_GeneratePointSetOfMeanPositions"> <property name="text"> <string>Generate PointSet of Mean Positions</string> </property> </widget> </item> <item> <widget class="QPushButton" name="m_GeneratePointSetsOfSinglePositions"> <property name="text"> <string>Generate PointSets of Single Positions</string> </property> </widget> </item> <item> <widget class="QPushButton" name="m_GenerateRotationLines"> <property name="text"> <string>Generate Lines for Rotation</string> </property> </widget> </item> <item> <widget class="Line" name="line_4"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> <item> <widget class="QLabel" name="label_19"> <property name="text"> <string>(3) - JITTER - Evaluation per file / data set:</string> </property> </widget> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <widget class="QLabel" name="label_2"> <property name="text"> <string>Result CSV Filename:</string> </property> </widget> </item> <item> <widget class="QLineEdit" name="m_OutputFilename"> <property name="text"> <string>D:/tmp/output.csv</string> </property> </widget> </item> </layout> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_3"> <item> <spacer name="horizontalSpacer_2"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="m_StartEvaluation"> <property name="minimumSize"> <size> <width>220</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> <width>200</width> <height>50</height> </size> </property> <property name="text"> <string>COMPUTE RESULTS PER DATA SET</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_3"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> </layout> </item> <item> <widget class="Line" name="line_5"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> <item> <widget class="QLabel" name="label_20"> <property name="text"> <string>(3) - ACCURACY - Evaluation of all data sets:</string> </property> </widget> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_16"> <item> <spacer name="horizontalSpacer_16"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="m_StartEvaluationAll"> <property name="minimumSize"> <size> <width>220</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> <width>200</width> <height>50</height> </size> </property> <property name="text"> <string>COMPUTE RESULTS OF ALL DATA</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_17"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> </layout> </item> <item> <widget class="Line" name="line_6"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> <item> <widget class="QLabel" name="label_21"> <property name="text"> <string>(4) - GRID MATCHING - Evaluation of all data sets:</string> </property> </widget> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_19"> <item> <widget class="QLabel" name="label_22"> <property name="text"> <string>Reference PointSet:</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_20"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QmitkDataStorageComboBox" name="m_ReferencePointSetComboBox"> <property name="minimumSize"> <size> <width>150</width> <height>0</height> </size> </property> </widget> </item> </layout> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_20"> <item> <widget class="QLabel" name="label_23"> <property name="text"> <string>Measurement PointSet:</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_21"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QmitkDataStorageComboBox" name="m_MeasurementPointSetComboBox"> <property name="minimumSize"> <size> <width>150</width> <height>0</height> </size> </property> </widget> </item> </layout> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_18"> <item> <spacer name="horizontalSpacer_18"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="m_GridMatching"> <property name="minimumSize"> <size> <width>220</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> <width>200</width> <height>50</height> </size> </property> <property name="text"> <string>PERFOM GRID MATCHING</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_19"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> </layout> </item> <item> <widget class="Line" name="line_7"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> <item> <widget class="QLabel" name="label_24"> <property name="text"> <string>(5) - ROTATION - Evaluation of all data sets:</string> </property> </widget> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_21"> <item> <spacer name="horizontalSpacer_22"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="m_ComputeRotation"> <property name="minimumSize"> <size> <width>220</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> <width>200</width> <height>50</height> </size> </property> <property name="text"> <string>COMPUTE ROTATION ERRORS</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_23"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> </layout> </item> <item> <spacer name="spacer1"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeType"> <enum>QSizePolicy::Expanding</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>220</height> </size> </property> </spacer> </item> </layout> </widget> <widget class="QWidget" name="tab_2"> <attribute name="title"> <string>Settings</string> </attribute> <layout class="QGridLayout" name="gridLayout_5"> <item row="0" column="0"> <widget class="QToolBox" name="toolBox"> <widget class="QWidget" name="page_5"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>409</width> - <height>768</height> + <width>420</width> + <height>843</height> </rect> </property> <attribute name="label"> <string>General</string> </attribute> <layout class="QVBoxLayout" name="verticalLayout_4"> <item> <widget class="QGroupBox" name="GeneralOptions"> <property name="title"> <string/> </property> <layout class="QVBoxLayout" name="verticalLayout_5"> <item> <layout class="QHBoxLayout" name="horizontalLayout_5"> <item> <widget class="QLabel" name="label_16"> <property name="text"> <string>Scaling Factor for Visualization:</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_15"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>38</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QDoubleSpinBox" name="m_ScalingFactor"> <property name="minimum"> <double>1.000000000000000</double> </property> </widget> </item> </layout> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_4"> <item> <widget class="QLabel" name="label_3"> <property name="text"> <string>Number of samples to analyze:</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_5"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>60</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QSpinBox" name="m_NumberOfSamples"> <property name="maximum"> <number>1000000</number> </property> <property name="value"> <number>150</number> </property> </widget> </item> </layout> </item> <item> <widget class="Line" name="line_8"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> <item> <widget class="QLabel" name="label_15"> <property name="text"> <string>Tracking Volume:</string> </property> </widget> </item> <item> <widget class="QRadioButton" name="m_standardVolume"> <property name="text"> <string>Standard Volume (10 X 9 Positions)</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> <item> <widget class="QRadioButton" name="m_mediumVolume"> <property name="text"> <string>Medium Volume (5 X 5 Positions)</string> </property> </widget> </item> + <item> + <widget class="QRadioButton" name="m_medium5x6Volume"> + <property name="text"> + <string>Medium Volume (5 X 6 Positions)</string> + </property> + </widget> + </item> <item> <widget class="QRadioButton" name="m_smallVolume"> <property name="text"> <string>Small Volume (3 X 4 Positions)</string> </property> </widget> </item> <item> <widget class="Line" name="line_9"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> <item> <widget class="QLabel" name="label_40"> <property name="text"> <string>Rotation Evaluation:</string> </property> </widget> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_26"> <item> <widget class="QLabel" name="label_41"> <property name="text"> <string>Rotation Vector:</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_28"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QLabel" name="label_42"> <property name="text"> <string>X</string> </property> </widget> </item> <item> <widget class="QSpinBox" name="m_rotVecX"> <property name="maximum"> <number>99999</number> </property> </widget> </item> <item> <spacer name="horizontalSpacer_27"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QLabel" name="label_43"> <property name="text"> <string>Y</string> </property> </widget> </item> <item> <widget class="QSpinBox" name="m_rotVecY"> <property name="maximum"> <number>99999</number> </property> </widget> </item> <item> <spacer name="horizontalSpacer_26"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QLabel" name="label_44"> <property name="text"> <string>Z</string> </property> </widget> </item> <item> <widget class="QSpinBox" name="m_rotVecZ"> <property name="maximum"> <number>99999</number> </property> <property name="value"> <number>10000</number> </property> </widget> </item> </layout> </item> <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>40</height> </size> </property> </spacer> </item> </layout> </widget> </item> </layout> </widget> <widget class="QWidget" name="page"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>286</width> - <height>746</height> + <width>180</width> + <height>584</height> </rect> </property> <attribute name="label"> <string>.csv file input options</string> </attribute> <layout class="QVBoxLayout" name="verticalLayout_13"> <item> <widget class="QGroupBox" name="m_FileOptionsBox"> <property name="title"> <string>File Options:</string> </property> <layout class="QVBoxLayout" name="verticalLayout_12"> <item> <layout class="QHBoxLayout" name="horizontalLayout_23"> <item> <widget class="QLabel" name="label_26"> <property name="text"> <string>Separator in the csv file: </string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_25"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>60</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QLineEdit" name="m_SeparatorSign"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> <property name="maximumSize"> <size> <width>40</width> <height>16777215</height> </size> </property> <property name="text"> <string>;</string> </property> <property name="maxLength"> <number>1</number> </property> </widget> </item> </layout> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_25"> <item> <widget class="QLabel" name="label_25"> <property name="text"> <string>Use every n-th smaple n:</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_24"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QSpinBox" name="m_SampleCount"> <property name="value"> <number>1</number> </property> </widget> </item> </layout> </item> <item> <widget class="QCheckBox" name="m_HeaderRow"> <property name="text"> <string>The csv file has a header row</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> </layout> </widget> </item> <item> <widget class="QGroupBox" name="CoordinateType"> <property name="title"> <string>Type of Coordinate System:</string> </property> <layout class="QVBoxLayout" name="verticalLayout_19"> <item> <layout class="QHBoxLayout" name="horizontalLayout_24"> <item> <widget class="QRadioButton" name="m_LeftHanded"> <property name="text"> <string>Left handed </string> </property> </widget> </item> <item> <widget class="QRadioButton" name="m_RigthHanded"> <property name="text"> <string>Right handed</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> </layout> </item> </layout> </widget> </item> <item> <widget class="QGroupBox" name="PositionAndOrientationOptions"> <property name="title"> <string>Position and Orientation Options:</string> </property> <layout class="QVBoxLayout" name="verticalLayout_11"> <item> <layout class="QGridLayout" name="gridLayout"> <item row="2" column="0"> <widget class="QLabel" name="label_28"> <property name="text"> <string>Y</string> </property> </widget> </item> <item row="2" column="1"> <widget class="QSpinBox" name="m_YPos"> <property name="value"> <number>4</number> </property> </widget> </item> <item row="3" column="0"> <widget class="QLabel" name="label_29"> <property name="text"> <string>Z</string> </property> </widget> </item> <item row="3" column="1"> <widget class="QSpinBox" name="m_ZPos"> <property name="value"> <number>5</number> </property> </widget> </item> <item row="1" column="0"> <widget class="QLabel" name="label_27"> <property name="text"> <string>X</string> </property> </widget> </item> <item row="1" column="1"> <widget class="QSpinBox" name="m_XPos"> <property name="value"> <number>3</number> </property> </widget> </item> <item row="0" column="0"> <widget class="QLabel" name="label_4"> <property name="text"> <string>Coordinate:</string> </property> </widget> </item> <item row="0" column="1"> <widget class="QLabel" name="label_5"> <property name="text"> <string>Colum number:</string> </property> </widget> </item> </layout> </item> <item> <widget class="QRadioButton" name="m_UseQuats"> <property name="text"> <string>Use Quaternions for Orientation</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> <item> <layout class="QGridLayout" name="gridLayout_2"> <item row="4" column="0"> <widget class="QLabel" name="label_35"> <property name="text"> <string>Qr</string> </property> </widget> </item> <item row="3" column="1"> <widget class="QSpinBox" name="m_Qz"> <property name="value"> <number>8</number> </property> </widget> </item> <item row="2" column="0"> <widget class="QLabel" name="label_33"> <property name="text"> <string>Qy</string> </property> </widget> </item> <item row="4" column="1"> <widget class="QSpinBox" name="m_Qr"> <property name="value"> <number>9</number> </property> </widget> </item> <item row="2" column="1"> <widget class="QSpinBox" name="m_Qy"> <property name="value"> <number>7</number> </property> </widget> </item> <item row="1" column="1"> <widget class="QSpinBox" name="m_Qx"> <property name="value"> <number>6</number> </property> </widget> </item> <item row="1" column="0"> <widget class="QLabel" name="label_32"> <property name="text"> <string>Qx</string> </property> </widget> </item> <item row="3" column="0"> <widget class="QLabel" name="label_34"> <property name="text"> <string>Qz</string> </property> </widget> </item> <item row="0" column="0"> <widget class="QLabel" name="label_6"> <property name="text"> <string>Quaternion component:</string> </property> </widget> </item> <item row="0" column="1"> <widget class="QLabel" name="label_36"> <property name="text"> <string>Column number:</string> </property> </widget> </item> </layout> </item> <item> <widget class="QRadioButton" name="m_UseEuler"> <property name="text"> <string>Use Euler Angles for Orientation</string> </property> </widget> </item> <item> <layout class="QGridLayout" name="gridLayout_3"> <item row="0" column="1"> <widget class="QLabel" name="label_30"> <property name="text"> <string>Column number:</string> </property> </widget> </item> <item row="1" column="0"> <widget class="QLabel" name="label_37"> <property name="text"> <string>Azimuth</string> </property> </widget> </item> <item row="2" column="1"> <widget class="QSpinBox" name="m_Elevation"> <property name="minimum"> <number>-1</number> </property> </widget> </item> <item row="3" column="0"> <widget class="QLabel" name="label_39"> <property name="text"> <string>Roll</string> </property> </widget> </item> <item row="0" column="0"> <widget class="QLabel" name="label_7"> <property name="text"> <string>Angle:</string> </property> </widget> </item> <item row="3" column="1"> <widget class="QSpinBox" name="m_Roll"> <property name="minimum"> <number>-1</number> </property> </widget> </item> <item row="2" column="0"> <widget class="QLabel" name="label_38"> <property name="text"> <string>Elevation</string> </property> </widget> </item> <item row="1" column="1"> <widget class="QSpinBox" name="m_Azimuth"> <property name="minimum"> <number>-1</number> </property> </widget> </item> </layout> </item> <item> <widget class="QWidget" name="EulerUnity" native="true"> <layout class="QVBoxLayout" name="verticalLayout_18"> <item> <widget class="QLabel" name="label_31"> <property name="text"> <string>Unity for Euler Angles:</string> </property> </widget> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_22"> <item> <widget class="QRadioButton" name="m_Radiants"> <property name="text"> <string>Radiants</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> <item> <widget class="QRadioButton" name="m_Degrees"> <property name="text"> <string>Degrees</string> </property> <property name="checked"> <bool>false</bool> </property> </widget> </item> </layout> </item> </layout> </widget> </item> </layout> </widget> </item> </layout> </widget> <widget class="QWidget" name="toolBoxPage1"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>343</width> - <height>607</height> + <width>217</width> + <height>452</height> </rect> </property> <attribute name="label"> <string>Output per data set</string> </attribute> <layout class="QVBoxLayout" name="verticalLayout_17"> <item> <widget class="QGroupBox" name="groupBox"> <property name="title"> <string>Position</string> </property> <layout class="QVBoxLayout" name="verticalLayout_14"> <item> <widget class="QCheckBox" name="m_settingPosMean"> <property name="text"> <string>Mean (x,y,z)</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> <item> <widget class="QCheckBox" name="m_settingPosStabw"> <property name="text"> <string>Standard Deviation (x,y,z)</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="m_settingPosSampleStabw"> <property name="text"> <string>Sample Standard Deviation (x,y,z)</string> </property> </widget> </item> </layout> </widget> </item> <item> <widget class="QGroupBox" name="groupBox_6"> <property name="title"> <string>Orientation</string> </property> <layout class="QVBoxLayout" name="verticalLayout_3"> <item> <widget class="QCheckBox" name="m_settingQuaternionMean"> <property name="text"> <string>Quaternion Mean (qx,qy,qz,qr)</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="m_QuaternionMeanSlerp"> <property name="text"> <string>Quaternion Mean (SLERP)</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="m_settionQuaternionStabw"> <property name="text"> <string>Quaternion Standard Deviation (qx,qy,qz,qr)</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="m_settingEulerMean"> <property name="text"> <string>Euler Mean (tx,ty,tz)</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="m_settingDifferenceAngles"> <property name="text"> <string>Difference Angles to all other Positions</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="m_DifferencesSLERP"> <property name="text"> <string>Difference Angles to all other Positions (SLERP)</string> </property> </widget> </item> </layout> </widget> </item> <item> <widget class="QGroupBox" name="groupBox_7"> <property name="title"> <string>Position Error</string> </property> <layout class="QVBoxLayout" name="verticalLayout_15"> <item> <widget class="QCheckBox" name="m_settingPosErrorMean"> <property name="text"> <string>Mean</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="m_settingPosErrorStabw"> <property name="text"> <string>Standard Deviation</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="m_settingPosErrorSampleStabw"> <property name="text"> <string>Sample Standard Deviation</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="m_settingPosErrorRMS"> <property name="text"> <string>RMS</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> <item> <widget class="QCheckBox" name="m_settingPosErrorMedian"> <property name="text"> <string>Median</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="m_settingPosErrorMinMax"> <property name="text"> <string>Min/Max</string> </property> </widget> </item> </layout> </widget> </item> <item> <widget class="QGroupBox" name="groupBox_8"> <property name="title"> <string>Orientation Error</string> </property> <layout class="QVBoxLayout" name="verticalLayout_16"> <item> <widget class="QCheckBox" name="m_settingEulerRMS"> <property name="text"> <string>Euler RMS</string> </property> </widget> </item> </layout> </widget> </item> </layout> </widget> </widget> </item> </layout> </widget> <widget class="QWidget" name="tab_3"> <attribute name="title"> <string>Tools</string> </attribute> <layout class="QVBoxLayout" name="verticalLayout_10"> <item> <widget class="QGroupBox" name="groupBox_2"> <property name="title"> <string>Point Set Ground Truth Generator</string> </property> <layout class="QVBoxLayout" name="verticalLayout_6"> <item> <layout class="QHBoxLayout" name="horizontalLayout_8"> <item> <widget class="QLabel" name="label_8"> <property name="text"> <string>Generate</string> </property> </widget> </item> <item> <widget class="QSpinBox" name="m_PointNumber1"> <property name="minimum"> <number>1</number> </property> <property name="maximum"> <number>999</number> </property> <property name="value"> <number>10</number> </property> </widget> </item> <item> <widget class="QLabel" name="label_9"> <property name="text"> <string>X</string> </property> </widget> </item> <item> <widget class="QSpinBox" name="m_PointNumber2"> <property name="minimum"> <number>1</number> </property> <property name="maximum"> <number>999</number> </property> <property name="value"> <number>9</number> </property> </widget> </item> <item> <widget class="QLabel" name="label_10"> <property name="text"> <string>Point Set</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_7"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> </layout> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_7"> <item> <widget class="QLabel" name="label_11"> <property name="text"> <string>Inter Point Distance (in mm):</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_8"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QSpinBox" name="m_PointDistance"> <property name="minimum"> <number>1</number> </property> <property name="maximum"> <number>99999</number> </property> <property name="value"> <number>50</number> </property> </widget> </item> </layout> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_9"> <item> <spacer name="horizontalSpacer_6"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="m_GeneratePointSet"> <property name="text"> <string>Generate</string> </property> </widget> </item> </layout> </item> </layout> </widget> </item> <item> <widget class="QGroupBox" name="groupBox_3"> <property name="title"> <string>Result CSV File to NavigationData Converter</string> </property> <layout class="QVBoxLayout" name="verticalLayout_7"> <item> <widget class="QRadioButton" name="m_ConvertSingleFile"> <property name="text"> <string>Convert Single File</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> <item> <widget class="QLabel" name="label_12"> <property name="text"> <string>Input CSV Logging File:</string> </property> </widget> </item> <item> <widget class="QLineEdit" name="m_InputCSV"> <property name="text"> <string>C:/Tools/test.csv</string> </property> </widget> </item> <item> <widget class="QLabel" name="label_13"> <property name="text"> <string>Output Navigation Data File:</string> </property> </widget> </item> <item> <widget class="QLineEdit" name="m_OutputXML"> <property name="text"> <string>C:/Tools/testoutput.xml</string> </property> </widget> </item> <item> <widget class="Line" name="line_2"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> <item> <widget class="QRadioButton" name="m_ConvertFileList"> <property name="text"> <string>Convert File List</string> </property> </widget> </item> <item> <widget class="QLabel" name="label_14"> <property name="text"> <string><!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></string> </property> </widget> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_11"> <item> <widget class="QLabel" name="m_labelCSVtoXMLInputList"> <property name="text"> <string>not loaded</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_10"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="m_loadCSVtoXMLInputList"> <property name="minimumSize"> <size> <width>100</width> <height>0</height> </size> </property> <property name="text"> <string>Load Input List</string> </property> </widget> </item> </layout> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_12"> <item> <widget class="QLabel" name="m_labelCSVtoXMLOutputList"> <property name="text"> <string>not loaded</string> </property> </widget> </item> <item> <spacer name="horizontalSpacer_11"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="m_loadCSVtoXMLOutputList"> <property name="minimumSize"> <size> <width>100</width> <height>0</height> </size> </property> <property name="text"> <string>Load Output List</string> </property> </widget> </item> </layout> </item> <item> <widget class="Line" name="line"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_14"> <item> <spacer name="horizontalSpacer_12"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QGroupBox" name="groupBox_4"> <property name="title"> <string>Output Format</string> </property> <layout class="QVBoxLayout" name="verticalLayout_9"> <item> <widget class="QRadioButton" name="m_ConvertXML"> <property name="text"> <string>XML</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> <item> <widget class="QRadioButton" name="m_ConvertCSV"> <property name="text"> <string>CSV</string> </property> </widget> </item> </layout> </widget> </item> </layout> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_10"> <item> <spacer name="horizontalSpacer_9"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="m_Convert"> <property name="text"> <string>Convert</string> </property> </widget> </item> </layout> </item> </layout> </widget> </item> <item> <widget class="QGroupBox" name="groupBox_5"> <property name="title"> <string>Orientation Calculation (out of three positions)</string> </property> <layout class="QVBoxLayout" name="verticalLayout_8"> <item> <layout class="QHBoxLayout" name="horizontalLayout_13"> <item> <spacer name="horizontalSpacer_13"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="m_OrientationCalculationGenerateReference"> <property name="text"> <string>Generate Reference From Current List</string> </property> </widget> </item> </layout> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_15"> <item> <spacer name="horizontalSpacer_14"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="m_OrientationCalculationWriteOrientationsToFile"> <property name="text"> <string>Write Orientation Quaternions To File</string> </property> </widget> </item> </layout> </item> </layout> </widget> </item> <item> <spacer name="verticalSpacer_3"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>632</height> </size> </property> </spacer> </item> </layout> </widget> </widget> </item> </layout> </widget> <layoutdefault spacing="6" margin="11"/> <customwidgets> <customwidget> <class>QmitkDataStorageComboBox</class> <extends>QComboBox</extends> <header>QmitkDataStorageComboBox.h</header> </customwidget> </customwidgets> <resources/> <connections/> </ui> 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 index d30c2104d8..bff4922ac3 100644 --- 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 @@ -1,453 +1,522 @@ /*=================================================================== 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 <boost/serialization/array_wrapper.hpp> #include <boost/accumulators/accumulators.hpp> #include <boost/accumulators/statistics.hpp> #include <boost/accumulators/statistics/stats.hpp> #include <boost/accumulators/statistics/mean.hpp> #include <boost/accumulators/statistics/moment.hpp> #include <boost/accumulators/statistics/tail_quantile.hpp> #include <boost/math/distributions/normal.hpp> #include <algorithm> bool mitk::HummelProtocolEvaluation::Evaluate15cmDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector<HummelProtocolDistanceError> &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 std::array<std::array<mitk::Point3D, 10> ,9> matrix = ParseMatrixStandardVolume(p); //these are the variables for the results: std::vector<double> distances; std::vector<std::string> 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 (std::size_t i = 0; i < distances.size(); ++i) { HummelProtocolDistanceError currentError; currentError.distanceError = fabs(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<HummelProtocolDistanceError> 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<HummelProtocolDistanceError> &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 std::array<std::array<mitk::Point3D, 10> ,9> matrix = ParseMatrixStandardVolume(p); //these are the variables for the results: std::vector<double> distances; std::vector<std::string> 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 (std::size_t i = 0; i < distances.size(); ++i) { HummelProtocolDistanceError currentError; currentError.distanceError = fabs(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<HummelProtocolDistanceError> 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<HummelProtocolDistanceError> &Results) { if (m != mitk::HummelProtocolEvaluation::standard) { MITK_WARN << "Accumulated distances are only evaluated for standard volumes, aborting!"; return false; } 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 = fabs(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<HummelProtocolDistanceError> 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<HummelProtocolDistanceError> &Results) { MITK_INFO << "########### 5 cm distance errors #############"; std::vector<double> distances; std::vector<std::string> 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 medium: { if (p->GetSize() != 25) { MITK_WARN << "Wrong number of points (expected 25), aborting"; return false; } MITK_INFO << "Computing Hummel protocol distance errors for medium measurement volumes (25 points)..."; int distanceCounter = 0; //convert measurements to matrix std::array<std::array<mitk::Point3D, 5>, 5> matrix = ParseMatrixMediumVolume(p); //evaluation of rows for (int row = 0; row < 5; row++) //rows for (int distance = 0; distance < 4; distance++) { distanceCounter++; mitk::Point3D point1 = p->GetPoint(row * 5 + distance); mitk::Point3D point2 = p->GetPoint(row * 5 + 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 < 5; column++) for (int row = 0; row < 4; row++) { distanceCounter++; mitk::Point3D point1 = matrix[row][column]; mitk::Point3D point2 = matrix[row + 1][column]; MITK_INFO << "Point 1:" << point1 << "/Point 2:" << point2 << "/Distance:" << point1.EuclideanDistanceTo(point2); 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; +case medium5x6: +{ + if (p->GetSize() != 30) { + MITK_WARN << "Wrong number of points (expected 30), aborting"; + return false; + } + MITK_INFO << "Computing Hummel protocol distance errors for 5x6 medium measurement volumes (30 points)..."; + + int distanceCounter = 0; + + //convert measurements to matrix + std::array<std::array<mitk::Point3D, 6>, 5> matrix = ParseMatrixMedium5x6Volume(p); + + /* Print out matrix for debugging + for (int row = 0; row < 5; row++) + { + for (int column = 0; column < 6; column++) + { + std::cout << matrix.at(row).at(column); + } + std::cout << "\n"; + } + */ + + //evaluation of rows + for (int row = 0; row < 5; row++) //rows + for (int distance = 0; distance < 5; distance++) + { + distanceCounter++; + mitk::Point3D point1 = p->GetPoint(row * 6 + distance); + mitk::Point3D point2 = p->GetPoint(row * 6 + 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 < 6; column++) + for (int row = 0; row < 4; 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); + description << "From " << point1 << " to " << point2 << std::endl; + descriptions.push_back(description.str()); + } +} +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 std::array<std::array<mitk::Point3D, 10>, 9> 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 (std::size_t i = 0; i < distances.size(); ++i) { HummelProtocolDistanceError currentError; currentError.distanceError = fabs(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<HummelProtocolDistanceError> statistics = mitk::HummelProtocolEvaluation::ComputeStatistics(Results); for (auto currentError : statistics) { Results.push_back(currentError); MITK_INFO << currentError.description << " : " << currentError.distanceError; } return true; } std::array<std::array<mitk::Point3D, 10>, 9> mitk::HummelProtocolEvaluation::ParseMatrixStandardVolume(mitk::PointSet::Pointer p) { std::array<std::array<mitk::Point3D, 10> ,9> returnValue; 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); return returnValue; } +std::array<std::array<mitk::Point3D, 6>, 5> mitk::HummelProtocolEvaluation::ParseMatrixMedium5x6Volume(mitk::PointSet::Pointer p) +{ + + std::array<std::array<mitk::Point3D, 6>, 5> returnValue; + + if (p->GetSize() != 30) + { + MITK_WARN << "PointSet does not have the right size. Expected 30 got " << p->GetSize() << " ... aborting!"; + return returnValue; + } + for (int row = 0; row < 5; row++) + for (int column = 0; column < 6; column++) + returnValue[row][column] = p->GetPoint(row * 6 + column); + return returnValue; +} + std::array<std::array<mitk::Point3D, 5>, 5> mitk::HummelProtocolEvaluation::ParseMatrixMediumVolume(mitk::PointSet::Pointer p) { std::array<std::array<mitk::Point3D, 5>, 5> returnValue; if (p->GetSize() != 25) { MITK_WARN << "PointSet does not have the right size. Expected 25 got " << p->GetSize() << " ... aborting!"; return returnValue; } for (int row = 0; row < 5; row++) for (int column = 0; column < 5; column++) { returnValue[row][column] = p->GetPoint(row * 5 + column); //MITK_INFO << "m " << row << "/" << column << ":" << p->GetPoint(row * 5 + column); } return returnValue; } std::vector<mitk::HummelProtocolEvaluation::HummelProtocolDistanceError> mitk::HummelProtocolEvaluation::ComputeStatistics(std::vector<mitk::HummelProtocolEvaluation::HummelProtocolDistanceError> values) { std::vector<mitk::HummelProtocolEvaluation::HummelProtocolDistanceError> returnValue; //convert input values to boost / using boost accumulators for statistics boost::accumulators::accumulator_set<double, boost::accumulators::features<boost::accumulators::tag::mean, //boost::accumulators::tag::median, boost::accumulators::tag::variance, boost::accumulators::tag::max, boost::accumulators::tag::min //boost::accumulators::tag::tail<boost::accumulators::left> > > acc; for (mitk::HummelProtocolEvaluation::HummelProtocolDistanceError each : values) { acc(each.distanceError); } returnValue.push_back({ static_cast<double>(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<double> 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 index d08a7d1c8c..3ceff58312 100644 --- 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 @@ -1,96 +1,98 @@ /*=================================================================== 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 <mitkPointSet.h> #include <array> 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, medium, standard }; + enum HummelProtocolMeasurementVolume { small, medium, medium5x6, 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<HummelProtocolDistanceError> &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<HummelProtocolDistanceError> &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<HummelProtocolDistanceError> &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<HummelProtocolDistanceError> &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<HummelProtocolDistanceError> ComputeStatistics(std::vector<HummelProtocolDistanceError> 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 std::array<std::array<mitk::Point3D, 10> ,9> ParseMatrixStandardVolume(mitk::PointSet::Pointer p); //It would be really wonderfull if we could replace std::array<std::array<mitk::Point3D, 10> ,9> by mitk::Matrix< mitk::Point3D, 9, 10 > but //unfortunatly this version does not compile under Linux. To be precise under Linux only matrices like this: mitk::Matriy<double, 9, 10> compile //even the usage of a double pointer (eg mitk::Matrix<double* , 9, 10>) does not compile. We always got an error message saying: //vnl_c_vector.h:42:49: error: invalid use of incomplete type ‘class vnl_numeric_traits<itk::Point<double, 3u> >’ //Under Windows this error does not appear there everything compiles fine. static std::array<std::array<mitk::Point3D, 5>, 5> ParseMatrixMediumVolume(mitk::PointSet::Pointer p); + static std::array<std::array<mitk::Point3D, 6>, 5> ParseMatrixMedium5x6Volume(mitk::PointSet::Pointer p); + }; } // namespace mitk #endif /* MITKHummelProtocolEvaluation_H_HEADER_INCLUDED_ */