diff --git a/Modules/IGT/Algorithms/mitkNavigationDataEvaluationFilter.cpp b/Modules/IGT/Algorithms/mitkNavigationDataEvaluationFilter.cpp index 4fcef91f9c..89ec5d87a7 100644 --- a/Modules/IGT/Algorithms/mitkNavigationDataEvaluationFilter.cpp +++ b/Modules/IGT/Algorithms/mitkNavigationDataEvaluationFilter.cpp @@ -1,314 +1,302 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkNavigationDataEvaluationFilter.h" #include #define _USE_MATH_DEFINES #include mitk::NavigationDataEvaluationFilter::NavigationDataEvaluationFilter() -: mitk::NavigationDataToNavigationDataFilter() + : mitk::NavigationDataToNavigationDataFilter() { - } - mitk::NavigationDataEvaluationFilter::~NavigationDataEvaluationFilter() { - } void mitk::NavigationDataEvaluationFilter::GenerateData() { + this->CreateOutputsForAllInputs(); // make sure that we have the same number of outputs as inputs + this->CreateMembersForAllInputs(); - this->CreateOutputsForAllInputs(); // make sure that we have the same number of outputs as inputs - this->CreateMembersForAllInputs(); - - /* update outputs with tracking data from tools */ - for (unsigned int i = 0; i < this->GetNumberOfOutputs() ; ++i) + /* update outputs with tracking data from tools */ + for (unsigned int i = 0; i < this->GetNumberOfOutputs(); ++i) + { + //first copy outputs to inputs + mitk::NavigationData* output = this->GetOutput(i); + assert(output); + const mitk::NavigationData* input = this->GetInput(i); + assert(input); + if (input->IsDataValid() == false) { output->SetDataValid(false); } + else { output->Graft(input); } + + //then save statistics + if (input->IsDataValid()) { - //first copy outputs to inputs - mitk::NavigationData* output = this->GetOutput(i); - assert(output); - const mitk::NavigationData* input = this->GetInput(i); - assert(input); - if (input->IsDataValid() == false) {output->SetDataValid(false);} - else {output->Graft(input);} - - //then save statistics - if(input->IsDataValid()) - { - m_LoggedPositions[i].push_back(input->GetPosition()); - m_LoggedQuaternions[i].push_back(input->GetOrientation()); - } - else - { - m_InvalidSamples[i]++; - } + m_LoggedPositions[i].push_back(input->GetPosition()); + m_LoggedQuaternions[i].push_back(input->GetOrientation()); } - + else + { + m_InvalidSamples[i]++; + } + } } void mitk::NavigationDataEvaluationFilter::CreateMembersForAllInputs() { - while(this->m_LoggedPositions.size() < this->GetNumberOfInputs()) - { - std::pair > newElement(m_LoggedPositions.size(),std::vector()); + while (this->m_LoggedPositions.size() < this->GetNumberOfInputs()) + { + std::pair > newElement(m_LoggedPositions.size(), std::vector()); m_LoggedPositions.insert(newElement); - } - while(this->m_LoggedQuaternions.size() < this->GetNumberOfInputs()) - { - std::pair > newElement(m_LoggedQuaternions.size(),std::vector()); + } + while (this->m_LoggedQuaternions.size() < this->GetNumberOfInputs()) + { + std::pair > newElement(m_LoggedQuaternions.size(), std::vector()); m_LoggedQuaternions.insert(newElement); - } - while(this->m_InvalidSamples.size() < this->GetNumberOfInputs()) - { - std::pair newElement(m_InvalidSamples.size(),0); + } + while (this->m_InvalidSamples.size() < this->GetNumberOfInputs()) + { + std::pair newElement(m_InvalidSamples.size(), 0); m_InvalidSamples.insert(newElement); - } - - + } } void mitk::NavigationDataEvaluationFilter::ResetStatistic() { -for (unsigned int i = 0; i < m_LoggedPositions.size(); i++) m_LoggedPositions[i] = std::vector(); -for (unsigned int i = 0; i < m_LoggedQuaternions.size(); i++) m_LoggedQuaternions[i] = std::vector(); -for (unsigned int i = 0; i < m_InvalidSamples.size(); i++) m_InvalidSamples[i] = 0; + for (unsigned int i = 0; i < m_LoggedPositions.size(); i++) m_LoggedPositions[i] = std::vector(); + for (unsigned int i = 0; i < m_LoggedQuaternions.size(); i++) m_LoggedQuaternions[i] = std::vector(); + for (unsigned int i = 0; i < m_InvalidSamples.size(); i++) m_InvalidSamples[i] = 0; } int mitk::NavigationDataEvaluationFilter::GetNumberOfAnalysedNavigationData(int input) { -return this->m_LoggedPositions[input].size(); + return this->m_LoggedPositions[input].size(); } - mitk::Point3D mitk::NavigationDataEvaluationFilter::GetPositionMean(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionMean(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionMean(); } mitk::Vector3D mitk::NavigationDataEvaluationFilter::GetPositionStandardDeviation(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionStandardDeviation(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionStandardDeviation(); } mitk::Vector3D mitk::NavigationDataEvaluationFilter::GetPositionSampleStandardDeviation(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionSampleStandardDeviation(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionSampleStandardDeviation(); } mitk::Quaternion mitk::NavigationDataEvaluationFilter::GetQuaternionMean(int input) { -return GetMean(m_LoggedQuaternions[input]); + return GetMean(m_LoggedQuaternions[input]); } mitk::Quaternion mitk::NavigationDataEvaluationFilter::GetQuaternionStandardDeviation(int input) { -mitk::Quaternion returnValue; -std::vector list1 = std::vector(); -std::vector list2 = std::vector(); -std::vector list3 = std::vector(); -std::vector list4 = std::vector(); -for (unsigned int i=0; i list1 = std::vector(); + std::vector list2 = std::vector(); + std::vector list3 = std::vector(); + std::vector list4 = std::vector(); + for (unsigned int i = 0; i < m_LoggedQuaternions[input].size(); i++) { - list1.push_back(m_LoggedQuaternions[input].at(i)[0]); - list2.push_back(m_LoggedQuaternions[input].at(i)[1]); - list3.push_back(m_LoggedQuaternions[input].at(i)[2]); - list4.push_back(m_LoggedQuaternions[input].at(i)[3]); + list1.push_back(m_LoggedQuaternions[input].at(i)[0]); + list2.push_back(m_LoggedQuaternions[input].at(i)[1]); + list3.push_back(m_LoggedQuaternions[input].at(i)[2]); + list4.push_back(m_LoggedQuaternions[input].at(i)[3]); } -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(); -returnValue[0] = myCalculator->GetStabw(list1); -returnValue[1] = myCalculator->GetStabw(list2); -returnValue[2] = myCalculator->GetStabw(list3); -returnValue[3] = myCalculator->GetStabw(list4); -return returnValue; + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(); + returnValue[0] = myCalculator->GetStabw(list1); + returnValue[1] = myCalculator->GetStabw(list2); + returnValue[2] = myCalculator->GetStabw(list3); + returnValue[3] = myCalculator->GetStabw(list4); + return returnValue; } mitk::Vector3D mitk::NavigationDataEvaluationFilter::GetEulerAnglesMean(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(QuaternionsToEulerAngles(m_LoggedQuaternions[input]))); -mitk::Vector3D returnValue; -returnValue[0] = myCalculator->GetPositionMean()[0]; -returnValue[1] = myCalculator->GetPositionMean()[1]; -returnValue[2] = myCalculator->GetPositionMean()[2]; -return returnValue; + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(QuaternionsToEulerAngles(m_LoggedQuaternions[input]))); + mitk::Vector3D returnValue; + returnValue[0] = myCalculator->GetPositionMean()[0]; + returnValue[1] = myCalculator->GetPositionMean()[1]; + returnValue[2] = myCalculator->GetPositionMean()[2]; + return returnValue; } double mitk::NavigationDataEvaluationFilter::GetEulerAnglesRMS(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(QuaternionsToEulerAngles(m_LoggedQuaternions[input]))); -return myCalculator->GetPositionErrorRMS(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(QuaternionsToEulerAngles(m_LoggedQuaternions[input]))); + return myCalculator->GetPositionErrorRMS(); } double mitk::NavigationDataEvaluationFilter::GetEulerAnglesRMSDegree(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(QuaternionsToEulerAnglesGrad(m_LoggedQuaternions[input]))); -return myCalculator->GetPositionErrorRMS(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(QuaternionsToEulerAnglesGrad(m_LoggedQuaternions[input]))); + return myCalculator->GetPositionErrorRMS(); } - - double mitk::NavigationDataEvaluationFilter::GetPositionErrorMean(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionErrorMean(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionErrorMean(); } double mitk::NavigationDataEvaluationFilter::GetPositionErrorStandardDeviation(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionErrorStandardDeviation(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionErrorStandardDeviation(); } double mitk::NavigationDataEvaluationFilter::GetPositionErrorSampleStandardDeviation(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionErrorSampleStandardDeviation(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionErrorSampleStandardDeviation(); } - double mitk::NavigationDataEvaluationFilter::GetPositionErrorRMS(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionErrorRMS(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionErrorRMS(); } double mitk::NavigationDataEvaluationFilter::GetPositionErrorMedian(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionErrorMedian(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionErrorMedian(); } double mitk::NavigationDataEvaluationFilter::GetPositionErrorMax(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionErrorMax(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionErrorMax(); } double mitk::NavigationDataEvaluationFilter::GetPositionErrorMin(int input) { -mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); -return myCalculator->GetPositionErrorMin(); + mitk::PointSetStatisticsCalculator::Pointer myCalculator = mitk::PointSetStatisticsCalculator::New(VectorToPointSet(m_LoggedPositions[input])); + return myCalculator->GetPositionErrorMin(); } int mitk::NavigationDataEvaluationFilter::GetNumberOfInvalidSamples(int input) { -return m_InvalidSamples[input]; + return m_InvalidSamples[input]; } double mitk::NavigationDataEvaluationFilter::GetPercentageOfInvalidSamples(int input) { -return (m_InvalidSamples[input]/(m_InvalidSamples[input]+((double)m_LoggedPositions[input].size())))*100.0; + return (m_InvalidSamples[input] / (m_InvalidSamples[input] + ((double)m_LoggedPositions[input].size())))*100.0; } mitk::Quaternion mitk::NavigationDataEvaluationFilter::GetMean(std::vector list) { -//calculate mean -mitk::Quaternion mean; -mean[0] = 0; -mean[1] = 0; -mean[2] = 0; -mean[3] = 0; - -for (unsigned int i=0; i pSet) { mitk::PointSet::Pointer returnValue = mitk::PointSet::New(); - for (unsigned int i=0; iInsertPoint(i,pSet.at(i)); + for (unsigned int i = 0; i < pSet.size(); i++) returnValue->InsertPoint(i, pSet.at(i)); return returnValue; } mitk::PointSet::Pointer mitk::NavigationDataEvaluationFilter::VectorToPointSet(std::vector pSet) { mitk::PointSet::Pointer returnValue = mitk::PointSet::New(); - for (unsigned int i=0; iInsertPoint(i,thisPoint); - } + returnValue->InsertPoint(i, thisPoint); + } return returnValue; } std::vector mitk::NavigationDataEvaluationFilter::QuaternionsToEulerAngles(std::vector quaterions) { std::vector returnValue = std::vector(); - for (unsigned int i=0; i mitk::NavigationDataEvaluationFilter::QuaternionsToEulerAnglesGrad(std::vector quaterions) { double PI = M_PI; std::vector returnValue = std::vector(); std::vector eulerAnglesRadians = QuaternionsToEulerAngles(quaterions); - for (unsigned int i=0; i #include #include #include #include //sleep headers #include #include typedef itk::MutexLockHolder MutexLockHolder; mitk::OpenIGTLinkTrackingDevice::OpenIGTLinkTrackingDevice() : mitk::TrackingDevice(), m_UpdateRate(60) { //set the type of this tracking device this->m_Data = mitk::OpenIGTLinkTypeInformation::GetDeviceDataOpenIGTLinkTrackingDeviceConnection(); m_OpenIGTLinkClient = mitk::IGTLClient::New(true); m_OpenIGTLinkClient->SetName("OpenIGTLink Tracking Device"); m_IGTLDeviceSource = mitk::IGTLTransformDeviceSource::New(); m_IGTLDeviceSource->SetIGTLDevice(m_OpenIGTLinkClient); } mitk::OpenIGTLinkTrackingDevice::~OpenIGTLinkTrackingDevice() { } int mitk::OpenIGTLinkTrackingDevice::GetPortNumber() { return m_OpenIGTLinkClient->GetPortNumber(); } std::string mitk::OpenIGTLinkTrackingDevice::GetHostname() { return m_OpenIGTLinkClient->GetHostname(); } void mitk::OpenIGTLinkTrackingDevice::SetPortNumber(int portNumber) { m_OpenIGTLinkClient->SetPortNumber(portNumber); } void mitk::OpenIGTLinkTrackingDevice::SetHostname(std::string hostname) { m_OpenIGTLinkClient->SetHostname(hostname); } bool mitk::OpenIGTLinkTrackingDevice::IsDeviceInstalled() { return true; } mitk::TrackingTool* mitk::OpenIGTLinkTrackingDevice::AddTool(const char* toolName, const char* fileName) { mitk::OpenIGTLinkTrackingTool::Pointer t;// = mitk::OpenIGTLinkTrackingTool::New(); //TODO: Implement if (this->InternalAddTool(t) == false) return NULL; return t.GetPointer(); } bool mitk::OpenIGTLinkTrackingDevice::InternalAddTool(OpenIGTLinkTrackingTool::Pointer tool) { m_AllTools.push_back(tool); return true; } bool mitk::OpenIGTLinkTrackingDevice::DiscoverTools(int waitingTime) { if (m_OpenIGTLinkClient->GetPortNumber() == -1) { MITK_WARN << "Connection not initialized, aborting (invalid port number)."; return false; } try { m_IGTLDeviceSource->Connect(); m_IGTLDeviceSource->StartCommunication(); } catch (std::runtime_error &e) { MITK_WARN << "Open IGT Link device retruned an error while trying to connect: " << e.what(); return false; } //send a message to the server: start tracking stream mitk::IGTLMessageFactory::Pointer msgFactory = m_OpenIGTLinkClient->GetMessageFactory(); std::string message = "STT_TDATA"; igtl::MessageBase::Pointer sttMsg = msgFactory->CreateInstance(message); //TODO: Fix this to dynamically get this from GUI ((igtl::StartTrackingDataMessage*)sttMsg.GetPointer())->SetResolution(m_UpdateRate); m_OpenIGTLinkClient->SendMessage(sttMsg); mitk::IGTLMessage::Pointer receivedMessage; std::chrono::high_resolution_clock::time_point time = std::chrono::high_resolution_clock::now(); std::chrono::milliseconds d = std::chrono::milliseconds(waitingTime); while (!(receivedMessage.IsNotNull() && receivedMessage->IsDataValid())) { m_IGTLDeviceSource->Update(); receivedMessage = m_IGTLDeviceSource->GetOutput(); if ((time + d) < std::chrono::high_resolution_clock::now()) break; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } //check the tracking stream for the number and type of tools //igtl::MessageBase::Pointer receivedMessage = m_OpenIGTLinkClient->GetNextMessage(); if (receivedMessage.IsNull()) { MITK_WARN << "No message was received. Is there really a server?"; return false; } else if (!receivedMessage->IsDataValid()) { MITK_WARN << "Received invalid message."; return false; } const char* msgType = receivedMessage->GetIGTLMessageType(); mitk::OpenIGTLinkTrackingDevice::TrackingMessageType type = GetMessageTypeFromString(msgType); switch (type) { case TDATA: return DiscoverToolsFromTData(dynamic_cast(receivedMessage->GetMessage().GetPointer())); case QTDATA: return DiscoverToolsFromQTData(dynamic_cast(receivedMessage->GetMessage().GetPointer())); case TRANSFORM: return DiscoverToolsFromTransform(); default: MITK_INFO << "Server does not send tracking data. Received data is not of a compatible type. Received type: " << msgType; return false; } } bool mitk::OpenIGTLinkTrackingDevice::DiscoverToolsFromTData(igtl::TrackingDataMessage::Pointer tdMsg) { MITK_INFO << "Start discovering tools by TDATA messages"; if (tdMsg == nullptr) { MITK_WARN << "Message was not a TrackingDataMessage, aborting!"; return false; } int numberOfTools = tdMsg->GetNumberOfTrackingDataElements(); MITK_INFO << "Found " << numberOfTools << " tools"; for (int i = 0; i < numberOfTools; i++) { igtl::TrackingDataElement::Pointer currentTrackingData; tdMsg->GetTrackingDataElement(i, currentTrackingData); std::string name = currentTrackingData->GetName(); AddNewToolForName(name, i); } m_IGTLDeviceSource->StopCommunication(); SetState(Ready); return true; } bool mitk::OpenIGTLinkTrackingDevice::DiscoverToolsFromQTData(igtl::QuaternionTrackingDataMessage::Pointer msg) { MITK_INFO << "Start discovering tools by QTDATA messages"; if (msg == nullptr) { MITK_WARN << "Message was not a QuaternionTrackingDataMessage, aborting!"; return false; } int numberOfTools = msg->GetNumberOfQuaternionTrackingDataElements(); MITK_INFO << "Found " << numberOfTools << " tools"; for (int i = 0; i < numberOfTools; i++) { igtl::QuaternionTrackingDataElement::Pointer currentTrackingData; msg->GetQuaternionTrackingDataElement(i, currentTrackingData); std::string name = currentTrackingData->GetName(); AddNewToolForName(name, i); } m_IGTLDeviceSource->StopCommunication(); SetState(Ready); return true; } void mitk::OpenIGTLinkTrackingDevice::AddNewToolForName(std::string name, int i) { mitk::OpenIGTLinkTrackingTool::Pointer newTool = mitk::OpenIGTLinkTrackingTool::New(); if (name == "") //if no name was given create a default name { std::stringstream defaultName; defaultName << "OpenIGTLinkTool#" << i; name = defaultName.str(); } MITK_INFO << "Added tool " << name << " to tracking device."; newTool->SetToolName(name); InternalAddTool(newTool); } bool mitk::OpenIGTLinkTrackingDevice::DiscoverToolsFromTransform() { MITK_INFO << "Start discovering tools by TRANSFORM messages"; std::map toolNameMap; bool condition = false; while (!condition) { //TODO: Fix this.. :/ std::this_thread::sleep_for(std::chrono::milliseconds(20)); m_IGTLDeviceSource->Update(); igtl::TransformMessage::Pointer msg = dynamic_cast(m_IGTLDeviceSource->GetOutput()->GetMessage().GetPointer()); if (msg == nullptr || msg.IsNull()) { MITK_INFO << "Received message could not be casted to TransformMessage. Skipping.."; continue; } condition = true; int count = toolNameMap[msg->GetDeviceName()]; if (count == 0) { MITK_WARN << "ADDED NEW TOOL TO TOOLCHAIN: " << msg->GetDeviceName() << " - 1"; toolNameMap[msg->GetDeviceName()] = 1; } else { toolNameMap[msg->GetDeviceName()]++; MITK_WARN << "INCREMENTED TOOL COUNT IN TOOLCHAIN: " << msg->GetDeviceName() << " - " << toolNameMap[msg->GetDeviceName()]; } for (std::map::iterator it = toolNameMap.begin(); it != toolNameMap.end(); ++it) { if (it->second < 5) { condition = false; break; } } } int i = 0; for (std::map::iterator it = toolNameMap.begin(); it != toolNameMap.end(); ++it) { AddNewToolForName(it->first, i++); } //TODO InternalAddTool for all tools return true; } void mitk::OpenIGTLinkTrackingDevice::UpdateTools() { if (this->GetState() != Tracking) { MITK_ERROR << "Method was called in the wrong state, something went wrong!"; return; } m_IGTLMsgToNavDataFilter->Update(); mitk::NavigationData::Pointer currentNavData = m_IGTLMsgToNavDataFilter->GetOutput(); const char* name = currentNavData->GetName(); - MITK_WARN << name; + //MITK_WARN << name; for (int i = 0; i < m_AllTools.size(); i++) { if (strcmp(m_AllTools.at(i)->GetToolName(), name) == 0) { m_AllTools.at(i)->SetDataValid(currentNavData->IsDataValid()); m_AllTools.at(i)->SetPosition(currentNavData->GetPosition()); m_AllTools.at(i)->SetOrientation(currentNavData->GetOrientation()); m_AllTools.at(i)->SetIGTTimeStamp(currentNavData->GetIGTTimeStamp()); } } } bool mitk::OpenIGTLinkTrackingDevice::StartTracking() { //check tracking state if (this->GetState() != Ready) { MITK_WARN << "Cannot start tracking, device is not ready!"; return false; } try { m_IGTLDeviceSource->StartCommunication(); //send a message to the server: start tracking stream mitk::IGTLMessageFactory::Pointer msgFactory = m_OpenIGTLinkClient->GetMessageFactory(); std::string message = "STT_TDATA"; //m_OpenIGTLinkClient->SendMessage(msgFactory->CreateInstance(message)); } catch (std::runtime_error &e) { MITK_WARN << "Open IGT Link device retruned an error while starting communication: " << e.what(); return false; } //create internal igtl pipeline m_IGTLMsgToNavDataFilter = mitk::IGTLMessageToNavigationDataFilter::New(); m_IGTLMsgToNavDataFilter->SetNumberOfExpectedOutputs(this->GetToolCount()); m_IGTLMsgToNavDataFilter->ConnectTo(m_IGTLDeviceSource); //connect itk events typedef itk::SimpleMemberCommand< mitk::OpenIGTLinkTrackingDevice > CurCommandType; CurCommandType::Pointer messageReceivedCommand = CurCommandType::New(); messageReceivedCommand->SetCallbackFunction(this, &mitk::OpenIGTLinkTrackingDevice::UpdateTools); m_MessageReceivedObserverTag = m_OpenIGTLinkClient->AddObserver(mitk::MessageReceivedEvent(), messageReceivedCommand); this->SetState(Tracking); return true; } bool mitk::OpenIGTLinkTrackingDevice::StopTracking() { //check tracking state if (this->GetState() != Tracking) { MITK_WARN << "Cannot open connection, device is already connected!"; return false; } m_OpenIGTLinkClient->RemoveObserver(m_MessageReceivedObserverTag); //disconnect itk events try { m_IGTLDeviceSource->StopCommunication(); } catch (std::runtime_error &e) { MITK_WARN << "Open IGT Link device retruned an error while stopping communication: " << e.what(); return false; } this->SetState(Ready); return true; } unsigned int mitk::OpenIGTLinkTrackingDevice::GetToolCount() const { return (unsigned int)this->m_AllTools.size(); } mitk::TrackingTool* mitk::OpenIGTLinkTrackingDevice::GetTool(unsigned int toolNumber) const { if (toolNumber >= this->GetToolCount()) return NULL; else return this->m_AllTools[toolNumber]; } bool mitk::OpenIGTLinkTrackingDevice::OpenConnection() { //check tracking state if (this->GetState() != Setup) { MITK_WARN << "Cannot open connection, device is already connected!"; return false; } try { m_IGTLDeviceSource->Connect(); } catch (std::runtime_error &e) { MITK_WARN << "Open IGT Link device retruned an error while trying to connect: " << e.what(); return false; } this->SetState(Ready); return true; } bool mitk::OpenIGTLinkTrackingDevice::CloseConnection() { //check tracking state if (this->GetState() != Ready) { MITK_WARN << "Cannot close connection, device is in the wrong state!"; return false; } try { m_IGTLDeviceSource->Disconnect(); } catch (std::runtime_error &e) { MITK_WARN << "Open IGT Link device retruned an error while trying to disconnect: " << e.what(); return false; } this->SetState(Setup); return true; } std::vector mitk::OpenIGTLinkTrackingDevice::GetAllTools() { return this->m_AllTools; } mitk::OpenIGTLinkTrackingDevice::TrackingMessageType mitk::OpenIGTLinkTrackingDevice::GetMessageTypeFromString(const char* messageTypeString) { if (strcmp(messageTypeString, "TDATA") == 0) { return mitk::OpenIGTLinkTrackingDevice::TrackingMessageType::TDATA; } else if (strcmp(messageTypeString, "QTDATA") == 0) { return mitk::OpenIGTLinkTrackingDevice::TrackingMessageType::QTDATA; } else if (strcmp(messageTypeString, "TRANSFORM") == 0) { return mitk::OpenIGTLinkTrackingDevice::TrackingMessageType::TRANSFORM; } else { return mitk::OpenIGTLinkTrackingDevice::TrackingMessageType::UNKNOWN; } } \ No newline at end of file diff --git a/Modules/IGTBase/include/mitkStaticIGTHelperFunctions.h b/Modules/IGTBase/include/mitkStaticIGTHelperFunctions.h index 285a7b4af2..6267ff3e57 100644 --- a/Modules/IGTBase/include/mitkStaticIGTHelperFunctions.h +++ b/Modules/IGTBase/include/mitkStaticIGTHelperFunctions.h @@ -1,54 +1,53 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include #include #include "MitkIGTBaseExports.h" #include #include #include namespace mitk { class MITKIGTBASE_EXPORT StaticIGTHelperFunctions { public: /** Computes the angle in the plane perpendicular to the rotation axis of the two quaterions. * Therefore, a vector is rotated with the difference of both rotations and the angle is computed. - * In some cases you might want to defice this vector e.g., if working with 5D tools. By default - * the vector is defined along the Z-axis which works for Aurora 5D tools. + * In some cases you might want to define this vector e.g., if working with 5D tools. For NDI Aurora + * 5D tools you need to defined this vector along the Z-axis. * @return Returns the angle in degrees. **/ static double GetAngleBetweenTwoQuaterions(mitk::Quaternion a, mitk::Quaternion b, itk::Vector rotationVector); - /** Computes the angle in the plane perpendicular to the rotation axis of the two quaterions. - * Therefore, a vector is rotated with the difference of both rotations and the angle is computed. - * In some cases you might want to defice this vector e.g., if working with 5D tools. By default - * the vector is defined along the Z-axis which works for Aurora 5D tools. + /** Computes difference between two quaternions in degree, which is the minimum rotation angle between + * these two quaternions. + * The used formula is described here: https://fgiesen.wordpress.com/2013/01/07/small-note-on-quaternion-distance-metrics/ * @return Returns the angle in degrees. **/ static double GetAngleBetweenTwoQuaterions(mitk::Quaternion a, mitk::Quaternion b); /** Converts euler angles (in degrees) to a rotation matrix. */ static itk::Matrix ConvertEulerAnglesToRotationMatrix(double alpha, double beta, double gamma); /** @brief Computes the fiducial registration error out of two sets of fiducials. * The two sets must have the same size and the points must correspond to each other. * @param transform This transform is applied to the image fiducials before the FRE calculation if it is given. * @return Returns the FRE. Returns -1 if there was an error. */ static double ComputeFRE(mitk::PointSet::Pointer imageFiducials, mitk::PointSet::Pointer realWorldFiducials, vtkSmartPointer transform = NULL); }; } diff --git a/Modules/IGTBase/src/mitkStaticIGTHelperFunctions.cpp b/Modules/IGTBase/src/mitkStaticIGTHelperFunctions.cpp index 4f12a78c05..c9872b6ebd 100644 --- a/Modules/IGTBase/src/mitkStaticIGTHelperFunctions.cpp +++ b/Modules/IGTBase/src/mitkStaticIGTHelperFunctions.cpp @@ -1,115 +1,120 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include double mitk::StaticIGTHelperFunctions::GetAngleBetweenTwoQuaterions(mitk::Quaternion a, mitk::Quaternion b, itk::Vector rotationVector) { double returnValue; - itk::Vector point; //caution 5D-Tools: correct verctor along the tool axis is needed + itk::Vector point; //caution 5D-Tools: correct vector along the tool axis is needed point[0] = rotationVector[0]; point[1] = rotationVector[1]; point[2] = rotationVector[2]; //Quaternions used for rotations should alway be normalized, so just to be safe: a.normalize(); b.normalize(); itk::Matrix rotMatrixA; for(int i=0; i<3; i++) for(int j=0; j<3; j++) rotMatrixA[i][j] = a.rotation_matrix_transpose().transpose()[i][j]; itk::Matrix rotMatrixB; for(int i=0; i<3; i++) for(int j=0; j<3; j++) rotMatrixB[i][j] = b.rotation_matrix_transpose().transpose()[i][j]; itk::Vector pt1 = rotMatrixA * point; itk::Vector pt2 = rotMatrixB * point; returnValue = (pt1[0]*pt2[0]+pt1[1]*pt2[1]+pt1[2]*pt2[2]) / ( sqrt(pow(pt1[0],2.0)+pow(pt1[1],2.0)+pow(pt1[2],2.0)) * sqrt(pow(pt2[0],2.0)+pow(pt2[1],2.0)+pow(pt2[2],2.0))); returnValue = acos(returnValue) * 57.296; //57,296 = 180/Pi ; conversion to degrees return returnValue; } double mitk::StaticIGTHelperFunctions::GetAngleBetweenTwoQuaterions(mitk::Quaternion a, mitk::Quaternion b) { - itk::Vector rotationVector = itk::Vector(); - rotationVector[0] = 0; - rotationVector[1] = 0; - rotationVector[2] = 1000; - return GetAngleBetweenTwoQuaterions(a,b,rotationVector); + //formula returnValue = 2 * acos ( a b ) + //(+ normalization because we need unit quaternions) + //derivation from here: https://fgiesen.wordpress.com/2013/01/07/small-note-on-quaternion-distance-metrics/ + double returnValue = ((a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]) / (sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2] + a[3] * a[3])*sqrt(b[0] * b[0] + b[1] * b[1] + b[2] * b[2] + b[3] * b[3]))); + returnValue = 2 * acos(returnValue); + return returnValue; } itk::Matrix mitk::StaticIGTHelperFunctions::ConvertEulerAnglesToRotationMatrix(double alpha, double beta, double gamma) { double PI = 3.141592653589793; alpha = alpha * PI / 180; beta = beta * PI / 180; gamma = gamma * PI / 180; //convert angles to matrix: itk::Matrix matrix; /* x-Konvention (Z, X, Z) matrix[0][0] = cos(alpha) * cos(gamma) - sin(alpha) * cos(beta) * sin(gamma); matrix[0][1] = -cos(alpha) * sin(gamma)- sin(alpha) * cos(beta) * cos(gamma); matrix[0][2] = sin(alpha) * sin(beta); matrix[1][0] = sin(alpha) * cos(gamma) + cos(alpha) * cos(beta) * sin(gamma); matrix[1][1] = cos(alpha) * cos(beta) * cos(gamma) - sin(alpha) * sin(gamma); matrix[1][2] = -cos(alpha) * sin(beta); matrix[2][0] = sin(beta) * sin(gamma); matrix[2][1] = sin(beta) * cos(gamma); matrix[2][2] = cos(beta); */ //Luftfahrtnorm (DIN 9300) (Yaw-Pitch-Roll, Z, Y, X) matrix[0][0] = cos(beta) * cos(alpha); matrix[0][1] = cos(beta) * sin(alpha); matrix[0][2] = -sin(beta); matrix[1][0] = sin(gamma) * sin(beta) * cos(alpha) - cos(gamma) * sin(alpha) ; matrix[1][1] = sin(gamma) * sin(beta) * sin(alpha) + cos(gamma) * cos(alpha); matrix[1][2] = sin(gamma) * cos(beta); matrix[2][0] = cos(gamma) * sin(beta) * cos(alpha) + sin(gamma) * sin(alpha); matrix[2][1] = cos(gamma) * sin(beta) * sin(alpha) - sin(gamma) * cos(alpha); matrix[2][2] = cos(gamma) * cos(beta); return matrix; } double mitk::StaticIGTHelperFunctions::ComputeFRE(mitk::PointSet::Pointer imageFiducials, mitk::PointSet::Pointer realWorldFiducials, vtkSmartPointer transform) { - if (imageFiducials->GetSize() != realWorldFiducials->GetSize()) return -1; + if (imageFiducials->GetSize() != realWorldFiducials->GetSize()) + { + MITK_WARN << "Cannot compute FRE, got different numbers of points (1: " << imageFiducials->GetSize() << " /2: " << realWorldFiducials->GetSize() << ")"; + return -1; + } double FRE = 0; for (int i = 0; i < imageFiducials->GetSize(); i++) { itk::Point current_image_fiducial_point = imageFiducials->GetPoint(i); if (transform != NULL) { current_image_fiducial_point = transform->TransformPoint(imageFiducials->GetPoint(i)[0], imageFiducials->GetPoint(i)[1], imageFiducials->GetPoint(i)[2]); } double cur_error_squared = current_image_fiducial_point.SquaredEuclideanDistanceTo(realWorldFiducials->GetPoint(i)); FRE += cur_error_squared; } FRE = sqrt(FRE / (double)imageFiducials->GetSize()); return FRE; } diff --git a/Modules/OpenIGTLink/DeviceSources/mitkIGTLTransformDeviceSource.cpp b/Modules/OpenIGTLink/DeviceSources/mitkIGTLTransformDeviceSource.cpp index eba5359c04..25e4699a6d 100644 --- a/Modules/OpenIGTLink/DeviceSources/mitkIGTLTransformDeviceSource.cpp +++ b/Modules/OpenIGTLink/DeviceSources/mitkIGTLTransformDeviceSource.cpp @@ -1,58 +1,58 @@ /*=================================================================== 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 "mitkIGTLTransformDeviceSource.h" #include "mitkIGTLMessage.h" //Microservices #include #include #include #include //itk #include mitk::IGTLTransformDeviceSource::IGTLTransformDeviceSource() : mitk::IGTLDeviceSource() { this->SetName("IGTLDeviceSource (Transforms)"); } mitk::IGTLTransformDeviceSource::~IGTLTransformDeviceSource() { } void mitk::IGTLTransformDeviceSource::GenerateData() { if (m_IGTLDevice.IsNull()) return; /* update output with message from the device */ IGTLMessage* msgOut = this->GetOutput(); assert(msgOut); igtl::MessageBase::Pointer msgIn = dynamic_cast(m_IGTLDevice->GetNextTransformMessage().GetPointer()); if (msgIn.IsNotNull()) { assert(msgIn); - MITK_INFO << "Received valid image"; + //MITK_INFO << "Received valid image"; msgOut->SetMessage(msgIn); msgOut->SetName(msgIn->GetDeviceName()); } } diff --git a/Modules/OpenIGTLink/mitkIGTLClient.cpp b/Modules/OpenIGTLink/mitkIGTLClient.cpp index 53df927ec3..953c8a4ad2 100644 --- a/Modules/OpenIGTLink/mitkIGTLClient.cpp +++ b/Modules/OpenIGTLink/mitkIGTLClient.cpp @@ -1,124 +1,124 @@ /*=================================================================== 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 "mitkIGTLClient.h" //#include "mitkIGTTimeStamp.h" //#include "mitkIGTHardwareException.h" #include "igtlTrackingDataMessage.h" #include #include #include #include #include typedef itk::MutexLockHolder MutexLockHolder; mitk::IGTLClient::IGTLClient(bool ReadFully) : IGTLDevice(ReadFully) { } mitk::IGTLClient::~IGTLClient() { } bool mitk::IGTLClient::OpenConnection() { if (this->GetState() != Setup) { mitkThrowException(mitk::Exception) << "Can only try to open the connection if in setup mode. State was " << this->GetState(); return false; } std::string hostname = this->GetHostname(); int portNumber = this->GetPortNumber(); if (portNumber == -1 || hostname.size() <= 0) { //port number or hostname was not correct MITK_WARN << "Port number or hostname was not correct"; return false; } //create a new client socket m_Socket = igtl::ClientSocket::New(); //try to connect to the igtl server int response = dynamic_cast(m_Socket.GetPointer())-> ConnectToServer(hostname.c_str(), portNumber); //check the response if (response != 0) { MITK_ERROR << "The client could not connect to " << hostname << " port: " << portNumber; return false; } // everything is initialized and connected so the communication can be started this->SetState(Ready); //inform observers about this new client this->InvokeEvent(NewClientConnectionEvent()); return true; } void mitk::IGTLClient::Receive() { - MITK_INFO << "Trying to receive message"; + //MITK_INFO << "Trying to receive message"; //try to receive a message, if the socket is not present anymore stop the //communication unsigned int status = this->ReceivePrivate(this->m_Socket); if (status == IGTL_STATUS_NOT_PRESENT) { this->StopCommunicationWithSocket(this->m_Socket); //inform observers about loosing the connection to this socket this->InvokeEvent(LostConnectionEvent()); MITK_WARN("IGTLClient") << "Lost connection to server socket."; } } void mitk::IGTLClient::Send() { igtl::MessageBase::Pointer curMessage; //get the latest message from the queue curMessage = this->m_MessageQueue->PullSendMessage(); // there is no message => return if (curMessage.IsNull()) return; if (!this->SendMessagePrivate(curMessage.GetPointer(), this->m_Socket)) { MITK_WARN("IGTLDevice") << "Could not send the message."; } } void mitk::IGTLClient::StopCommunicationWithSocket(igtl::Socket* /*socket*/) { m_StopCommunicationMutex->Lock(); m_StopCommunication = true; m_StopCommunicationMutex->Unlock(); } unsigned int mitk::IGTLClient::GetNumberOfConnections() { return this->m_Socket->GetConnected(); } diff --git a/Modules/OpenIGTLink/mitkIGTLDevice.cpp b/Modules/OpenIGTLink/mitkIGTLDevice.cpp index 94da6b323b..54d8e2b87e 100644 --- a/Modules/OpenIGTLink/mitkIGTLDevice.cpp +++ b/Modules/OpenIGTLink/mitkIGTLDevice.cpp @@ -1,562 +1,562 @@ /*=================================================================== 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 "mitkIGTLDevice.h" //#include "mitkIGTException.h" //#include "mitkIGTTimeStamp.h" #include #include #include #include #include #include //remove later #include //TODO: Which timeout is acceptable and also needed to transmit image data? Is there a maximum data limit? static const int SOCKET_SEND_RECEIVE_TIMEOUT_MSEC = 100; typedef itk::MutexLockHolder MutexLockHolder; mitk::IGTLDevice::IGTLDevice(bool ReadFully) : // m_Data(mitk::DeviceDataUnspecified), m_State(mitk::IGTLDevice::Setup), m_Name("Unspecified Device"), m_StopCommunication(false), m_Hostname("127.0.0.1"), m_PortNumber(-1), m_MultiThreader(nullptr), m_SendThreadID(0), m_ReceiveThreadID(0), m_ConnectThreadID(0) { m_ReadFully = ReadFully; m_StopCommunicationMutex = itk::FastMutexLock::New(); m_StateMutex = itk::FastMutexLock::New(); // m_LatestMessageMutex = itk::FastMutexLock::New(); m_SendingFinishedMutex = itk::FastMutexLock::New(); m_ReceivingFinishedMutex = itk::FastMutexLock::New(); m_ConnectingFinishedMutex = itk::FastMutexLock::New(); // execution rights are owned by the application thread at the beginning m_SendingFinishedMutex->Lock(); m_ReceivingFinishedMutex->Lock(); m_ConnectingFinishedMutex->Lock(); m_MultiThreader = itk::MultiThreader::New(); // m_Data = mitk::DeviceDataUnspecified; // m_LatestMessage = igtl::MessageBase::New(); m_MessageFactory = mitk::IGTLMessageFactory::New(); m_MessageQueue = mitk::IGTLMessageQueue::New(); } mitk::IGTLDevice::~IGTLDevice() { /* stop communication and disconnect from igtl device */ if (GetState() == Running) { this->StopCommunication(); this->CloseConnection(); } else if (GetState() == Ready) { this->CloseConnection(); } /* cleanup tracking thread */ if (m_MultiThreader.IsNotNull()) { if ((m_SendThreadID != 0)) { m_MultiThreader->TerminateThread(m_SendThreadID); } if ((m_ReceiveThreadID != 0)) { m_MultiThreader->TerminateThread(m_ReceiveThreadID); } if ((m_ConnectThreadID != 0)) { m_MultiThreader->TerminateThread(m_ConnectThreadID); } } m_MultiThreader = nullptr; } mitk::IGTLDevice::IGTLDeviceState mitk::IGTLDevice::GetState() const { MutexLockHolder lock(*m_StateMutex); return m_State; } void mitk::IGTLDevice::SetState(IGTLDeviceState state) { itkDebugMacro("setting m_State to " << state); m_StateMutex->Lock(); // MutexLockHolder lock(*m_StateMutex); // lock and unlock the mutex if (m_State == state) { m_StateMutex->Unlock(); return; } m_State = state; m_StateMutex->Unlock(); this->Modified(); } bool mitk::IGTLDevice::TestConnection() { return true; } unsigned int mitk::IGTLDevice::ReceivePrivate(igtl::Socket* socket) { // Create a message buffer to receive header igtl::MessageHeader::Pointer headerMsg; headerMsg = igtl::MessageHeader::New(); // Initialize receive buffer headerMsg->InitPack(); // Receive generic header from the socket int r = socket->Receive(headerMsg->GetPackPointer(), headerMsg->GetPackSize(), 0); //MITK_INFO << "Server received r = " << r; - MITK_INFO << "Received r = " << r; + //MITK_INFO << "Received r = " << r; if (r == 0) //connection error { // an error was received, therefore the communication with this socket // must be stoppedy return IGTL_STATUS_NOT_PRESENT; } else if (r == -1) //timeout { // a timeout was received, this is no error state, thus, do nothing return IGTL_STATUS_TIME_OUT; } else if (r == headerMsg->GetPackSize()) { // Deserialize the header and check the CRC // ERROR HERE: This probably means the header data is corrupted... int crcCheck = headerMsg->Unpack(1); if (crcCheck & igtl::MessageHeader::UNPACK_HEADER) { // Allocate a time stamp igtl::TimeStamp::Pointer ts; ts = igtl::TimeStamp::New(); // Get time stamp igtlUint32 sec; igtlUint32 nanosec; headerMsg->GetTimeStamp(ts); ts->GetTimeStamp(&sec, &nanosec); // std::cerr << "Time stamp: " // << sec << "." // << nanosec << std::endl; // std::cerr << "Dev type and name: " << headerMsg->GetDeviceType() << " " // << headerMsg->GetDeviceName() << std::endl; // headerMsg->Print(std::cout); //check the type of the received message //if it is a GET_, STP_ or RTS_ command push it into the command queue //otherwise continue reading the whole message from the socket const char* curDevType = headerMsg->GetDeviceType(); if (std::strstr(curDevType, "GET_") != nullptr || std::strstr(curDevType, "STP_") != nullptr || std::strstr(curDevType, "RTS_") != nullptr) { this->m_MessageQueue->PushCommandMessage(headerMsg); this->InvokeEvent(CommandReceivedEvent()); return IGTL_STATUS_OK; } //Create a message according to the header message igtl::MessageBase::Pointer curMessage; curMessage = m_MessageFactory->CreateInstance(headerMsg); //check if the curMessage is created properly, if not the message type is //not supported and the message has to be skipped if (curMessage.IsNull()) { socket->Skip(headerMsg->GetBodySizeToRead(), 0); // MITK_ERROR("IGTLDevice") << "The received type is not supported. Please " // "add it to the message factory."; return IGTL_STATUS_NOT_FOUND; } //insert the header to the message and allocate the pack curMessage->SetMessageHeader(headerMsg); curMessage->AllocatePack(); // Receive transform data from the socket int receiveCheck = 0; receiveCheck = socket->Receive(curMessage->GetPackBodyPointer(), curMessage->GetPackBodySize(), m_ReadFully); if (receiveCheck > 0) { int c = curMessage->Unpack(1); if (!(c & igtl::MessageHeader::UNPACK_BODY)) { return IGTL_STATUS_CHECKSUM_ERROR; } //check the type of the received message //if it is a command push it into the command queue //otherwise into the normal receive queue //STP_ commands are handled here because they implemented additional //member variables that are not stored in the header message if (std::strstr(curDevType, "STT_") != nullptr) { this->m_MessageQueue->PushCommandMessage(curMessage); this->InvokeEvent(CommandReceivedEvent()); } else { this->m_MessageQueue->PushMessage(curMessage); this->InvokeEvent(MessageReceivedEvent()); } return IGTL_STATUS_OK; } else { MITK_ERROR("IGTLDevice") << "Received a valid header but could not " << "read the whole message."; return IGTL_STATUS_UNKNOWN_ERROR; } } else { //CRC check failed MITK_ERROR << "CRC Check failed"; return IGTL_STATUS_CHECKSUM_ERROR; } } else { //Message size information and actual data size don't match. //this state is not suppossed to be reached, return unknown error MITK_ERROR << "IGTL status unknown"; return IGTL_STATUS_UNKNOWN_ERROR; } } void mitk::IGTLDevice::SendMessage(const mitk::IGTLMessage* msg) { this->SendMessage(msg->GetMessage()); } void mitk::IGTLDevice::SendMessage(igtl::MessageBase::Pointer msg) { //add the message to the queue m_MessageQueue->PushSendMessage(msg); } unsigned int mitk::IGTLDevice::SendMessagePrivate(igtl::MessageBase::Pointer msg, igtl::Socket::Pointer socket) { //check the input message if (msg.IsNull()) { MITK_ERROR("IGTLDevice") << "Could not send message because message is not " "valid. Please check."; return false; } // add the name of this device to the message msg->SetDeviceName(this->GetName().c_str()); // Pack (serialize) and send msg->Pack(); int sendSuccess = socket->Send(msg->GetPackPointer(), msg->GetPackSize()); if (sendSuccess) { this->InvokeEvent(MessageSentEvent()); return IGTL_STATUS_OK; } else { return IGTL_STATUS_UNKNOWN_ERROR; } } void mitk::IGTLDevice::RunCommunication(void (IGTLDevice::*ComFunction)(void), itk::FastMutexLock* mutex) { if (this->GetState() != Running) return; try { // keep lock until end of scope MutexLockHolder communicationFinishedLockHolder(*mutex); // Because m_StopCommunication is used by two threads, access has to be guarded // by a mutex. To minimize thread locking, a local copy is used here bool localStopCommunication; // update the local copy of m_StopCommunication this->m_StopCommunicationMutex->Lock(); localStopCommunication = this->m_StopCommunication; this->m_StopCommunicationMutex->Unlock(); while ((this->GetState() == Running) && (localStopCommunication == false)) { (this->*ComFunction)(); /* Update the local copy of m_StopCommunication */ this->m_StopCommunicationMutex->Lock(); localStopCommunication = m_StopCommunication; this->m_StopCommunicationMutex->Unlock(); // time to relax, this sets the maximum ever possible framerate to 1000 Hz itksys::SystemTools::Delay(1); } } catch (...) { mutex->Unlock(); this->StopCommunication(); MITK_ERROR("IGTLDevice::RunCommunication") << "Error while communicating. Thread stopped."; //mitkThrowException(mitk::IGTException) << "Error while communicating. Thread stopped."; } // StopCommunication was called, thus the mode should be changed back to Ready now // that the tracking loop has ended. //this->SetState(Ready); //this is done elsewhere MITK_DEBUG("IGTLDevice::RunCommunication") << "Reached end of communication."; // returning from this function (and ThreadStartCommunication()) // this will end the thread return; } bool mitk::IGTLDevice::StartCommunication() { if (this->GetState() != Ready) return false; // go to mode Running this->SetState(Running); // set a timeout for the sending and receiving this->m_Socket->SetTimeout(SOCKET_SEND_RECEIVE_TIMEOUT_MSEC); // update the local copy of m_StopCommunication this->m_StopCommunicationMutex->Lock(); this->m_StopCommunication = false; this->m_StopCommunicationMutex->Unlock(); // transfer the execution rights to tracking thread m_SendingFinishedMutex->Unlock(); m_ReceivingFinishedMutex->Unlock(); m_ConnectingFinishedMutex->Unlock(); // start new threads that execute the communication m_SendThreadID = m_MultiThreader->SpawnThread(this->ThreadStartSending, this); m_ReceiveThreadID = m_MultiThreader->SpawnThread(this->ThreadStartReceiving, this); m_ConnectThreadID = m_MultiThreader->SpawnThread(this->ThreadStartConnecting, this); // mitk::IGTTimeStamp::GetInstance()->Start(this); return true; } bool mitk::IGTLDevice::StopCommunication() { if (this->GetState() == Running) // Only if the object is in the correct state { // m_StopCommunication is used by two threads, so we have to ensure correct // thread handling m_StopCommunicationMutex->Lock(); m_StopCommunication = true; m_StopCommunicationMutex->Unlock(); // we have to wait here that the other thread recognizes the STOP-command // and executes it m_SendingFinishedMutex->Lock(); m_ReceivingFinishedMutex->Lock(); m_ConnectingFinishedMutex->Lock(); // mitk::IGTTimeStamp::GetInstance()->Stop(this); // notify realtime clock // StopCommunication was called, thus the mode should be changed back // to Ready now that the tracking loop has ended. this->SetState(Ready); } return true; } bool mitk::IGTLDevice::CloseConnection() { if (this->GetState() == Setup) { return true; } else if (this->GetState() == Running) { this->StopCommunication(); } m_Socket->CloseSocket(); /* return to setup mode */ this->SetState(Setup); // this->InvokeEvent(mitk::LostConnectionEvent()); return true; } bool mitk::IGTLDevice::SendRTSMessage(const char* type) { //construct the device type for the return message, it starts with RTS_ and //continues with the requested type std::string returnType("RTS_"); returnType.append(type); //create a return message igtl::MessageBase::Pointer rtsMsg = this->m_MessageFactory->CreateInstance(returnType); //if retMsg is NULL there is no return message defined and thus it is not //necessary to send one back if (rtsMsg.IsNotNull()) { this->SendMessage(rtsMsg); return true; } else { return false; } } void mitk::IGTLDevice::Connect() { MITK_DEBUG << "mitk::IGTLDevice::Connect();"; } igtl::ImageMessage::Pointer mitk::IGTLDevice::GetNextImage2dMessage() { return this->m_MessageQueue->PullImage2dMessage(); } igtl::ImageMessage::Pointer mitk::IGTLDevice::GetNextImage3dMessage() { return this->m_MessageQueue->PullImage3dMessage(); } igtl::TransformMessage::Pointer mitk::IGTLDevice::GetNextTransformMessage() { return this->m_MessageQueue->PullTransformMessage(); } igtl::TrackingDataMessage::Pointer mitk::IGTLDevice::GetNextTrackingDataMessage() { igtl::TrackingDataMessage::Pointer msg = this->m_MessageQueue->PullTrackingMessage(); return msg; } igtl::StringMessage::Pointer mitk::IGTLDevice::GetNextStringMessage() { return this->m_MessageQueue->PullStringMessage(); } igtl::MessageBase::Pointer mitk::IGTLDevice::GetNextMiscMessage() { return this->m_MessageQueue->PullMiscMessage(); } igtl::MessageBase::Pointer mitk::IGTLDevice::GetNextCommand() { return m_MessageQueue->PullCommandMessage(); } void mitk::IGTLDevice::EnableInfiniteBufferingMode( mitk::IGTLMessageQueue::Pointer queue, bool enable) { queue->EnableInfiniteBuffering(enable); } ITK_THREAD_RETURN_TYPE mitk::IGTLDevice::ThreadStartSending(void* pInfoStruct) { /* extract this pointer from Thread Info structure */ struct itk::MultiThreader::ThreadInfoStruct * pInfo = (struct itk::MultiThreader::ThreadInfoStruct*)pInfoStruct; if (pInfo == nullptr) { return ITK_THREAD_RETURN_VALUE; } if (pInfo->UserData == nullptr) { return ITK_THREAD_RETURN_VALUE; } IGTLDevice *igtlDevice = (IGTLDevice*)pInfo->UserData; if (igtlDevice != nullptr) { igtlDevice->RunCommunication(&mitk::IGTLDevice::Send, igtlDevice->m_SendingFinishedMutex); } igtlDevice->m_SendThreadID = 0; // erase thread id because thread will end. return ITK_THREAD_RETURN_VALUE; } ITK_THREAD_RETURN_TYPE mitk::IGTLDevice::ThreadStartReceiving(void* pInfoStruct) { /* extract this pointer from Thread Info structure */ struct itk::MultiThreader::ThreadInfoStruct * pInfo = (struct itk::MultiThreader::ThreadInfoStruct*)pInfoStruct; if (pInfo == nullptr) { return ITK_THREAD_RETURN_VALUE; } if (pInfo->UserData == nullptr) { return ITK_THREAD_RETURN_VALUE; } IGTLDevice *igtlDevice = (IGTLDevice*)pInfo->UserData; if (igtlDevice != nullptr) { igtlDevice->RunCommunication(&mitk::IGTLDevice::Receive, igtlDevice->m_ReceivingFinishedMutex); } igtlDevice->m_ReceiveThreadID = 0; // erase thread id because thread will end. return ITK_THREAD_RETURN_VALUE; } ITK_THREAD_RETURN_TYPE mitk::IGTLDevice::ThreadStartConnecting(void* pInfoStruct) { /* extract this pointer from Thread Info structure */ struct itk::MultiThreader::ThreadInfoStruct * pInfo = (struct itk::MultiThreader::ThreadInfoStruct*)pInfoStruct; if (pInfo == nullptr) { return ITK_THREAD_RETURN_VALUE; } if (pInfo->UserData == nullptr) { return ITK_THREAD_RETURN_VALUE; } IGTLDevice *igtlDevice = (IGTLDevice*)pInfo->UserData; if (igtlDevice != nullptr) { igtlDevice->RunCommunication(&mitk::IGTLDevice::Connect, igtlDevice->m_ConnectingFinishedMutex); } igtlDevice->m_ConnectThreadID = 0; // erase thread id because thread will end. return ITK_THREAD_RETURN_VALUE; } diff --git a/Modules/OpenIGTLink/mitkIGTLMessageQueue.cpp b/Modules/OpenIGTLink/mitkIGTLMessageQueue.cpp index 86aebe3253..4d2244067b 100644 --- a/Modules/OpenIGTLink/mitkIGTLMessageQueue.cpp +++ b/Modules/OpenIGTLink/mitkIGTLMessageQueue.cpp @@ -1,312 +1,312 @@ /*=================================================================== 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 "mitkIGTLMessageQueue.h" #include #include "igtlMessageBase.h" void mitk::IGTLMessageQueue::PushSendMessage(igtl::MessageBase::Pointer message) { this->m_Mutex->Lock(); if (this->m_BufferingType == IGTLMessageQueue::Infinit) m_SendQueue.clear(); m_SendQueue.push_back(message); this->m_Mutex->Unlock(); } void mitk::IGTLMessageQueue::PushCommandMessage(igtl::MessageBase::Pointer message) { this->m_Mutex->Lock(); if (this->m_BufferingType == IGTLMessageQueue::Infinit) m_CommandQueue.clear(); m_CommandQueue.push_back(message); this->m_Mutex->Unlock(); } void mitk::IGTLMessageQueue::PushMessage(igtl::MessageBase::Pointer msg) { this->m_Mutex->Lock(); std::stringstream infolog; infolog << "Received message of type "; if (dynamic_cast(msg.GetPointer()) != nullptr) { if (this->m_BufferingType == IGTLMessageQueue::Infinit) m_TrackingDataQueue.clear(); this->m_TrackingDataQueue.push_back(dynamic_cast(msg.GetPointer())); infolog << "TDATA"; } else if (dynamic_cast(msg.GetPointer()) != nullptr) { if (this->m_BufferingType == IGTLMessageQueue::Infinit) m_TransformQueue.clear(); this->m_TransformQueue.push_back(dynamic_cast(msg.GetPointer())); infolog << "TRANSFORM"; } else if (dynamic_cast(msg.GetPointer()) != nullptr) { if (this->m_BufferingType == IGTLMessageQueue::Infinit) m_StringQueue.clear(); this->m_StringQueue.push_back(dynamic_cast(msg.GetPointer())); infolog << "STRING"; } else if (dynamic_cast(msg.GetPointer()) != nullptr) { igtl::ImageMessage::Pointer imageMsg = dynamic_cast(msg.GetPointer()); int* dim = new int[3]; imageMsg->GetDimensions(dim); if (dim[2] > 1) { if (this->m_BufferingType == IGTLMessageQueue::Infinit) m_Image3dQueue.clear(); this->m_Image3dQueue.push_back(dynamic_cast(msg.GetPointer())); infolog << "IMAGE3D"; } else { if (this->m_BufferingType == IGTLMessageQueue::Infinit) m_Image2dQueue.clear(); this->m_Image2dQueue.push_back(dynamic_cast(msg.GetPointer())); infolog << "IMAGE2D"; } } else { if (this->m_BufferingType == IGTLMessageQueue::Infinit) m_MiscQueue.clear(); this->m_MiscQueue.push_back(msg); infolog << "OTHER"; } m_Latest_Message = msg; - MITK_INFO << infolog.str(); + //MITK_INFO << infolog.str(); this->m_Mutex->Unlock(); } igtl::MessageBase::Pointer mitk::IGTLMessageQueue::PullSendMessage() { igtl::MessageBase::Pointer ret = nullptr; this->m_Mutex->Lock(); if (this->m_SendQueue.size() > 0) { ret = this->m_SendQueue.front(); this->m_SendQueue.pop_front(); } this->m_Mutex->Unlock(); return ret; } igtl::MessageBase::Pointer mitk::IGTLMessageQueue::PullMiscMessage() { igtl::MessageBase::Pointer ret = nullptr; this->m_Mutex->Lock(); if (this->m_MiscQueue.size() > 0) { ret = this->m_MiscQueue.front(); this->m_MiscQueue.pop_front(); } this->m_Mutex->Unlock(); return ret; } igtl::ImageMessage::Pointer mitk::IGTLMessageQueue::PullImage2dMessage() { igtl::ImageMessage::Pointer ret = nullptr; this->m_Mutex->Lock(); if (this->m_Image2dQueue.size() > 0) { ret = this->m_Image2dQueue.front(); this->m_Image2dQueue.pop_front(); } this->m_Mutex->Unlock(); return ret; } igtl::ImageMessage::Pointer mitk::IGTLMessageQueue::PullImage3dMessage() { igtl::ImageMessage::Pointer ret = nullptr; this->m_Mutex->Lock(); if (this->m_Image3dQueue.size() > 0) { ret = this->m_Image3dQueue.front(); this->m_Image3dQueue.pop_front(); } this->m_Mutex->Unlock(); return ret; } igtl::TrackingDataMessage::Pointer mitk::IGTLMessageQueue::PullTrackingMessage() { igtl::TrackingDataMessage::Pointer ret = nullptr; this->m_Mutex->Lock(); if (this->m_TrackingDataQueue.size() > 0) { ret = this->m_TrackingDataQueue.front(); this->m_TrackingDataQueue.pop_front(); } this->m_Mutex->Unlock(); return ret; } igtl::MessageBase::Pointer mitk::IGTLMessageQueue::PullCommandMessage() { igtl::MessageBase::Pointer ret = nullptr; this->m_Mutex->Lock(); if (this->m_CommandQueue.size() > 0) { ret = this->m_CommandQueue.front(); this->m_CommandQueue.pop_front(); } this->m_Mutex->Unlock(); return ret; } igtl::StringMessage::Pointer mitk::IGTLMessageQueue::PullStringMessage() { igtl::StringMessage::Pointer ret = nullptr; this->m_Mutex->Lock(); if (this->m_StringQueue.size() > 0) { ret = this->m_StringQueue.front(); this->m_StringQueue.pop_front(); } this->m_Mutex->Unlock(); return ret; } igtl::TransformMessage::Pointer mitk::IGTLMessageQueue::PullTransformMessage() { igtl::TransformMessage::Pointer ret = nullptr; this->m_Mutex->Lock(); if (this->m_TransformQueue.size() > 0) { ret = this->m_TransformQueue.front(); this->m_TransformQueue.pop_front(); } this->m_Mutex->Unlock(); return ret; } std::string mitk::IGTLMessageQueue::GetNextMsgInformationString() { this->m_Mutex->Lock(); std::stringstream s; if (this->m_Latest_Message != nullptr) { s << "Device Type: " << this->m_Latest_Message->GetDeviceType() << std::endl; s << "Device Name: " << this->m_Latest_Message->GetDeviceName() << std::endl; } else { s << "No Msg"; } this->m_Mutex->Unlock(); return s.str(); } std::string mitk::IGTLMessageQueue::GetNextMsgDeviceType() { this->m_Mutex->Lock(); std::stringstream s; if (m_Latest_Message != nullptr) { s << this->m_Latest_Message->GetDeviceType(); } else { s << ""; } this->m_Mutex->Unlock(); return s.str(); } std::string mitk::IGTLMessageQueue::GetLatestMsgInformationString() { this->m_Mutex->Lock(); std::stringstream s; if (m_Latest_Message != nullptr) { s << "Device Type: " << this->m_Latest_Message->GetDeviceType() << std::endl; s << "Device Name: " << this->m_Latest_Message->GetDeviceName() << std::endl; } else { s << "No Msg"; } this->m_Mutex->Unlock(); return s.str(); } std::string mitk::IGTLMessageQueue::GetLatestMsgDeviceType() { this->m_Mutex->Lock(); std::stringstream s; if (m_Latest_Message != nullptr) { s << this->m_Latest_Message->GetDeviceType(); } else { s << ""; } this->m_Mutex->Unlock(); return s.str(); } int mitk::IGTLMessageQueue::GetSize() { return (this->m_CommandQueue.size() + this->m_Image2dQueue.size() + this->m_Image3dQueue.size() + this->m_MiscQueue.size() + this->m_StringQueue.size() + this->m_TrackingDataQueue.size() + this->m_TransformQueue.size()); } void mitk::IGTLMessageQueue::EnableInfiniteBuffering(bool enable) { this->m_Mutex->Lock(); if (enable) this->m_BufferingType = IGTLMessageQueue::BufferingType::Infinit; else this->m_BufferingType = IGTLMessageQueue::BufferingType::NoBuffering; this->m_Mutex->Unlock(); } mitk::IGTLMessageQueue::IGTLMessageQueue() { this->m_Mutex = itk::FastMutexLock::New(); this->m_BufferingType = IGTLMessageQueue::Infinit; } mitk::IGTLMessageQueue::~IGTLMessageQueue() { this->m_Mutex->Unlock(); } \ No newline at end of file diff --git a/Plugins/PluginList.cmake b/Plugins/PluginList.cmake index 199198f500..781bb1f751 100644 --- a/Plugins/PluginList.cmake +++ b/Plugins/PluginList.cmake @@ -1,98 +1,99 @@ # Plug-ins must be ordered according to their dependencies set(MITK_PLUGINS org.blueberry.core.runtime:ON org.blueberry.core.expressions:OFF org.blueberry.core.commands:OFF org.blueberry.core.jobs:OFF org.blueberry.ui.qt:OFF org.blueberry.ui.qt.help:OFF org.blueberry.ui.qt.log:ON org.blueberry.ui.qt.objectinspector:OFF #org.blueberry.test:ON #org.blueberry.uitest:ON #Testing/org.blueberry.core.runtime.tests:ON #Testing/org.blueberry.osgi.tests:ON org.mitk.core.services:ON org.mitk.gui.common:ON org.mitk.planarfigure:ON org.mitk.core.ext:OFF org.mitk.core.jobs:OFF org.mitk.simulation:OFF org.mitk.gui.qt.application:ON org.mitk.gui.qt.coreapplication:OFF org.mitk.gui.qt.ext:OFF org.mitk.gui.qt.extapplication:OFF org.mitk.gui.qt.common:ON org.mitk.gui.qt.stdmultiwidgeteditor:ON org.mitk.gui.qt.common.legacy:OFF org.mitk.gui.qt.cmdlinemodules:OFF org.mitk.gui.qt.diffusionimagingapp:OFF org.mitk.gui.qt.datamanager:ON org.mitk.gui.qt.datamanagerlight:OFF org.mitk.gui.qt.properties:ON org.mitk.gui.qt.basicimageprocessing:OFF org.mitk.gui.qt.dicom:OFF org.mitk.gui.qt.dicominspector:OFF org.mitk.gui.qt.diffusionimaging:OFF org.mitk.gui.qt.diffusionimaging.connectomics:OFF org.mitk.gui.qt.diffusionimaging.denoising:OFF org.mitk.gui.qt.diffusionimaging.fiberfox:OFF org.mitk.gui.qt.diffusionimaging.fiberprocessing:OFF org.mitk.gui.qt.diffusionimaging.ivim:OFF org.mitk.gui.qt.diffusionimaging.odfpeaks:OFF org.mitk.gui.qt.diffusionimaging.partialvolume:OFF org.mitk.gui.qt.diffusionimaging.preprocessing:OFF org.mitk.gui.qt.diffusionimaging.reconstruction:OFF org.mitk.gui.qt.diffusionimaging.registration:OFF org.mitk.gui.qt.diffusionimaging.tbss:OFF org.mitk.gui.qt.diffusionimaging.tractography:OFF org.mitk.gui.qt.dosevisualization:OFF org.mitk.gui.qt.geometrytools:OFF org.mitk.gui.qt.igtexamples:OFF org.mitk.gui.qt.igttracking:OFF org.mitk.gui.qt.openigtlink:OFF org.mitk.gui.qt.imagecropper:OFF org.mitk.gui.qt.imagenavigator:ON org.mitk.gui.qt.viewnavigator:OFF org.mitk.gui.qt.materialeditor:OFF org.mitk.gui.qt.measurementtoolbox:OFF org.mitk.gui.qt.moviemaker:OFF org.mitk.gui.qt.pointsetinteraction:OFF org.mitk.gui.qt.pointsetinteractionmultispectrum:OFF org.mitk.gui.qt.python:OFF org.mitk.gui.qt.registration:OFF org.mitk.gui.qt.remeshing:OFF org.mitk.gui.qt.segmentation:OFF org.mitk.gui.qt.simulation:OFF org.mitk.gui.qt.aicpregistration:OFF org.mitk.gui.qt.renderwindowmanager:OFF org.mitk.gui.qt.toftutorial:OFF org.mitk.gui.qt.tofutil:OFF org.mitk.gui.qt.tubegraph:OFF org.mitk.gui.qt.ugvisualization:OFF org.mitk.gui.qt.ultrasound:OFF org.mitk.gui.qt.volumevisualization:OFF org.mitk.gui.qt.eventrecorder:OFF org.mitk.gui.qt.xnat:OFF org.mitk.gui.qt.igt.app.echotrack:OFF org.mitk.gui.qt.spectrocamrecorder:OFF org.mitk.gui.qt.classificationsegmentation:OFF org.mitk.gui.qt.overlaymanager:OFF + org.mitk.gui.qt.igt.app.hummelprotocolmeasurements:OFF org.mitk.gui.qt.multilabelsegmentation:OFF org.mitk.matchpoint.core.helper:OFF org.mitk.gui.qt.matchpoint.algorithm.browser:OFF org.mitk.gui.qt.matchpoint.algorithm.control:OFF org.mitk.gui.qt.matchpoint.algorithm.batch:OFF org.mitk.gui.qt.matchpoint.mapper:OFF org.mitk.gui.qt.matchpoint.framereg:OFF org.mitk.gui.qt.matchpoint.visualizer:OFF org.mitk.gui.qt.matchpoint.evaluator:OFF org.mitk.gui.qt.matchpoint.manipulator:OFF org.mitk.gui.qt.cest:OFF ) diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/CMakeLists.txt b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/CMakeLists.txt new file mode 100644 index 0000000000..4a35b65739 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/CMakeLists.txt @@ -0,0 +1,7 @@ +project(org_mitk_gui_qt_igtapphummelprotocolmeasurements) + +mitk_create_plugin( + EXPORT_DIRECTIVE IGTTRACKINGSEMIAUTOMATICMEASUREMENT_EXPORT + EXPORTED_INCLUDE_SUFFIXES src + MODULE_DEPENDS MitkIGT MitkIGTUI MitkCameraCalibration +) diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/Manual.dox b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/Manual.dox new file mode 100644 index 0000000000..1bfc037b22 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/Manual.dox @@ -0,0 +1,19 @@ +/** +\bundlemainpage{org.mitk.gui.qt.igttrackingsemiautomaticmeasurement} IGT Tracking Semi Automatic Measurement + +\imageMacro{icon.png,"Icon of IGT Tracking Semi Automatic Measurement",2.00} + +Available sections: + - \ref org.mitk.gui.qt.igttrackingsemiautomaticmeasurementOverview + +\section org.mitk.gui.qt.igttrackingsemiautomaticmeasurementOverview +Dieses PlugIn dient zur semiautomatischen Aufzeichnung von Messreihen mit Trackingsystemen. Entsprechend konfiguriert ist es auch für das Hummel-Protokoll einsetzbar. Zentrale Komponente des PlugIns ist eine IGT Pipeline zur Messdatenaufzeichnung, wie in Abbildung 1 dargestellt. + +\imageMacro{pipeline.png,"Icon of IGT Tracking Semi Automatic Measurement",10.00} + +Ein Screenshot der Benutzeroberfläche des PlugIns ist in Abbildung 2 zu sehen. Das Initialisieren und Starten des Trackingsystems erfolgt dabei im nicht dargestellten Tab "Tracking Initialization", der im Wesentlichen aus dem TrackingDeviceConfigurationWidget besteht. Zur Durchführung der Messungen unterstützt das PlugIn das Laden einer Liste mit Dateinamen für die Messungen, wie im oberen Teil des Screenshots zu sehen. Diese Liste wird abgearbeitet, wobei mit dem Button "Start Next Measurement" jeweils die nächste Messung gestartet wird. Die während der Messung aufgezeichneten Daten werden in eine Datei das Ausgabeverzeichnis geschrieben. Dabei entspricht der Dateiname dem aktuellen Namen aus der Liste. Die Anzahl der aufzuzeichnenden Messwerte pro Messung kann in den Einstellungen angegeben werden. Gab es bei einer Messung einen Fehler kann die Messung durch Auswahl des entsprechenden Buttons auch wiederholt werden. + +\imageMacro{screenshot.png,"Icon of IGT Tracking Semi Automatic Measurement",10.00} + +Das PlugIn unterstützt außerdem die Ansteuerung eines zweiten Trackingsystems. Dieses System soll einen am Phantom angebrachtes Tool (Reference Sensor) tracken und so sicherstellen, dass sich das Phantom während der Messung nicht bewegt. Wurde eine Bewegung des Phantoms festgestellt wird im unteren Teil des PlugIns "NOT OK" angezeigt und die Messung muss ggf. wiederholt werden. +*/ diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/icon.png b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/icon.png new file mode 100644 index 0000000000..c6d6160a7b Binary files /dev/null and b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/icon.png differ diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/pipeline.png b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/pipeline.png new file mode 100644 index 0000000000..c241b19f0c Binary files /dev/null and b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/pipeline.png differ diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/screenshot.png b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/screenshot.png new file mode 100644 index 0000000000..9aa1ad96e3 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/Manual/screenshot.png differ diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/doxygen/modules.dox b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/doxygen/modules.dox new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/documentation/doxygen/modules.dox @@ -0,0 +1 @@ + diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/files.cmake b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/files.cmake new file mode 100644 index 0000000000..dca2349880 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/files.cmake @@ -0,0 +1,42 @@ +SET(SRC_CPP_FILES + +) + +SET(INTERNAL_CPP_FILES + QmitkIGTTrackingSemiAutomaticMeasurementView.cpp + QmitkIGTTrackingDataEvaluationView.cpp + mitkPluginActivator.cpp + mitkNavigationDataCSVSequentialPlayer.cpp + mitkHummelProtocolEvaluation.cpp +) + +SET(UI_FILES + src/internal/QmitkIGTTrackingSemiAutomaticMeasurementViewControls.ui + src/internal/QmitkIGTTrackingDataEvaluationViewControls.ui +) + +SET(MOC_H_FILES + src/internal/QmitkIGTTrackingSemiAutomaticMeasurementView.h + src/internal/QmitkIGTTrackingDataEvaluationView.h + src/internal/mitkPluginActivator.h +) + +SET(CACHED_RESOURCE_FILES + resources/iconMeasurementTracking.svg + resources/iconMeasurementEvaluation.svg + plugin.xml +) + +SET(QRC_FILES + resources/QmitkIGTTrackingSemiAutomaticMeasurementView.qrc +) + +SET(CPP_FILES) + +foreach(file ${SRC_CPP_FILES}) + SET(CPP_FILES ${CPP_FILES} src/${file}) +endforeach(file ${SRC_CPP_FILES}) + +foreach(file ${INTERNAL_CPP_FILES}) + SET(CPP_FILES ${CPP_FILES} src/internal/${file}) +endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/manifest_headers.cmake new file mode 100644 index 0000000000..3c096ce558 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/manifest_headers.cmake @@ -0,0 +1,5 @@ +set(Plugin-Name "IGT APP: Hummel Protocol Measurements") +set(Plugin-Version "0.1") +set(Plugin-Vendor "DKFZ, Medical and Biological Informatics") +set(Plugin-ContactAddress "http://www.mitk.org") +set(Require-Plugin org.mitk.gui.qt.common.legacy) diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/plugin.xml b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/plugin.xml new file mode 100644 index 0000000000..fcffbca51a --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/plugin.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/QmitkIGTTrackingSemiAutomaticMeasurementView.qrc b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/QmitkIGTTrackingSemiAutomaticMeasurementView.qrc new file mode 100644 index 0000000000..ec7237a283 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/QmitkIGTTrackingSemiAutomaticMeasurementView.qrc @@ -0,0 +1,8 @@ + + + iconMeasurementTracking.svg + + + iconMeasurementEvaluation.svg + + diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/iconMeasurementEvaluation.svg b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/iconMeasurementEvaluation.svg new file mode 100644 index 0000000000..041b2ba8c2 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/iconMeasurementEvaluation.svg @@ -0,0 +1,1177 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/iconMeasurementTracking.svg b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/iconMeasurementTracking.svg new file mode 100644 index 0000000000..7d859cb212 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/resources/iconMeasurementTracking.svg @@ -0,0 +1,1263 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationView.cpp b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationView.cpp new file mode 100644 index 0000000000..605e16b151 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationView.cpp @@ -0,0 +1,1295 @@ +/*========================================================================= + +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 + +// Blueberry +#include +#include + +// Qmitk +#include "QmitkIGTTrackingDataEvaluationView.h" +#include "QmitkStdMultiWidget.h" + +// Qt +#include +#include +#include + +// MITK +#include "mitkNavigationDataCSVSequentialPlayer.h" +#include +#include +#include +#include +#include + + +//ITK +#include + +//VNL +#include + +//vtk headers +#include +#include +#include + +const std::string QmitkIGTTrackingDataEvaluationView::VIEW_ID = "org.mitk.views.igttrackingdataevaluation"; + +QmitkIGTTrackingDataEvaluationView::QmitkIGTTrackingDataEvaluationView() + : QmitkFunctionality() + , m_Controls(0) + , m_MultiWidget(NULL) + , m_scalingfactor(1) +{ + m_CSVtoXMLInputFilenameVector = std::vector(); + m_CSVtoXMLOutputFilenameVector = std::vector(); +} + +QmitkIGTTrackingDataEvaluationView::~QmitkIGTTrackingDataEvaluationView() +{ +} + +void QmitkIGTTrackingDataEvaluationView::CreateQtPartControl(QWidget *parent) +{ + // build up qt view, unless already done + if (!m_Controls) + { + // create GUI widgets from the Qt Designer's .ui file + m_Controls = new Ui::QmitkIGTTrackingDataEvaluationViewControls; + m_Controls->setupUi(parent); + + connect(m_Controls->m_LoadInputFileList, SIGNAL(clicked()), this, SLOT(OnLoadFileList())); + connect(m_Controls->m_StartEvaluation, SIGNAL(clicked()), this, SLOT(OnEvaluateData())); + connect(m_Controls->m_AddToCurrentList, SIGNAL(clicked()), this, SLOT(OnAddToCurrentList())); + connect(m_Controls->m_GeneratePointSetOfMeanPositions, SIGNAL(clicked()), this, SLOT(OnGeneratePointSet())); + connect(m_Controls->m_GenerateRotationLines, SIGNAL(clicked()), this, SLOT(OnGenerateRotationLines())); + connect(m_Controls->m_GeneratePointSet, SIGNAL(clicked()), this, SLOT(OnGenerateGroundTruthPointSet())); + connect(m_Controls->m_Convert, SIGNAL(clicked()), this, SLOT(OnConvertCSVtoXMLFile())); + connect(m_Controls->m_loadCSVtoXMLInputList, SIGNAL(clicked()), this, SLOT(OnCSVtoXMLLoadInputList())); + connect(m_Controls->m_loadCSVtoXMLOutputList, SIGNAL(clicked()), this, SLOT(OnCSVtoXMLLoadOutputList())); + connect(m_Controls->m_OrientationCalculationGenerateReference, SIGNAL(clicked()), this, SLOT(OnOrientationCalculation_CalcRef())); + connect(m_Controls->m_OrientationCalculationWriteOrientationsToFile, SIGNAL(clicked()), this, SLOT(OnOrientationCalculation_CalcOrientandWriteToFile())); + connect(m_Controls->m_GeneratePointSetsOfSinglePositions, SIGNAL(clicked()), this, SLOT(OnGeneratePointSetsOfSinglePositions())); + connect(m_Controls->m_StartEvaluationAll, SIGNAL(clicked()), this, SLOT(OnEvaluateDataAll())); + connect(m_Controls->m_GridMatching, SIGNAL(clicked()), this, SLOT(OnPerfomGridMatching())); + connect(m_Controls->m_ComputeRotation, SIGNAL(clicked()), this, SLOT(OnComputeRotation())); + + //initialize data storage combo boxes + m_Controls->m_ReferencePointSetComboBox->SetDataStorage(this->GetDataStorage()); + m_Controls->m_ReferencePointSetComboBox->SetAutoSelectNewItems(true); + m_Controls->m_ReferencePointSetComboBox->SetPredicate(mitk::NodePredicateDataType::New("PointSet")); + m_Controls->m_MeasurementPointSetComboBox->SetDataStorage(this->GetDataStorage()); + m_Controls->m_MeasurementPointSetComboBox->SetAutoSelectNewItems(true); + m_Controls->m_MeasurementPointSetComboBox->SetPredicate(mitk::NodePredicateDataType::New("PointSet")); + } +} + +void QmitkIGTTrackingDataEvaluationView::OnComputeRotation() +{ + //Get all data from UI + auto EvaluationDataCollection = GetAllDataFromUIList(); + //Compute mean Quaternions + auto OrientationVector = GetMeanOrientationsOfAllData(EvaluationDataCollection); + + //Compute Rotations + + itk::Vector rotationVec; + //adapt for Aurora 5D tools: [0,0,1000] + rotationVec[0] = m_Controls->m_rotVecX->value(); //X + rotationVec[1] = m_Controls->m_rotVecY->value(); //Y + rotationVec[2] = m_Controls->m_rotVecZ->value(); //Z + + + std::vector allOrientationErrors; + for (int i = 0; i < (OrientationVector.size() - 1); i++) + { + double AngleBetweenTwoQuaternions = mitk::StaticIGTHelperFunctions::GetAngleBetweenTwoQuaterions(OrientationVector.at(i), OrientationVector.at(i+1), rotationVec); + double AngularError = 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 orientationErrorStatistics; + orientationErrorStatistics = mitk::HummelProtocolEvaluation::ComputeStatistics(allOrientationErrors); + MITK_INFO << "## Rotation error statistics: ##"; + for (auto stat : orientationErrorStatistics) { MITK_INFO << stat.description << ": " << stat.distanceError; } + + //write results to file + allOrientationErrors.insert(allOrientationErrors.end(), orientationErrorStatistics.begin(), orientationErrorStatistics.end()); + allOrientationErrors.push_back({rotationVec[0],"Rot Vector [x]"}); + allOrientationErrors.push_back({rotationVec[1], "Rot Vector [y]"}); + allOrientationErrors.push_back({rotationVec[2], "Rot Vector [z]"}); + std::stringstream filenameOrientationStat; + filenameOrientationStat << std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str() << ".orientationStatistics.csv"; + MITK_INFO << "Writing output to file " << filenameOrientationStat.str(); + writeToFile(filenameOrientationStat.str(), allOrientationErrors); +} + +void QmitkIGTTrackingDataEvaluationView::OnPerfomGridMatching() +{ + mitk::PointSet::Pointer reference = dynamic_cast(m_Controls->m_ReferencePointSetComboBox->GetSelectedNode()->GetData()); + mitk::PointSet::Pointer measurement = dynamic_cast(m_Controls->m_MeasurementPointSetComboBox->GetSelectedNode()->GetData()); + //convert point sets to vtk poly data + vtkSmartPointer sourcePoints = vtkSmartPointer::New(); + vtkSmartPointer targetPoints = vtkSmartPointer::New(); + for (int i = 0; iGetSize(); i++) + { + double point[3] = { reference->GetPoint(i)[0], reference->GetPoint(i)[1], reference->GetPoint(i)[2] }; + sourcePoints->InsertNextPoint(point); + double point_targets[3] = { measurement->GetPoint(i)[0], measurement->GetPoint(i)[1], measurement->GetPoint(i)[2] }; + targetPoints->InsertNextPoint(point_targets); + } + //compute transform + vtkSmartPointer transform = vtkSmartPointer::New(); + transform->SetSourceLandmarks(sourcePoints); + transform->SetTargetLandmarks(targetPoints); + transform->SetModeToRigidBody(); + transform->Modified(); + transform->Update(); + //compute FRE of transform + double FRE = mitk::StaticIGTHelperFunctions::ComputeFRE(reference, measurement, transform); + MITK_INFO << "FRE after grid matching: " + QString::number(FRE) + " mm"; + //convert from vtk to itk data types + itk::Matrix rotationFloat = itk::Matrix(); + itk::Vector translationFloat = itk::Vector(); + itk::Matrix rotationDouble = itk::Matrix(); + itk::Vector translationDouble = itk::Vector(); + + vtkSmartPointer m = transform->GetMatrix(); + for (int k = 0; k<3; k++) for (int l = 0; l<3; l++) + { + rotationFloat[k][l] = m->GetElement(k, l); + rotationDouble[k][l] = m->GetElement(k, l); + + } + for (int k = 0; k<3; k++) + { + translationFloat[k] = m->GetElement(k, 3); + translationDouble[k] = m->GetElement(k, 3); + } + //create affine transform 3D + mitk::AffineTransform3D::Pointer mitkTransform = mitk::AffineTransform3D::New(); + mitkTransform->SetMatrix(rotationDouble); + mitkTransform->SetOffset(translationDouble); + mitk::NavigationData::Pointer transformNavigationData = mitk::NavigationData::New(mitkTransform); + m_Controls->m_ReferencePointSetComboBox->GetSelectedNode()->GetData()->GetGeometry()->SetIndexToWorldTransform(mitkTransform); + m_Controls->m_ReferencePointSetComboBox->GetSelectedNode()->GetData()->GetGeometry()->Modified(); + + //write to file + + std::stringstream filename; + filename << std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str() << ".GridMatchingResult.csv"; + MITK_INFO << "Writing output to file " << filename.str(); + std::vector FRE_Error; + FRE_Error.push_back({ FRE, "FRE after grid matching [mm]" }); + writeToFile(filename.str(), FRE_Error); +} + +void QmitkIGTTrackingDataEvaluationView::OnOrientationCalculation_CalcRef() +{ + if (m_FilenameVector.size() != 3) + { + MessageBox("Need exactly three points as reference, aborting!"); + return; + } + + //start loop and iterate through all files of list + for (int i = 0; i < m_FilenameVector.size(); i++) + { + //create navigation data player + mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = ConstructNewNavigationDataPlayer(); + myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); + myPlayer->SetFileName(m_FilenameVector.at(i)); + + //check if the stream is valid and skip file if not + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + continue; + } + */ + + //create evaluation filter + mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); + + //connect pipeline + for (int j = 0; j < myPlayer->GetNumberOfOutputs(); j++) { myEvaluationFilter->SetInput(j, myPlayer->GetOutput(j)); } + + //update pipline until number of samlples is reached + for (int j = 0; j < m_Controls->m_NumberOfSamples->value(); j++) + { + myEvaluationFilter->Update(); + } + + //store mean position as reference + switch (i) + { + case 0: + m_RefPoint1 = myEvaluationFilter->GetPositionMean(0); + break; + case 1: + m_RefPoint2 = myEvaluationFilter->GetPositionMean(0); + break; + case 2: + m_RefPoint3 = myEvaluationFilter->GetPositionMean(0); + break; + } + } + MessageBox("Created Reference!"); +} + +void QmitkIGTTrackingDataEvaluationView::OnOrientationCalculation_CalcOrientandWriteToFile() +{ + //start loop and iterate through all files of list + for (int i = 0; i < m_FilenameVector.size(); i++) + { + //create navigation data player + mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = ConstructNewNavigationDataPlayer(); + myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); + myPlayer->SetFileName(m_FilenameVector.at(i)); + + //check if the stream is valid and skip file if not + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + continue; + } + */ + + //open file header + QString outputname = QString(m_FilenameVector.at(i).c_str()) + "_orientationFile.csv"; + m_CurrentWriteFile.open(outputname.toStdString().c_str(), std::ios::out); + if (m_CurrentWriteFile.bad()) + { + MessageBox("Error: Can't open output file!"); + return; + } + + //write header to file + m_CurrentWriteFile << "Nr;Calypso_Time;Valid_Reference;MeasureTool_Measurement-Tool[x];MeasureTool_Measurement-Tool[y];MeasureTool_Measurement-Tool[z];MeasureTool_Measurement-Tool[qx];MeasureTool_Measurement-Tool[qy];MeasureTool_Measurement-Tool[qz];MeasureTool_Measurement-Tool[qr]\n"; + + //update pipeline until number of samples is reached + int step = 0; + mitk::Point3D point1, point2, point3; + mitk::Quaternion current_orientation; + + for (int j = 0; !myPlayer->IsAtEnd(); j++) + { + myPlayer->Update(); + mitk::NavigationData::Pointer currentNavData = myPlayer->GetOutput(0); + switch (step) + { + case 0: + step++; + point1 = currentNavData->GetPosition(); + break; + case 1: + step++; + point2 = currentNavData->GetPosition(); + break; + case 2: + step = 0; + point3 = currentNavData->GetPosition(); + + //compute transform from reference to current points + if (point1[0] == 0 && + point1[1] == 0 && + point1[2] == 0 && + point2[0] == 0 && + point2[1] == 0 && + point2[2] == 0 && + point3[0] == 0 && + point3[1] == 0 && + point3[2] == 0 + ) current_orientation.fill(0); + else + { + /* Drehen um eine Achse um das "Umschlagen" zu vermeiden + itk::Matrix rot180degreeAroundY; + rot180degreeAroundY.Fill(0); + rot180degreeAroundY[0][0] = -1; + rot180degreeAroundY[1][1] = 1; + rot180degreeAroundY[2][2] = -1; + point1 = rot180degreeAroundY * point1; + point2 = rot180degreeAroundY * point2; + point3 = rot180degreeAroundY * point3; + */ + + vtkSmartPointer transform = vtkSmartPointer::New(); + vtkSmartPointer sourcePoints = vtkSmartPointer::New(); + double sourcepoint1[3] = { point1[0], point1[1], point1[2] }; + double sourcepoint2[3] = { point2[0], point2[1], point2[2] }; + double sourcepoint3[3] = { point3[0], point3[1], point3[2] }; + sourcePoints->InsertNextPoint(sourcepoint1); + sourcePoints->InsertNextPoint(sourcepoint2); + sourcePoints->InsertNextPoint(sourcepoint3); + vtkSmartPointer targetPoints = vtkSmartPointer::New(); + double targetpoint1[3] = { m_RefPoint1[0], m_RefPoint1[1], m_RefPoint1[2] }; + double targetpoint2[3] = { m_RefPoint2[0], m_RefPoint2[1], m_RefPoint2[2] }; + double targetpoint3[3] = { m_RefPoint3[0], m_RefPoint3[1], m_RefPoint3[2] }; + targetPoints->InsertNextPoint(targetpoint1); + targetPoints->InsertNextPoint(targetpoint2); + targetPoints->InsertNextPoint(targetpoint3); + + transform->SetSourceLandmarks(sourcePoints); + transform->SetTargetLandmarks(targetPoints); + transform->Modified(); + transform->Update(); + + mitk::Transform::Pointer newTransform = mitk::Transform::New(); + newTransform->SetMatrix(transform->GetMatrix()); + current_orientation = newTransform->GetOrientation(); + + //add pointset with the three positions + if ((j > 15) && (j < 18)) + { + mitk::DataNode::Pointer newNode = mitk::DataNode::New(); + mitk::PointSet::Pointer newPointSet = mitk::PointSet::New(); + newPointSet->InsertPoint(0, point1); + newPointSet->InsertPoint(1, point2); + newPointSet->InsertPoint(2, point3); + QString name = QString(m_FilenameVector.at(i).c_str()); + newNode->SetName(name.toStdString().c_str()); + newNode->SetData(newPointSet); + newNode->SetFloatProperty("pointsize", 0.1); + this->GetDataStorage()->Add(newNode); + } + } + + break; + } + m_CurrentWriteFile << i << ";"; + m_CurrentWriteFile << currentNavData->GetTimeStamp() << ";"; //IMPORTANT: change to GetIGTTimeStamp in new version! + m_CurrentWriteFile << "true;"; + m_CurrentWriteFile << currentNavData->GetPosition()[0] << ";"; + m_CurrentWriteFile << currentNavData->GetPosition()[1] << ";"; + m_CurrentWriteFile << currentNavData->GetPosition()[2] << ";"; + m_CurrentWriteFile << current_orientation.x() << ";"; + m_CurrentWriteFile << current_orientation.y() << ";"; + m_CurrentWriteFile << current_orientation.z() << ";"; + m_CurrentWriteFile << current_orientation.r() << ";"; + m_CurrentWriteFile << "\n"; + } + //close output file + m_CurrentWriteFile.close(); + } + MessageBox("Finished!"); +} + +void QmitkIGTTrackingDataEvaluationView::StdMultiWidgetAvailable(QmitkStdMultiWidget &stdMultiWidget) +{ + m_MultiWidget = &stdMultiWidget; +} + +void QmitkIGTTrackingDataEvaluationView::StdMultiWidgetNotAvailable() +{ + m_MultiWidget = NULL; +} + +void QmitkIGTTrackingDataEvaluationView::OnAddToCurrentList() +{ + //read in files + QStringList files = QFileDialog::getOpenFileNames(NULL, "Select one or more files to open", "/", "CSV (*.csv)"); + if (files.isEmpty()) return; + + for (int i = 0; i < files.size(); i++){ + std::string tmp = files.at(i).toStdString().c_str(); + m_FilenameVector.push_back(tmp); + } + /* + //save old locale + char * oldLocale; + oldLocale = setlocale( LC_ALL, 0 ); + + //define own locale + std::locale C("C"); + setlocale( LC_ALL, "C" ); + */ //TODO: check if this is needed here, and load old locale if yes + + /* + //read file + std::ifstream file; + file.open(filename.toStdString().c_str(), std::ios::in); + if (file.good()) + { + //read out file + file.seekg(0L, std::ios::beg); // move to begin of file + while (!file.eof()) + { + std::string buffer; + std::getline(file, buffer); // read out file line by line + if (buffer.size() > 0) + { + std::string thisFilename = ""; + if (m_Controls->m_AddPath->isChecked()) thisFilename = m_Controls->m_ListPath->text().toStdString(); + thisFilename.append(buffer); + m_FilenameVector.push_back(thisFilename); + } + } + } + */ + //fill list at GUI + m_Controls->m_FileList->clear(); + for (unsigned int i = 0; i < m_FilenameVector.size(); i++) { new QListWidgetItem(tr(m_FilenameVector.at(i).c_str()), m_Controls->m_FileList); } +} + +void QmitkIGTTrackingDataEvaluationView::OnLoadFileList() +{ + m_FilenameVector = std::vector(); + m_FilenameVector.clear(); + OnAddToCurrentList(); +} + +void QmitkIGTTrackingDataEvaluationView::OnEvaluateDataAll() +{ + std::vector results5cm, results15cm, results30cm, resultsAccum; + mitk::HummelProtocolEvaluation::HummelProtocolMeasurementVolume volume; + if (m_Controls->m_standardVolume->isChecked()) + { + volume = mitk::HummelProtocolEvaluation::standard; + mitk::HummelProtocolEvaluation::Evaluate5cmDistances(m_PointSetMeanPositions, volume, results5cm); + mitk::HummelProtocolEvaluation::Evaluate15cmDistances(m_PointSetMeanPositions, volume, results15cm); + mitk::HummelProtocolEvaluation::Evaluate30cmDistances(m_PointSetMeanPositions, volume, results30cm); + mitk::HummelProtocolEvaluation::EvaluateAccumulatedDistances(m_PointSetMeanPositions, volume, resultsAccum); + } + else 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); + } + + + //write results to file + std::stringstream filename5cm; + filename5cm << std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str() << ".results5cm.csv"; + MITK_INFO << "Writing output to file " << filename5cm.str(); + writeToFile(filename5cm.str(), results5cm); + + std::stringstream filename15cm; + filename15cm << std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str() << ".results15cm.csv"; + MITK_INFO << "Writing output to file " << filename15cm.str(); + writeToFile(filename15cm.str(), results15cm); + + std::stringstream filename30cm; + filename30cm << std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str() << ".results30cm.csv"; + MITK_INFO << "Writing output to file " << filename30cm.str(); + writeToFile(filename30cm.str(), results30cm); + + std::stringstream filenameAccum; + filenameAccum << std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str() << ".resultsAccumDist.csv"; + MITK_INFO << "Writing output to file " << filenameAccum.str(); + writeToFile(filenameAccum.str(), resultsAccum); +} + +void QmitkIGTTrackingDataEvaluationView::OnEvaluateData() +{ + //open output file + m_CurrentWriteFile.open(std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str(), std::ios::out); + if (m_CurrentWriteFile.bad()) + { + MessageBox("Error: Can't open output file!"); + return; + } + + std::vector jitterValues; + + //write output file header + WriteHeader(); + + //start loop and iterate through all files of list + for (int i = 0; i < m_FilenameVector.size(); i++) + { + //create navigation data player + mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = ConstructNewNavigationDataPlayer(); + myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); + myPlayer->SetFileName(m_FilenameVector.at(i)); + + //check if the stream is valid and skip file if not + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + + continue; + } + */ + + //create evaluation filter + mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); + + //connect pipeline + for (int j = 0; j < myPlayer->GetNumberOfOutputs(); j++) { myEvaluationFilter->SetInput(j, myPlayer->GetOutput(j)); } + + if (myPlayer->GetNumberOfSnapshots() < m_Controls->m_NumberOfSamples->value()) + { + MITK_WARN << "Number of snapshots (" << myPlayer->GetNumberOfSnapshots() << ") smaller than number of samples to evaluate (" << m_Controls->m_NumberOfSamples->value() << ") ! Cannot proceed!"; + return; + } + + //update pipline until number of samples is reached + for (int j = 0; j < m_Controls->m_NumberOfSamples->value(); j++) + { + myEvaluationFilter->Update(); + //Debug output: + //std::cout.precision(5); + //std::cout << "Euler " << j << ";" << myPlayer->GetOutput()->GetOrientation().rotation_euler_angles()[0] << ";" << myPlayer->GetOutput()->GetOrientation().rotation_euler_angles()[1] << ";" << myPlayer->GetOutput()->GetOrientation().rotation_euler_angles()[2] << "\n"; + } + + //store all jitter values in separate vector for statistics + jitterValues.push_back({ myEvaluationFilter->GetPositionErrorRMS(0), "RMS" }); + + //write result to output file + WriteDataSet(myEvaluationFilter, m_FilenameVector.at(i)); + } + + //close output file for single data + m_CurrentWriteFile.close(); + + //compute statistics + std::vector jitterStatistics = mitk::HummelProtocolEvaluation::ComputeStatistics(jitterValues); + MITK_INFO << "## Jitter (RMS) statistics: ##"; + for (auto jitterStat : jitterStatistics) {MITK_INFO << jitterStat.description << ": " << jitterStat.distanceError;} + + //write statistic results to separate file + std::stringstream filenameJitterStat; + filenameJitterStat << std::string(m_Controls->m_OutputFilename->text().toUtf8()).c_str() << ".resultsJitterStatistics.csv"; + MITK_INFO << "Writing output to file " << filenameJitterStat.str(); + writeToFile(filenameJitterStat.str(), jitterStatistics); + + //calculate angles if option is on + if (m_Controls->m_settingDifferenceAngles->isChecked() || m_Controls->m_DifferencesSLERP->isChecked()) CalculateDifferenceAngles(); + + MessageBox("Finished!"); +} + +void QmitkIGTTrackingDataEvaluationView::OnGeneratePointSetsOfSinglePositions() +{ + m_scalingfactor = m_Controls->m_ScalingFactor->value(); + + //start loop and iterate through all files of list + for (int i = 0; i < m_FilenameVector.size(); i++) + { + //create point set for this file + mitk::PointSet::Pointer thisPointSet = mitk::PointSet::New(); + + //create navigation data player + mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = ConstructNewNavigationDataPlayer(); + myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); + myPlayer->SetFileName(m_FilenameVector.at(i)); + + //check if the stream is valid and skip file if not + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + + continue; + } + */ + + //update pipline until number of samlples is reached and store every single point + for (int j = 0; j < m_Controls->m_NumberOfSamples->value(); j++) + { + myPlayer->Update(); + mitk::Point3D thisPoint = myPlayer->GetOutput()->GetPosition(); + thisPoint[0] *= m_scalingfactor; + thisPoint[1] *= m_scalingfactor; + thisPoint[2] *= m_scalingfactor; + thisPointSet->InsertPoint(j, thisPoint); + } + + //add point set to data storage + mitk::DataNode::Pointer newNode = mitk::DataNode::New(); + QString name = this->m_Controls->m_prefix->text() + QString("PointSet_of_All_Positions_") + QString::number(i); + newNode->SetName(name.toStdString()); + newNode->SetData(thisPointSet); + this->GetDataStorage()->Add(newNode); + } +} + +void QmitkIGTTrackingDataEvaluationView::OnGeneratePointSet() +{ + m_scalingfactor = m_Controls->m_ScalingFactor->value(); + + mitk::PointSet::Pointer generatedPointSet = mitk::PointSet::New(); + + //start loop and iterate through all files of list + for (int i = 0; i < m_FilenameVector.size(); i++) + { + //create navigation data player + mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = ConstructNewNavigationDataPlayer(); + myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); + myPlayer->SetFileName(m_FilenameVector.at(i)); + + //check if the stream is valid and skip file if not + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + + continue; + } + */ + + //create evaluation filter + mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); + + //connect pipeline + for (int j = 0; j < myPlayer->GetNumberOfOutputs(); j++) { myEvaluationFilter->SetInput(j, myPlayer->GetOutput(j)); } + + //update pipline until number of samlples is reached + for (int j = 0; j < m_Controls->m_NumberOfSamples->value(); j++) { myEvaluationFilter->Update(); } + + //add mean position to point set + mitk::Point3D meanPos = myEvaluationFilter->GetPositionMean(0); + if (m_scalingfactor != 1) + { + meanPos[0] *= m_scalingfactor; + meanPos[1] *= m_scalingfactor; + meanPos[2] *= m_scalingfactor; + } + generatedPointSet->InsertPoint(i, meanPos); + } + + //add point set to data storage + mitk::DataNode::Pointer newNode = mitk::DataNode::New(); + QString name = this->m_Controls->m_prefix->text() + "PointSet_of_Mean_Positions"; + newNode->SetName(name.toStdString()); + newNode->SetData(generatedPointSet); + newNode->SetFloatProperty("pointsize", 5); + this->GetDataStorage()->Add(newNode); + m_PointSetMeanPositions = generatedPointSet; +} + +void QmitkIGTTrackingDataEvaluationView::OnGenerateRotationLines() +{ + m_scalingfactor = m_Controls->m_ScalingFactor->value(); + + //start loop and iterate through all files of list + for (int i = 0; i < m_FilenameVector.size(); i++) + { + //create navigation data player + mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = ConstructNewNavigationDataPlayer(); + myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); + myPlayer->SetFileName(m_FilenameVector.at(i)); + + //check if the stream is valid and skip file if not + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + } + else + */ + { + //create evaluation filter + mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); + + //connect pipeline + for (int j = 0; j < myPlayer->GetNumberOfOutputs(); j++) { myEvaluationFilter->SetInput(j, myPlayer->GetOutput(j)); } + + //update pipline until number of samlples is reached + for (int j = 0; j < m_Controls->m_NumberOfSamples->value(); j++) + { + myEvaluationFilter->Update(); + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + continue; + } + */ + } + //if (!myPlayer->IsAtEnd()) continue; + + //create line from mean pos to a second point which lies along the sensor (1,0,0 in tool coordinates for aurora) + mitk::Point3D meanPos = myEvaluationFilter->GetPositionMean(0); + if (m_scalingfactor != 1) + { + meanPos[0] *= m_scalingfactor; + meanPos[1] *= m_scalingfactor; + meanPos[2] *= m_scalingfactor; + } + mitk::Point3D secondPoint; + mitk::Point3D thirdPoint; + mitk::Point3D fourthPoint; + + mitk::FillVector3D(secondPoint, 2, 0, 0); //X + vnl_vector secondPointTransformed = myEvaluationFilter->GetQuaternionMean(0).rotation_matrix_transpose().transpose() * secondPoint.Get_vnl_vector() + meanPos.Get_vnl_vector(); + mitk::Point3D secondPointTransformedMITK; + mitk::FillVector3D(secondPointTransformedMITK, secondPointTransformed[0], secondPointTransformed[1], secondPointTransformed[2]); + + mitk::FillVector3D(thirdPoint, 0, 4, 0); //Y + vnl_vector thirdPointTransformed = myEvaluationFilter->GetQuaternionMean(0).rotation_matrix_transpose().transpose() * thirdPoint.Get_vnl_vector() + meanPos.Get_vnl_vector(); + mitk::Point3D thirdPointTransformedMITK; + mitk::FillVector3D(thirdPointTransformedMITK, thirdPointTransformed[0], thirdPointTransformed[1], thirdPointTransformed[2]); + + mitk::FillVector3D(fourthPoint, 0, 0, 6); //Z + vnl_vector fourthPointTransformed = myEvaluationFilter->GetQuaternionMean(0).rotation_matrix_transpose().transpose() * fourthPoint.Get_vnl_vector() + meanPos.Get_vnl_vector(); + mitk::Point3D fourthPointTransformedMITK; + mitk::FillVector3D(fourthPointTransformedMITK, fourthPointTransformed[0], fourthPointTransformed[1], fourthPointTransformed[2]); + + mitk::PointSet::Pointer rotationLine = mitk::PointSet::New(); + rotationLine->InsertPoint(0, secondPointTransformedMITK); + rotationLine->InsertPoint(1, meanPos); + rotationLine->InsertPoint(2, thirdPointTransformedMITK); + rotationLine->InsertPoint(3, meanPos); + rotationLine->InsertPoint(4, fourthPointTransformedMITK); + + mitk::DataNode::Pointer newNode = mitk::DataNode::New(); + QString nodeName = this->m_Controls->m_prefix->text() + "RotationLineNumber" + QString::number(i); + newNode->SetName(nodeName.toStdString()); + newNode->SetData(rotationLine); + newNode->SetBoolProperty("show contour", true); + newNode->SetFloatProperty("pointsize", 0.5); + this->GetDataStorage()->Add(newNode); + } + } +} + +void QmitkIGTTrackingDataEvaluationView::OnGenerateGroundTruthPointSet() +{ + mitk::PointSet::Pointer generatedPointSet = mitk::PointSet::New(); + int currentPointID = 0; + mitk::Point3D currentPoint; + mitk::FillVector3D(currentPoint, 0, 0, 0); + for (int i = 0; i < m_Controls->m_PointNumber2->value(); i++) + { + for (int j = 0; j < m_Controls->m_PointNumber1->value(); j++) + { + generatedPointSet->InsertPoint(currentPointID, currentPoint); + currentPointID++; + currentPoint[1] += m_Controls->m_PointDistance->value(); + } + currentPoint[1] = 0; + currentPoint[2] += m_Controls->m_PointDistance->value(); + } + mitk::DataNode::Pointer newNode = mitk::DataNode::New(); + QString nodeName = "GroundTruthPointSet_" + QString::number(m_Controls->m_PointNumber1->value()) + "x" + QString::number(m_Controls->m_PointNumber2->value()) + "_(" + QString::number(m_Controls->m_PointDistance->value()) + "mm)"; + newNode->SetName(nodeName.toStdString()); + newNode->SetData(generatedPointSet); + newNode->SetFloatProperty("pointsize", 5); + this->GetDataStorage()->Add(newNode); +} + +void QmitkIGTTrackingDataEvaluationView::OnConvertCSVtoXMLFile() +{ + if (m_Controls->m_ConvertSingleFile->isChecked()) + { //convert one file + int lines = ConvertOneFile(this->m_Controls->m_InputCSV->text().toStdString(), this->m_Controls->m_OutputXML->text().toStdString()); + + QString result = "Converted one file with" + QString::number(lines) + " data sets"; + MessageBox(result.toStdString()); + } + else //converte file list + { + if (m_CSVtoXMLInputFilenameVector.empty() || m_CSVtoXMLOutputFilenameVector.empty()) + { + MessageBox("Error: one list is not loaded!"); + return; + } + else if (m_CSVtoXMLInputFilenameVector.size() != m_CSVtoXMLOutputFilenameVector.size()) + { + MessageBox("Error: lists do not have the same number of files!"); + return; + } + for (int i = 0; i < m_CSVtoXMLInputFilenameVector.size(); i++) + { + int lines = ConvertOneFile(m_CSVtoXMLInputFilenameVector.at(i), m_CSVtoXMLOutputFilenameVector.at(i)); + } + QString result = "Converted " + QString::number(m_CSVtoXMLInputFilenameVector.size()) + " files from file list!"; + MessageBox(result.toStdString()); + } +} + +int QmitkIGTTrackingDataEvaluationView::ConvertOneFile(std::string inputFilename, std::string outputFilename) +{ + std::vector myNavigationDatas = GetNavigationDatasFromFile(inputFilename); + mitk::NavigationDataRecorderDeprecated::Pointer myRecorder = mitk::NavigationDataRecorderDeprecated::New(); + myRecorder->SetFileName(outputFilename.c_str()); + mitk::NavigationData::Pointer input = mitk::NavigationData::New(); + if (m_Controls->m_ConvertCSV->isChecked()) myRecorder->SetOutputFormat(mitk::NavigationDataRecorderDeprecated::csv); + myRecorder->AddNavigationData(input); + myRecorder->StartRecording(); + for (int i = 0; i < myNavigationDatas.size(); i++) + { + input->Graft(myNavigationDatas.at(i)); + myRecorder->Update(); + } + myRecorder->StopRecording(); + return myNavigationDatas.size(); +} + +void QmitkIGTTrackingDataEvaluationView::OnCSVtoXMLLoadInputList() +{ + //read in filename + QString filename = QFileDialog::getOpenFileName(NULL, tr("Open Measurement Filename List"), "/", tr("All Files (*.*)")); + if (filename.isNull()) return; + + m_CSVtoXMLInputFilenameVector = this->GetFileContentLineByLine(filename.toStdString()); + + m_Controls->m_labelCSVtoXMLInputList->setText("READY"); +} + +void QmitkIGTTrackingDataEvaluationView::OnCSVtoXMLLoadOutputList() +{ + //read in filename + QString filename = QFileDialog::getOpenFileName(NULL, tr("Open Measurement Filename List"), "/", tr("All Files (*.*)")); + if (filename.isNull()) return; + + m_CSVtoXMLOutputFilenameVector = this->GetFileContentLineByLine(filename.toStdString()); + + m_Controls->m_labelCSVtoXMLOutputList->setText("READY"); +} + +void QmitkIGTTrackingDataEvaluationView::MessageBox(std::string s) +{ + QMessageBox msgBox; + msgBox.setText(s.c_str()); + msgBox.exec(); +} + +void QmitkIGTTrackingDataEvaluationView::WriteHeader() +{ + m_CurrentWriteFile << "Filename;"; + m_CurrentWriteFile << "N;"; + m_CurrentWriteFile << "N_invalid;"; + m_CurrentWriteFile << "Percentage_invalid;"; + + if (m_Controls->m_settingPosMean->isChecked()) + { + m_CurrentWriteFile << "Position_Mean[x];"; + m_CurrentWriteFile << "Position_Mean[y];"; + m_CurrentWriteFile << "Position_Mean[z];"; + } + + if (m_Controls->m_settingPosStabw->isChecked()) + { + m_CurrentWriteFile << "Position_StandDev[x];"; + m_CurrentWriteFile << "Position_StandDev[y];"; + m_CurrentWriteFile << "Position_StandDev[z];"; + } + + if (m_Controls->m_settingPosSampleStabw->isChecked()) + { + m_CurrentWriteFile << "Position_SampleStandDev[x];"; + m_CurrentWriteFile << "Position_SampleStandDev[y];"; + m_CurrentWriteFile << "Position_SampleStandDev[z];"; + } + + if (m_Controls->m_settingQuaternionMean->isChecked()) + { + m_CurrentWriteFile << "Quaternion_Mean[qx];"; + m_CurrentWriteFile << "Quaternion_Mean[qy];"; + m_CurrentWriteFile << "Quaternion_Mean[qz];"; + m_CurrentWriteFile << "Quaternion_Mean[qr];"; + } + + if (m_Controls->m_settionQuaternionStabw->isChecked()) + { + m_CurrentWriteFile << "Quaternion_StandDev[qx];"; + m_CurrentWriteFile << "Quaternion_StandDev[qy];"; + m_CurrentWriteFile << "Quaternion_StandDev[qz];"; + m_CurrentWriteFile << "Quaternion_StandDev[qr];"; + } + + if (m_Controls->m_settingPosErrorMean->isChecked()) m_CurrentWriteFile << "PositionError_Mean;"; + + if (m_Controls->m_settingPosErrorStabw->isChecked()) m_CurrentWriteFile << "PositionError_StandDev;"; + + if (m_Controls->m_settingPosErrorSampleStabw->isChecked()) m_CurrentWriteFile << "PositionError_SampleStandDev;"; + + if (m_Controls->m_settingPosErrorRMS->isChecked()) m_CurrentWriteFile << "PositionError_RMS;"; + + if (m_Controls->m_settingPosErrorMedian->isChecked()) m_CurrentWriteFile << "PositionError_Median;"; + + if (m_Controls->m_settingPosErrorMinMax->isChecked()) + { + m_CurrentWriteFile << "PositionError_Max;"; + m_CurrentWriteFile << "PositionError_Min;"; + } + + if (m_Controls->m_settingEulerMean->isChecked()) + { + m_CurrentWriteFile << "Euler_tx;"; + m_CurrentWriteFile << "Euler_ty;"; + m_CurrentWriteFile << "Euler_tz;"; + } + + if (m_Controls->m_settingEulerRMS->isChecked()) + { + m_CurrentWriteFile << "EulerErrorRMS (rad);"; + m_CurrentWriteFile << "EulerErrorRMS (grad);"; + } + + m_CurrentWriteFile << "\n"; +} + +void QmitkIGTTrackingDataEvaluationView::WriteDataSet(mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter, std::string dataSetName) +{ + if (myEvaluationFilter->GetNumberOfOutputs() == 0) m_CurrentWriteFile << "Error: no input \n"; + else + { + m_CurrentWriteFile << dataSetName << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetNumberOfAnalysedNavigationData(0) << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetNumberOfInvalidSamples(0) << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPercentageOfInvalidSamples(0) << ";"; + + if (m_Controls->m_settingPosMean->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetPositionMean(0)[0] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPositionMean(0)[1] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPositionMean(0)[2] << ";"; + } + + if (m_Controls->m_settingPosStabw->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetPositionStandardDeviation(0)[0] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPositionStandardDeviation(0)[1] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPositionStandardDeviation(0)[2] << ";"; + } + + if (m_Controls->m_settingPosSampleStabw->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetPositionSampleStandardDeviation(0)[0] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPositionSampleStandardDeviation(0)[1] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPositionSampleStandardDeviation(0)[2] << ";"; + } + + if (m_Controls->m_settingQuaternionMean->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionMean(0).x() << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionMean(0).y() << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionMean(0).z() << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionMean(0).r() << ";"; + } + + if (m_Controls->m_settionQuaternionStabw->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionStandardDeviation(0).x() << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionStandardDeviation(0).y() << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionStandardDeviation(0).z() << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetQuaternionStandardDeviation(0).r() << ";"; + } + + if (m_Controls->m_settingPosErrorMean->isChecked()) m_CurrentWriteFile << myEvaluationFilter->GetPositionErrorMean(0) << ";"; + if (m_Controls->m_settingPosErrorStabw->isChecked()) m_CurrentWriteFile << myEvaluationFilter->GetPositionErrorStandardDeviation(0) << ";"; + if (m_Controls->m_settingPosErrorSampleStabw->isChecked()) m_CurrentWriteFile << myEvaluationFilter->GetPositionErrorSampleStandardDeviation(0) << ";"; + if (m_Controls->m_settingPosErrorRMS->isChecked()) m_CurrentWriteFile << myEvaluationFilter->GetPositionErrorRMS(0) << ";"; + if (m_Controls->m_settingPosErrorMedian->isChecked()) m_CurrentWriteFile << myEvaluationFilter->GetPositionErrorMedian(0) << ";"; + if (m_Controls->m_settingPosErrorMinMax->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetPositionErrorMax(0) << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetPositionErrorMin(0) << ";"; + } + + if (m_Controls->m_settingEulerMean->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetEulerAnglesMean(0)[0] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetEulerAnglesMean(0)[1] << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetEulerAnglesMean(0)[2] << ";"; + } + + if (m_Controls->m_settingEulerRMS->isChecked()) + { + m_CurrentWriteFile << myEvaluationFilter->GetEulerAnglesRMS(0) << ";"; + m_CurrentWriteFile << myEvaluationFilter->GetEulerAnglesRMSDegree(0) << ";"; + } + + m_CurrentWriteFile << "\n"; + } +} + + +std::vector QmitkIGTTrackingDataEvaluationView::GetMeanOrientationsOfAllData(std::vector allData, bool useSLERP) +{ + std::vector returnValue; + + for (auto dataSet : allData) + { + if (useSLERP) returnValue.push_back(GetSLERPAverage(dataSet)); + else returnValue.push_back(dataSet->GetQuaternionMean(0)); + } + + return returnValue; +} + + +std::vector QmitkIGTTrackingDataEvaluationView::GetAllDataFromUIList() +{ + std::vector EvaluationDataCollection; + + //start loop and iterate through all files of list: store the evaluation data + for (int i = 0; i < m_FilenameVector.size(); i++) + { + //create navigation data player + mitk::NavigationDataCSVSequentialPlayer::Pointer myPlayer = ConstructNewNavigationDataPlayer(); + myPlayer->SetFiletype(mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV); + myPlayer->SetFileName(m_FilenameVector.at(i)); + + //create evaluation filter + mitk::NavigationDataEvaluationFilter::Pointer myEvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); + + //check if the stream is valid and skip file if not + /* + if (!myPlayer->GetStreamValid()) + { + MITK_ERROR << "Error in file " << m_FilenameVector.at(i) << ": " << myPlayer->GetErrorMessage() << " ; Skipping file!"; + } + else + */ + { + //connect pipeline + for (int j = 0; j < myPlayer->GetNumberOfOutputs(); j++) { myEvaluationFilter->SetInput(j, myPlayer->GetOutput(j)); } + //update pipline until number of samlples is reached + for (int j = 0; j < m_Controls->m_NumberOfSamples->value(); j++) { myEvaluationFilter->Update(); } + } + + myEvaluationFilter->SetInput(NULL); + myPlayer = NULL; + EvaluationDataCollection.push_back(myEvaluationFilter); + } + + return EvaluationDataCollection; +} + +void QmitkIGTTrackingDataEvaluationView::CalculateDifferenceAngles() +{ + //Get all data from UI + std::vector EvaluationDataCollection = GetAllDataFromUIList(); + + //calculation and writing of output data + //open output file + m_CurrentAngleDifferencesWriteFile.open(std::string((m_Controls->m_OutputFilename->text() + ".angledifferences.csv").toUtf8()).c_str(), std::ios::out); + if (m_CurrentAngleDifferencesWriteFile.bad()) + { + MessageBox("Error: Can't open output file for angle differences calculation!"); + return; + } + //write header + WriteDifferenceAnglesHeader(); + //compute angle differences + QString pos1 = "invalid"; + QString pos2 = "invalid"; + //now iterate through all evaluation data and calculate the angles + for (int i = 0; i < m_FilenameVector.size(); i++) + { + pos1 = QString::fromStdString(itksys::SystemTools::GetFilenameWithoutLastExtension(m_FilenameVector.at(i))); + for (int j = 0; j < m_FilenameVector.size(); j++) + { + pos2 = QString::fromStdString(itksys::SystemTools::GetFilenameWithoutLastExtension(m_FilenameVector.at(j))); + + mitk::Quaternion q1; + mitk::Quaternion q2; + + if (m_Controls->m_DifferencesSLERP->isChecked()) + { + //compute slerp average + q1 = GetSLERPAverage(EvaluationDataCollection.at(i)); + q2 = GetSLERPAverage(EvaluationDataCollection.at(j)); + } + else + { + //compute arithmetic average + q1 = EvaluationDataCollection.at(i)->GetQuaternionMean(0); + q2 = EvaluationDataCollection.at(j)->GetQuaternionMean(0); + } + + itk::Vector rotationVec; + //adapt for Aurora 5D tools: [0,0,1000] + rotationVec[0] = 10000; //X + rotationVec[1] = 0; //Y + rotationVec[2] = 0; //Z + double AngleBetweenTwoQuaternions = mitk::StaticIGTHelperFunctions::GetAngleBetweenTwoQuaterions(q1, q2, rotationVec); + + //write data set + WriteDifferenceAnglesDataSet(pos1.toStdString(), pos2.toStdString(), i, j, AngleBetweenTwoQuaternions); + } + } + + //close output file + m_CurrentAngleDifferencesWriteFile.close(); +} + +void QmitkIGTTrackingDataEvaluationView::WriteDifferenceAnglesHeader() +{ + m_CurrentAngleDifferencesWriteFile << "Name;Idx1;Idx2;Angle [Degree]\n"; +} + +void QmitkIGTTrackingDataEvaluationView::WriteDifferenceAnglesDataSet(std::string pos1, std::string pos2, int idx1, int idx2, double angle) +{ + //double PI = 3.1415926535897932384626433832795; + //double angle_degree = angle * 180 / PI; + m_CurrentAngleDifferencesWriteFile << "Angle between " << pos1 << " and " << pos2 << ";" << idx1 << ";" << idx2 << ";" << angle << "\n";//<< ";" << angle_degree << "\n"; + MITK_INFO << "Angle: " << angle; +} + +std::vector QmitkIGTTrackingDataEvaluationView::GetNavigationDatasFromFile(std::string filename) +{ + std::vector returnValue = std::vector(); + std::vector fileContentLineByLine = GetFileContentLineByLine(filename); + for (int i = 1; i < fileContentLineByLine.size(); i++) //skip header so start at 1 + { + returnValue.push_back(GetNavigationDataOutOfOneLine(fileContentLineByLine.at(i))); + } + + return returnValue; +} + +std::vector QmitkIGTTrackingDataEvaluationView::GetFileContentLineByLine(std::string filename) +{ + std::vector readData = std::vector(); + + //save old locale + char * oldLocale; + oldLocale = setlocale(LC_ALL, 0); + + //define own locale + std::locale C("C"); + setlocale(LC_ALL, "C"); + + //read file + std::ifstream file; + file.open(filename.c_str(), std::ios::in); + if (file.good()) + { + //read out file + file.seekg(0L, std::ios::beg); // move to begin of file + while (!file.eof()) + { + std::string buffer; + std::getline(file, buffer); // read out file line by line + if (buffer.size() > 0) readData.push_back(buffer); + } + } + + file.close(); + + //switch back to old locale + setlocale(LC_ALL, oldLocale); + + return readData; +} + +mitk::NavigationData::Pointer QmitkIGTTrackingDataEvaluationView::GetNavigationDataOutOfOneLine(std::string line) +{ + mitk::NavigationData::Pointer returnValue = mitk::NavigationData::New(); + + QString myLine = QString(line.c_str()); + + QStringList myLineList = myLine.split(';'); + + mitk::Point3D position; + mitk::Quaternion orientation; + + double time = myLineList.at(1).toDouble(); + + bool valid = false; + if (myLineList.at(2).toStdString() == "1") valid = true; + + position[0] = myLineList.at(3).toDouble(); + position[1] = myLineList.at(4).toDouble(); + position[2] = myLineList.at(5).toDouble(); + + orientation[0] = myLineList.at(6).toDouble(); + orientation[1] = myLineList.at(7).toDouble(); + orientation[2] = myLineList.at(8).toDouble(); + orientation[3] = myLineList.at(9).toDouble(); + + //returnValue->SetTimeStamp(time); + returnValue->SetDataValid(valid); + returnValue->SetPosition(position); + returnValue->SetOrientation(orientation); + + return returnValue; +} + +mitk::Quaternion QmitkIGTTrackingDataEvaluationView::GetSLERPAverage(mitk::NavigationDataEvaluationFilter::Pointer evaluationFilter) +{ + mitk::Quaternion average; + + //build a vector of quaternions from the evaulation filter (caution always takes the first (0) input of the filter + std::vector quaternions = std::vector(); + for (int i = 0; i < evaluationFilter->GetNumberOfAnalysedNavigationData(0); i++) + { + mitk::Quaternion currentq = evaluationFilter->GetLoggedOrientation(i, 0); + + quaternions.push_back(currentq); + } + + //compute the slerp average using the quaternion averaging class + mitk::QuaternionAveraging::Pointer myAverager = mitk::QuaternionAveraging::New(); + average = myAverager->CalcAverage(quaternions); + + return average; +} + +void QmitkIGTTrackingDataEvaluationView::writeToFile(std::string filename, std::vector values) +{ + std::fstream currentFile; + currentFile.open(filename.c_str(), std::ios::out); + if (currentFile.bad()) { MITK_WARN << "Cannot open file, aborting!"; return; } + currentFile << "Description" << ";" << "Error[mm]" << "\n"; + for (auto currentError : values) + { + currentFile << currentError.description << ";" << currentError.distanceError << "\n"; + } + currentFile.close(); +} + +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/QmitkIGTTrackingDataEvaluationView.h b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationView.h new file mode 100644 index 0000000000..28271da295 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationView.h @@ -0,0 +1,142 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef QmitkIGTTrackingDataEvaluationView_h +#define QmitkIGTTrackingDataEvaluationView_h + +#include + +#include + +#include "ui_QmitkIGTTrackingDataEvaluationViewControls.h" +#include "mitkHummelProtocolEvaluation.h" + +#include +#include "mitkNavigationDataCSVSequentialPlayer.h" + + + +/*! + \brief QmitkIGTTrackingDataEvaluationView + + \warning This application module is not yet documented. Use "svn blame/praise/annotate" and ask the author to provide basic documentation. + + \sa QmitkFunctionality + \ingroup Functionalities +*/ +class QmitkIGTTrackingDataEvaluationView : public QmitkFunctionality +{ + // this is needed for all Qt objects that should have a Qt meta-object + // (everything that derives from QObject and wants to have signal/slots) + Q_OBJECT + + public: + + static const std::string VIEW_ID; + + QmitkIGTTrackingDataEvaluationView(); + virtual ~QmitkIGTTrackingDataEvaluationView(); + + virtual void CreateQtPartControl(QWidget *parent); + + virtual void StdMultiWidgetAvailable (QmitkStdMultiWidget &stdMultiWidget); + virtual void StdMultiWidgetNotAvailable(); + + protected slots: + + void OnLoadFileList(); + void OnAddToCurrentList(); + void OnEvaluateData(); + void OnEvaluateDataAll(); + void OnGeneratePointSet(); + void OnGeneratePointSetsOfSinglePositions(); + void OnGenerateRotationLines(); + void OnGenerateGroundTruthPointSet(); + void OnConvertCSVtoXMLFile(); + void OnCSVtoXMLLoadInputList(); + void OnCSVtoXMLLoadOutputList(); + void OnPerfomGridMatching(); + void OnComputeRotation(); + + /** Reads in exactly three position files als reference. */ + void OnOrientationCalculation_CalcRef(); + /** Uses always three positions (1,2,3: first orientation; 4,5,6: second orientation; and so on) in every file to calcualte a orientation. */ + void OnOrientationCalculation_CalcOrientandWriteToFile(); + + + protected: + + Ui::QmitkIGTTrackingDataEvaluationViewControls* m_Controls; + + QmitkStdMultiWidget* m_MultiWidget; + + std::vector m_FilenameVector; + + void MessageBox(std::string s); + + std::fstream m_CurrentWriteFile; + void WriteHeader(); + void WriteDataSet(mitk::NavigationDataEvaluationFilter::Pointer evaluationFilter, std::string dataSetName); + + //members for orientation calculation + mitk::Point3D m_RefPoint1; + mitk::Point3D m_RefPoint2; + mitk::Point3D m_RefPoint3; + + double m_scalingfactor; //scaling factor for visualization, 1 by default + + //angle diffrences: seperated file + std::fstream m_CurrentAngleDifferencesWriteFile; + void CalculateDifferenceAngles(); + void WriteDifferenceAnglesHeader(); + void WriteDifferenceAnglesDataSet(std::string pos1, std::string pos2, int idx1, int idx2, double angle); + + void writeToFile(std::string filename, std::vector values); + + //different help methods to read a csv logging file + std::vector GetNavigationDatasFromFile(std::string filename); + std::vector GetFileContentLineByLine(std::string filename); + mitk::NavigationData::Pointer GetNavigationDataOutOfOneLine(std::string line); + + //help method to sonstruct the NavigationDataCSVSequentialPlayer filled with all the options from the UI + mitk::NavigationDataCSVSequentialPlayer::Pointer ConstructNewNavigationDataPlayer(); + + //CSV to XML members + std::vector m_CSVtoXMLInputFilenameVector; + std::vector m_CSVtoXMLOutputFilenameVector; + + //returns the number of converted lines + int ConvertOneFile(std::string inputFilename, std::string outputFilename); + + /** @brief calculates the angle in the plane perpendicular to the rotation axis of the two quaterions. */ + double GetAngleBetweenTwoQuaterions(mitk::Quaternion a, mitk::Quaternion b); + + /** @brief calculates the slerp average of a set of quaternions which is stored in the navigation data evaluation filter */ + mitk::Quaternion GetSLERPAverage(mitk::NavigationDataEvaluationFilter::Pointer); + + /** @brief Stores the mean positions of all evaluated data */ + mitk::PointSet::Pointer m_PointSetMeanPositions; + + /** @return returns the mean orientation of all given data */ + std::vector GetMeanOrientationsOfAllData(std::vector allData, bool useSLERP = false); + + /** @return returns all data read from the data list as NavigationDataEvaluationFilters */ + std::vector GetAllDataFromUIList(); +}; + + + +#endif // _QMITKIGTTRACKINGDATAEVALUATIONVIEW_H_INCLUDED diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationViewControls.ui b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationViewControls.ui new file mode 100644 index 0000000000..2dd08f80f3 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingDataEvaluationViewControls.ui @@ -0,0 +1,1780 @@ + + + QmitkIGTTrackingDataEvaluationViewControls + + + + 0 + 0 + 448 + 955 + + + + + 0 + 0 + + + + QmitkTemplate + + + + + + 0 + + + + Evaluation + + + + + + Input File List (recorded NavigationData / *.csv): + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 120 + 0 + + + + Load New List + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 120 + 0 + + + + Add To Current List + + + + + + + + + Qt::Horizontal + + + + + + + (1) - VISUALIZATION - of all data sets: + + + + + + + + + Prefix for Data Nodes: + + + + + + + + + + + + Generate PointSet of Mean Positions + + + + + + + Generate PointSets of Single Positions + + + + + + + Generate Lines for Rotation + + + + + + + Qt::Horizontal + + + + + + + (3) - JITTER - Evaluation per file / data set: + + + + + + + + + Result CSV Filename: + + + + + + + D:/tmp/output.csv + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 220 + 0 + + + + + 200 + 50 + + + + COMPUTE RESULTS PER DATA SET + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + (3) - ACCURACY - Evaluation of all data sets: + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 220 + 0 + + + + + 200 + 50 + + + + COMPUTE RESULTS OF ALL DATA + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + (4) - GRID MATCHING - Evaluation of all data sets: + + + + + + + + + Reference PointSet: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 150 + 0 + + + + + + + + + + + + Measurement PointSet: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 150 + 0 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 220 + 0 + + + + + 200 + 50 + + + + PERFOM GRID MATCHING + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + (5) - ROTATION - Evaluation of all data sets: + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 220 + 0 + + + + + 200 + 50 + + + + COMPUTE ROTATION ERRORS + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 220 + + + + + + + + + Settings + + + + + + + + 0 + 0 + 409 + 768 + + + + General + + + + + + + + + + + + + + Scaling Factor for Visualization: + + + + + + + Qt::Horizontal + + + + 38 + 20 + + + + + + + + 1.000000000000000 + + + + + + + + + + + Number of samples to analyze: + + + + + + + Qt::Horizontal + + + + 60 + 20 + + + + + + + + 1000000 + + + 150 + + + + + + + + + Qt::Horizontal + + + + + + + Tracking Volume: + + + + + + + Standard Volume (10 X 9 Positions) + + + true + + + + + + + Medium Volume (5 X 5 Positions) + + + + + + + Small Volume (3 X 4 Positions) + + + + + + + Qt::Horizontal + + + + + + + Rotation Evaluation: + + + + + + + + + Rotation Vector: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + X + + + + + + + 99999 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Y + + + + + + + 99999 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Z + + + + + + + 99999 + + + 10000 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + 0 + 286 + 746 + + + + .csv file input options + + + + + + File Options: + + + + + + + + Separator in the csv file: + + + + + + + Qt::Horizontal + + + + 60 + 20 + + + + + + + + + 0 + 0 + + + + + 40 + 16777215 + + + + ; + + + 1 + + + + + + + + + + + Use every n-th smaple n: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 1 + + + + + + + + + The csv file has a header row + + + true + + + + + + + + + + Type of Coordinate System: + + + + + + + + Left handed + + + + + + + Right handed + + + true + + + + + + + + + + + + Position and Orientation Options: + + + + + + + + Y + + + + + + + 4 + + + + + + + Z + + + + + + + 5 + + + + + + + X + + + + + + + 3 + + + + + + + Coordinate: + + + + + + + Colum number: + + + + + + + + + Use Quaternions for Orientation + + + true + + + + + + + + + Qr + + + + + + + 8 + + + + + + + Qy + + + + + + + 9 + + + + + + + 7 + + + + + + + 6 + + + + + + + Qx + + + + + + + Qz + + + + + + + Quaternion component: + + + + + + + Column number: + + + + + + + + + Use Euler Angles for Orientation + + + + + + + + + Column number: + + + + + + + Azimuth + + + + + + + -1 + + + + + + + Roll + + + + + + + Angle: + + + + + + + -1 + + + + + + + Elevation + + + + + + + -1 + + + + + + + + + + + + Unity for Euler Angles: + + + + + + + + + Radiants + + + true + + + + + + + Degrees + + + false + + + + + + + + + + + + + + + + + 0 + 0 + 343 + 607 + + + + Output per data set + + + + + + Position + + + + + + Mean (x,y,z) + + + true + + + + + + + Standard Deviation (x,y,z) + + + + + + + Sample Standard Deviation (x,y,z) + + + + + + + + + + Orientation + + + + + + Quaternion Mean (qx,qy,qz,qr) + + + + + + + Quaternion Mean (SLERP) + + + + + + + Quaternion Standard Deviation (qx,qy,qz,qr) + + + + + + + Euler Mean (tx,ty,tz) + + + + + + + Difference Angles to all other Positions + + + + + + + Difference Angles to all other Positions (SLERP) + + + + + + + + + + Position Error + + + + + + Mean + + + + + + + Standard Deviation + + + + + + + Sample Standard Deviation + + + + + + + RMS + + + true + + + + + + + Median + + + + + + + Min/Max + + + + + + + + + + Orientation Error + + + + + + Euler RMS + + + + + + + + + + + + + + + Tools + + + + + + Point Set Ground Truth Generator + + + + + + + + Generate + + + + + + + 1 + + + 999 + + + 10 + + + + + + + X + + + + + + + 1 + + + 999 + + + 9 + + + + + + + Point Set + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Inter Point Distance (in mm): + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 1 + + + 99999 + + + 50 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Generate + + + + + + + + + + + + Result CSV File to NavigationData Converter + + + + + + Convert Single File + + + true + + + + + + + Input CSV Logging File: + + + + + + + C:/Tools/test.csv + + + + + + + Output Navigation Data File: + + + + + + + C:/Tools/testoutput.xml + + + + + + + Qt::Horizontal + + + + + + + Convert File List + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-style:italic;">(use text files with a complete filename in every line)</span></p></body></html> + + + + + + + + + not loaded + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 100 + 0 + + + + Load Input List + + + + + + + + + + + not loaded + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 100 + 0 + + + + Load Output List + + + + + + + + + Qt::Horizontal + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Output Format + + + + + + XML + + + true + + + + + + + CSV + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Convert + + + + + + + + + + + + Orientation Calculation (out of three positions) + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Generate Reference From Current List + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Write Orientation Quaternions To File + + + + + + + + + + + + Qt::Vertical + + + + 20 + 632 + + + + + + + + + + + + + + QmitkDataStorageComboBox + QComboBox +
QmitkDataStorageComboBox.h
+
+
+ + +
diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementView.cpp b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementView.cpp new file mode 100644 index 0000000000..1a06eb6f3b --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementView.cpp @@ -0,0 +1,632 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +// Blueberry +#include +#include + +// Qmitk +#include "QmitkIGTTrackingSemiAutomaticMeasurementView.h" +#include "QmitkStdMultiWidget.h" + +// Qt +#include +#include +#include +#include + +// MITK +#include +#include +#include +#include "mitkHummelProtocolEvaluation.h" + +// POCO +#include +#include + +const std::string QmitkIGTTrackingSemiAutomaticMeasurementView::VIEW_ID = "org.mitk.views.igttrackingsemiautomaticmeasurement"; + +QmitkIGTTrackingSemiAutomaticMeasurementView::QmitkIGTTrackingSemiAutomaticMeasurementView() + : QmitkFunctionality() + , m_Controls(0) + , m_MultiWidget(NULL) +{ + m_NextFile = 0; + m_FilenameVector = std::vector(); + m_Timer = new QTimer(this); + m_logging = false; + m_referenceValid = true; + m_tracking = false; + m_EvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); +} + +QmitkIGTTrackingSemiAutomaticMeasurementView::~QmitkIGTTrackingSemiAutomaticMeasurementView() +{ +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::CreateResults() +{ + QString LogFileName = m_Controls->m_OutputPath->text() + "_results.log"; + mitk::LoggingBackend::Unregister(); + mitk::LoggingBackend::SetLogFile(LogFileName.toStdString().c_str()); + mitk::LoggingBackend::Register(); + + double RMSmean = 0; + for (int i = 0; i < m_RMSValues.size(); i++) + { + MITK_INFO << "RMS at " << this->m_FilenameVector.at(i) << ": " << m_RMSValues.at(i); + RMSmean += m_RMSValues.at(i); + } + RMSmean /= m_RMSValues.size(); + MITK_INFO << "RMS mean over " << m_RMSValues.size() << " values: " << RMSmean; + + mitk::DataNode::Pointer newNode = mitk::DataNode::New(); + newNode->SetName("Tracking Results"); + newNode->SetData(this->m_MeanPoints); + this->GetDataStorage()->Add(newNode); + + std::vector results5cmDistances; + if (m_Controls->m_mediumVolume->isChecked()) + mitk::HummelProtocolEvaluation::Evaluate5cmDistances(m_MeanPoints, mitk::HummelProtocolEvaluation::medium, results5cmDistances); + else if (m_Controls->m_smallVolume->isChecked()) + mitk::HummelProtocolEvaluation::Evaluate5cmDistances(m_MeanPoints, mitk::HummelProtocolEvaluation::small, results5cmDistances); + else if (m_Controls->m_standardVolume->isChecked()) + mitk::HummelProtocolEvaluation::Evaluate5cmDistances(m_MeanPoints, mitk::HummelProtocolEvaluation::standard, results5cmDistances); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::CreateQtPartControl(QWidget *parent) +{ + // build up qt view, unless already done + if (!m_Controls) + { + // create GUI widgets from the Qt Designer's .ui file + m_Controls = new Ui::QmitkIGTTrackingSemiAutomaticMeasurementViewControls; + m_Controls->setupUi(parent); + + //buttons + connect(m_Controls->m_LoadMeasurementToolStorage, SIGNAL(clicked()), this, SLOT(OnLoadMeasurementStorage())); + connect(m_Controls->m_LoadReferenceToolStorage, SIGNAL(clicked()), this, SLOT(OnLoadReferenceStorage())); + connect(m_Controls->m_StartTracking, SIGNAL(clicked()), this, SLOT(OnStartTracking())); + connect(m_Controls->m_LoadList, SIGNAL(clicked()), this, SLOT(OnMeasurementLoadFile())); + connect(m_Controls->m_StartNextMeasurement, SIGNAL(clicked()), this, SLOT(StartNextMeasurement())); + connect(m_Controls->m_ReapeatLastMeasurement, SIGNAL(clicked()), this, SLOT(RepeatLastMeasurement())); + connect(m_Controls->m_SetReference, SIGNAL(clicked()), this, SLOT(OnSetReference())); + connect(m_Controls->m_UseReferenceTrackingSystem, SIGNAL(toggled(bool)), this, SLOT(OnUseReferenceToggled(bool))); + connect(m_Controls->m_CreateResults, SIGNAL(clicked()), this, SLOT(CreateResults())); + + //event filter + qApp->installEventFilter(this); + + //timers + connect(m_Timer, SIGNAL(timeout()), this, SLOT(UpdateTimer())); + } + + //initialize some view + m_Controls->m_StopTracking->setEnabled(false); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::StdMultiWidgetAvailable(QmitkStdMultiWidget &stdMultiWidget) +{ + m_MultiWidget = &stdMultiWidget; +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::OnUseReferenceToggled(bool state) +{ + if (state) + { + m_Controls->m_ReferenceBox->setEnabled(true); + m_Controls->m_SetReference->setEnabled(true); + } + + else + { + m_Controls->m_ReferenceBox->setEnabled(false); + m_Controls->m_SetReference->setEnabled(false); + } +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::StdMultiWidgetNotAvailable() +{ + m_MultiWidget = NULL; +} + +mitk::NavigationToolStorage::Pointer QmitkIGTTrackingSemiAutomaticMeasurementView::ReadStorage(std::string file) +{ + mitk::NavigationToolStorage::Pointer returnValue; + + //initialize tool storage + returnValue = mitk::NavigationToolStorage::New(); + + //read tool storage from disk + mitk::NavigationToolStorageDeserializer::Pointer myDeserializer = mitk::NavigationToolStorageDeserializer::New(GetDataStorage()); + returnValue = myDeserializer->Deserialize(file); + if (returnValue.IsNull()) + { + QMessageBox msgBox; + msgBox.setText(myDeserializer->GetErrorMessage().c_str()); + msgBox.exec(); + + returnValue = NULL; + } + + return returnValue; +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::OnSetReference() +{ + //initialize reference + m_ReferenceStartPositions = std::vector(); + m_ReferenceTrackingDeviceSource->Update(); + QString Label = "Positions At Start: "; + for (int i = 0; i < m_ReferenceTrackingDeviceSource->GetNumberOfOutputs(); i++) + { + mitk::Point3D position = m_ReferenceTrackingDeviceSource->GetOutput(i)->GetPosition(); + Label = Label + "Tool" + QString::number(i) + ":[" + QString::number(position[0]) + ":" + QString::number(position[1]) + ":" + QString::number(position[1]) + "] "; + m_ReferenceStartPositions.push_back(position); + } + m_Controls->m_ReferencePosAtStart->setText(Label); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::OnLoadMeasurementStorage() +{ + //read in filename + QString filename = QFileDialog::getOpenFileName(NULL, tr("Open Toolfile"), "/", tr("All Files (*.*)")); + if (filename.isNull()) return; + + m_MeasurementStorage = ReadStorage(filename.toStdString()); + + //update label + Poco::Path myPath = Poco::Path(filename.toStdString()); //use this to seperate filename from path + QString toolLabel = QString("Tool Storage: ") + QString::number(m_MeasurementStorage->GetToolCount()) + " Tools from " + myPath.getFileName().c_str(); + m_Controls->m_MeasurementToolStorageLabel->setText(toolLabel); + + //update status widget + m_Controls->m_ToolStatusWidget->RemoveStatusLabels(); + m_Controls->m_ToolStatusWidget->PreShowTools(m_MeasurementStorage); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::OnLoadReferenceStorage() +{ + //read in filename + static QString oldFile; + if (oldFile.isNull()) oldFile = "/"; + QString filename = QFileDialog::getOpenFileName(NULL, tr("Open Toolfile"), oldFile, tr("All Files (*.*)")); + if (filename.isNull()) return; + oldFile = filename; + + m_ReferenceStorage = ReadStorage(filename.toStdString()); + + //update label + Poco::Path myPath = Poco::Path(filename.toStdString()); //use this to seperate filename from path + QString toolLabel = QString("Tool Storage: ") + QString::number(m_ReferenceStorage->GetToolCount()) + " Tools from " + myPath.getFileName().c_str(); + m_Controls->m_ReferenceToolStorageLabel->setText(toolLabel); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::OnStartTracking() +{ + //check if everything is ready to start tracking + if (m_MeasurementStorage.IsNull()) + { + MessageBox("Error: No measurement tools loaded yet!"); + return; + } + else if (m_ReferenceStorage.IsNull() && m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + MessageBox("Error: No refernce tools loaded yet!"); + return; + } + else if (m_MeasurementStorage->GetToolCount() == 0) + { + MessageBox("Error: No way to track without tools!"); + return; + } + else if (m_Controls->m_UseReferenceTrackingSystem->isChecked() && (m_ReferenceStorage->GetToolCount() == 0)) + { + MessageBox("Error: No way to track without tools!"); + return; + } + + //build the first IGT pipeline (MEASUREMENT) + mitk::TrackingDeviceSourceConfigurator::Pointer myTrackingDeviceSourceFactory1 = mitk::TrackingDeviceSourceConfigurator::New(this->m_MeasurementStorage, this->m_Controls->m_MeasurementTrackingDeviceConfigurationWidget->GetTrackingDevice()); + m_MeasurementTrackingDeviceSource = myTrackingDeviceSourceFactory1->CreateTrackingDeviceSource(this->m_MeasurementToolVisualizationFilter); + if (m_MeasurementTrackingDeviceSource.IsNull()) + { + MessageBox(myTrackingDeviceSourceFactory1->GetErrorMessage()); + return; + } + //connect the tool visualization widget + for (int i = 0; i < m_MeasurementTrackingDeviceSource->GetNumberOfOutputs(); i++) + { + m_Controls->m_ToolStatusWidget->AddNavigationData(m_MeasurementTrackingDeviceSource->GetOutput(i)); + m_EvaluationFilter->SetInput(i, m_MeasurementTrackingDeviceSource->GetOutput(i)); + } + m_Controls->m_ToolStatusWidget->ShowStatusLabels(); + m_Controls->m_ToolStatusWidget->SetShowPositions(true); + m_Controls->m_ToolStatusWidget->SetShowQuaternions(true); + + //build the second IGT pipeline (REFERENCE) + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + mitk::TrackingDeviceSourceConfigurator::Pointer myTrackingDeviceSourceFactory2 = mitk::TrackingDeviceSourceConfigurator::New(this->m_ReferenceStorage, this->m_Controls->m_ReferenceDeviceConfigurationWidget->GetTrackingDevice()); + m_ReferenceTrackingDeviceSource = myTrackingDeviceSourceFactory2->CreateTrackingDeviceSource(); + if (m_ReferenceTrackingDeviceSource.IsNull()) + { + MessageBox(myTrackingDeviceSourceFactory2->GetErrorMessage()); + return; + } + } + + //initialize tracking + try + { + m_MeasurementTrackingDeviceSource->Connect(); + m_MeasurementTrackingDeviceSource->StartTracking(); + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + m_ReferenceTrackingDeviceSource->Connect(); + m_ReferenceTrackingDeviceSource->StartTracking(); + } + } + catch (...) + { + MessageBox("Error while starting the tracking device!"); + return; + } + + //set reference + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) OnSetReference(); + + //start timer + m_Timer->start(1000 / (m_Controls->m_SamplingRate->value())); + + m_Controls->m_StartTracking->setEnabled(false); + m_Controls->m_StartTracking->setEnabled(true); + + m_tracking = true; +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::OnStopTracking() +{ + if (this->m_logging) FinishMeasurement(); + m_MeasurementTrackingDeviceSource->Disconnect(); + m_MeasurementTrackingDeviceSource->StopTracking(); + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + m_ReferenceTrackingDeviceSource->Disconnect(); + m_ReferenceTrackingDeviceSource->StopTracking(); + } + m_Timer->stop(); + m_Controls->m_StartTracking->setEnabled(true); + m_Controls->m_StartTracking->setEnabled(false); + m_tracking = false; +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::OnMeasurementLoadFile() +{ + m_FilenameVector = std::vector(); + m_FilenameVector.clear(); + m_NextFile = 0; + + //read in filename + QString filename = QFileDialog::getOpenFileName(NULL, tr("Open Measurement Filename List"), "/", tr("All Files (*.*)")); + if (filename.isNull()) return; + + //save old locale + char * oldLocale; + oldLocale = setlocale(LC_ALL, 0); + + //define own locale + std::locale C("C"); + setlocale(LC_ALL, "C"); + + //read file + std::ifstream file; + file.open(filename.toStdString().c_str(), std::ios::in); + if (file.good()) + { + //read out file + file.seekg(0L, std::ios::beg); // move to begin of file + while (!file.eof()) + { + std::string buffer; + std::getline(file, buffer); // read out file line by line + if (buffer.size() > 0) m_FilenameVector.push_back(buffer); + } + } + + //fill list at GUI + m_Controls->m_MeasurementList->clear(); + for (unsigned int i = 0; i < m_FilenameVector.size(); i++) { new QListWidgetItem(tr(m_FilenameVector.at(i).c_str()), m_Controls->m_MeasurementList); } + + //update label next measurement + std::stringstream label; + label << "Next Measurement: " << m_FilenameVector.at(0); + m_Controls->m_NextMeasurement->setText(label.str().c_str()); + + //reset results files + m_MeanPoints = mitk::PointSet::New(); + m_RMSValues = std::vector(); + m_EvaluationFilter = mitk::NavigationDataEvaluationFilter::New(); + if (m_MeasurementToolVisualizationFilter.IsNotNull()) m_EvaluationFilter->SetInput(0, m_MeasurementToolVisualizationFilter->GetOutput(0)); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::UpdateTimer() +{ + if (m_EvaluationFilter.IsNotNull() && m_logging) m_EvaluationFilter->Update(); + else m_MeasurementToolVisualizationFilter->Update(); + + m_Controls->m_ToolStatusWidget->Refresh(); + + //update reference + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + m_ReferenceTrackingDeviceSource->Update(); + QString Label = "Current Positions: "; + bool distanceThresholdExceeded = false; + for (int i = 0; i < m_ReferenceTrackingDeviceSource->GetNumberOfOutputs(); i++) + { + mitk::Point3D position = m_ReferenceTrackingDeviceSource->GetOutput(i)->GetPosition(); + Label = Label + "Tool" + QString::number(i) + ":[" + QString::number(position[0]) + ":" + QString::number(position[1]) + ":" + QString::number(position[1]) + "] "; + if (position.EuclideanDistanceTo(m_ReferenceStartPositions.at(i)) > m_Controls->m_ReferenceThreshold->value()) distanceThresholdExceeded = true; + } + m_Controls->m_ReferenceCurrentPos->setText(Label); + if (distanceThresholdExceeded) + { + m_Controls->m_ReferenceOK->setText("NOT OK!"); + m_referenceValid = false; + } + else + { + m_Controls->m_ReferenceOK->setText("OK"); + m_referenceValid = true; + } + } + + //update logging + if (m_logging) + { + //check for missing objects + if (m_MeasurementLoggingFilterXML.IsNull() || + m_MeasurementLoggingFilterCSV.IsNull() + ) + { + return; + } + + //log/measure + m_MeasurementLoggingFilterXML->Update(); + m_MeasurementLoggingFilterCSV->Update(); + + if (m_Controls->m_UseReferenceTrackingSystem->isChecked() && + m_ReferenceLoggingFilterXML.IsNotNull() && + m_ReferenceLoggingFilterCSV.IsNotNull()) + { + m_ReferenceLoggingFilterXML->Update(); + m_ReferenceLoggingFilterCSV->Update(); + } + m_loggedFrames++; + LogAdditionalCSVFile(); + + //check if all frames are logged ... if yes finish the measurement + if (m_loggedFrames > m_Controls->m_SamplesPerMeasurement->value()) { FinishMeasurement(); } + + //update logging label + QString loggingLabel = "Collected Samples: " + QString::number(m_loggedFrames); + m_Controls->m_CollectedSamples->setText(loggingLabel); + } +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::StartNextMeasurement() +{ + if (this->m_NextFile >= m_FilenameVector.size()) + { + MessageBox("Last Measurement reached!"); + return; + } + + m_loggedFrames = 0; + m_logging = true; + + //check if directory exists, if not create one + Poco::File myPath(std::string(m_Controls->m_OutputPath->text().toUtf8()).c_str()); + if (!myPath.exists()) myPath.createDirectory(); + + QString LogFileName = m_Controls->m_OutputPath->text() + QString(m_FilenameVector.at(m_NextFile).c_str()) + ".log"; + mitk::LoggingBackend::Unregister(); + mitk::LoggingBackend::SetLogFile(LogFileName.toStdString().c_str()); + mitk::LoggingBackend::Register(); + + //initialize logging filters + m_MeasurementLoggingFilterXML = mitk::NavigationDataRecorderDeprecated::New(); + m_MeasurementLoggingFilterXML->SetRecordingMode(mitk::NavigationDataRecorderDeprecated::NormalFile); + m_MeasurementLoggingFilterCSV = mitk::NavigationDataRecorderDeprecated::New(); + m_MeasurementLoggingFilterCSV->SetRecordingMode(mitk::NavigationDataRecorderDeprecated::NormalFile); + m_MeasurementLoggingFilterXML->SetOutputFormat(mitk::NavigationDataRecorderDeprecated::xml); + m_MeasurementLoggingFilterCSV->SetOutputFormat(mitk::NavigationDataRecorderDeprecated::csv); + QString MeasurementFilenameXML = m_Controls->m_OutputPath->text() + QString(m_FilenameVector.at(m_NextFile).c_str()) + ".xml"; + QString MeasurementFilenameCSV = m_Controls->m_OutputPath->text() + QString(m_FilenameVector.at(m_NextFile).c_str()) + ".csv"; + m_MeasurementLoggingFilterXML->SetFileName(MeasurementFilenameXML.toStdString()); + m_MeasurementLoggingFilterCSV->SetFileName(MeasurementFilenameCSV.toStdString()); + m_MeasurementLoggingFilterXML->SetRecordCountLimit(m_Controls->m_SamplesPerMeasurement->value()); + m_MeasurementLoggingFilterCSV->SetRecordCountLimit(m_Controls->m_SamplesPerMeasurement->value()); + + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + m_ReferenceLoggingFilterXML = mitk::NavigationDataRecorderDeprecated::New(); + m_ReferenceLoggingFilterXML->SetRecordingMode(mitk::NavigationDataRecorderDeprecated::NormalFile); + m_ReferenceLoggingFilterCSV = mitk::NavigationDataRecorderDeprecated::New(); + m_ReferenceLoggingFilterCSV->SetRecordingMode(mitk::NavigationDataRecorderDeprecated::NormalFile); + m_ReferenceLoggingFilterXML->SetOutputFormat(mitk::NavigationDataRecorderDeprecated::xml); + m_ReferenceLoggingFilterCSV->SetOutputFormat(mitk::NavigationDataRecorderDeprecated::csv); + QString ReferenceFilenameXML = m_Controls->m_OutputPath->text() + "Reference_" + QString(m_FilenameVector.at(m_NextFile).c_str()) + ".xml"; + QString ReferenceFilenameCSV = m_Controls->m_OutputPath->text() + "Reference_" + QString(m_FilenameVector.at(m_NextFile).c_str()) + ".csv"; + m_ReferenceLoggingFilterXML->SetFileName(ReferenceFilenameXML.toStdString()); + m_ReferenceLoggingFilterCSV->SetFileName(ReferenceFilenameCSV.toStdString()); + m_ReferenceLoggingFilterXML->SetRecordCountLimit(m_Controls->m_SamplesPerMeasurement->value()); + m_ReferenceLoggingFilterCSV->SetRecordCountLimit(m_Controls->m_SamplesPerMeasurement->value()); + } + + //start additional csv logging + StartLoggingAdditionalCSVFile(m_FilenameVector.at(m_NextFile)); + + //connect filter + for (int i = 0; i < m_MeasurementToolVisualizationFilter->GetNumberOfOutputs(); i++) + { + m_MeasurementLoggingFilterXML->AddNavigationData(m_MeasurementToolVisualizationFilter->GetOutput(i)); + m_MeasurementLoggingFilterCSV->AddNavigationData(m_MeasurementToolVisualizationFilter->GetOutput(i)); + } + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) for (int i = 0; i < m_ReferenceTrackingDeviceSource->GetNumberOfOutputs(); i++) + { + m_ReferenceLoggingFilterXML->AddNavigationData(m_ReferenceTrackingDeviceSource->GetOutput(i)); + m_ReferenceLoggingFilterCSV->AddNavigationData(m_ReferenceTrackingDeviceSource->GetOutput(i)); + } + + //start filter + m_MeasurementLoggingFilterXML->StartRecording(); + m_MeasurementLoggingFilterCSV->StartRecording(); + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + m_ReferenceLoggingFilterXML->StartRecording(); + m_ReferenceLoggingFilterCSV->StartRecording(); + } + + //disable all buttons + DisableAllButtons(); + + //update label next measurement + std::stringstream label; + if ((m_NextFile + 1) >= m_FilenameVector.size()) label << "Next Measurement: "; + else label << "Next Measurement: " << m_FilenameVector.at(m_NextFile + 1); + m_Controls->m_NextMeasurement->setText(label.str().c_str()); + + //update label last measurement + std::stringstream label2; + label2 << "Last Measurement: " << m_FilenameVector.at(m_NextFile); + m_Controls->m_LastMeasurement->setText(label2.str().c_str()); + + m_NextFile++; +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::RepeatLastMeasurement() +{ + m_NextFile--; + StartNextMeasurement(); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::MessageBox(std::string s) +{ + QMessageBox msgBox; + msgBox.setText(s.c_str()); + msgBox.exec(); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::DisableAllButtons() +{ + m_Controls->m_LoadList->setEnabled(false); + m_Controls->m_StartNextMeasurement->setEnabled(false); + m_Controls->m_ReapeatLastMeasurement->setEnabled(false); + m_Controls->m_SamplingRate->setEnabled(false); + m_Controls->m_SamplesPerMeasurement->setEnabled(false); + m_Controls->m_ReferenceThreshold->setEnabled(false); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::EnableAllButtons() +{ + m_Controls->m_LoadList->setEnabled(true); + m_Controls->m_StartNextMeasurement->setEnabled(true); + m_Controls->m_ReapeatLastMeasurement->setEnabled(true); + m_Controls->m_SamplingRate->setEnabled(true); + m_Controls->m_SamplesPerMeasurement->setEnabled(true); + m_Controls->m_ReferenceThreshold->setEnabled(true); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::FinishMeasurement() +{ + m_logging = false; + + m_MeasurementLoggingFilterXML->StopRecording(); + m_MeasurementLoggingFilterCSV->StopRecording(); + if (m_Controls->m_UseReferenceTrackingSystem->isChecked()) + { + m_ReferenceLoggingFilterXML->StopRecording(); + m_ReferenceLoggingFilterCSV->StopRecording(); + } + StopLoggingAdditionalCSVFile(); + + int id = m_NextFile - 1; + mitk::Point3D positionMean = m_EvaluationFilter->GetPositionMean(0); + MITK_INFO << "Evaluated " << m_EvaluationFilter->GetNumberOfAnalysedNavigationData(0) << " samples."; + double rms = m_EvaluationFilter->GetPositionErrorRMS(0); + MITK_INFO << "RMS: " << rms; + MITK_INFO << "Position Mean: " << positionMean; + m_MeanPoints->SetPoint(id, positionMean); + if (m_RMSValues.size() <= id) m_RMSValues.push_back(rms); + else m_RMSValues[id] = rms; + + m_EvaluationFilter->ResetStatistic(); + + EnableAllButtons(); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::StartLoggingAdditionalCSVFile(std::string filePostfix) +{ + //write logfile header + QString header = "Nr;MITK_Time;Valid_Reference;"; + QString tool = QString("MeasureTool_") + QString(m_MeasurementTrackingDeviceSource->GetOutput(0)->GetName()); + header = header + tool + "[x];" + tool + "[y];" + tool + "[z];" + tool + "[qx];" + tool + "[qy];" + tool + "[qz];" + tool + "[qr]\n"; + + //open logfile and write header + m_logFileCSV.open(std::string(m_Controls->m_OutputPath->text().toUtf8()).append("/LogFileCombined").append(filePostfix.c_str()).append(".csv").c_str(), std::ios::out); + m_logFileCSV << header.toStdString().c_str(); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::LogAdditionalCSVFile() +{ + mitk::Point3D pos = m_MeasurementTrackingDeviceSource->GetOutput(0)->GetPosition(); + mitk::Quaternion rot = m_MeasurementTrackingDeviceSource->GetOutput(0)->GetOrientation(); + std::string valid = ""; + if (m_referenceValid) valid = "true"; + else valid = "false"; + std::stringstream timestamp; + timestamp << m_MeasurementTrackingDeviceSource->GetOutput(0)->GetTimeStamp(); + QString dataSet = QString::number(m_loggedFrames) + ";" + QString(timestamp.str().c_str()) + ";" + QString(valid.c_str()) + ";" + QString::number(pos[0]) + ";" + QString::number(pos[1]) + ";" + QString::number(pos[2]) + ";" + QString::number(rot.x()) + ";" + QString::number(rot.y()) + ";" + QString::number(rot.z()) + ";" + QString::number(rot.r()) + "\n"; + m_logFileCSV << dataSet.toStdString(); +} + +void QmitkIGTTrackingSemiAutomaticMeasurementView::StopLoggingAdditionalCSVFile() +{ + m_logFileCSV.close(); +} + +bool QmitkIGTTrackingSemiAutomaticMeasurementView::eventFilter(QObject *obj, QEvent *ev) +{ + if (ev->type() == QEvent::KeyPress) + { + QKeyEvent *k = (QKeyEvent *)ev; + bool up = false; + bool down = false; + if (k->key() == 16777238) up = true; //page up + else if (k->key() == 16777239) down = true; //page down + + if (down && m_tracking && !m_logging) + { + StartNextMeasurement(); + } + } + + return false; +} diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementView.h b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementView.h new file mode 100644 index 0000000000..3ec59713ba --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementView.h @@ -0,0 +1,132 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef QmitkIGTTrackingSemiAutomaticMeasurementView_h +#define QmitkIGTTrackingSemiAutomaticMeasurementView_h + +#include + +#include + +//QT +#include + +//MITK +#include +#include +#include +#include +#include + +#include "ui_QmitkIGTTrackingSemiAutomaticMeasurementViewControls.h" + +/*! + \brief QmitkIGTTrackingSemiAutomaticMeasurementView + + \warning This application module is not yet documented. Use "svn blame/praise/annotate" and ask the author to provide basic documentation. + + \sa QmitkFunctionality + \ingroup Functionalities + */ +class QmitkIGTTrackingSemiAutomaticMeasurementView : public QmitkFunctionality +{ + // this is needed for all Qt objects that should have a Qt meta-object + // (everything that derives from QObject and wants to have signal/slots) + Q_OBJECT + +public: + + static const std::string VIEW_ID; + + QmitkIGTTrackingSemiAutomaticMeasurementView(); + virtual ~QmitkIGTTrackingSemiAutomaticMeasurementView(); + + virtual void CreateQtPartControl(QWidget *parent); + + virtual void StdMultiWidgetAvailable(QmitkStdMultiWidget &stdMultiWidget); + virtual void StdMultiWidgetNotAvailable(); + + protected slots: + + void OnLoadMeasurementStorage(); + void OnLoadReferenceStorage(); + void OnStartTracking(); + void OnStopTracking(); + void OnMeasurementLoadFile(); + void OnSetReference(); + void StartNextMeasurement(); + void RepeatLastMeasurement(); + void UpdateTimer(); + void CreateResults(); + void OnUseReferenceToggled(bool state); + +protected: + + Ui::QmitkIGTTrackingSemiAutomaticMeasurementViewControls* m_Controls; + + QmitkStdMultiWidget* m_MultiWidget; + + //the tool storages + mitk::NavigationToolStorage::Pointer m_MeasurementStorage; + mitk::NavigationToolStorage::Pointer m_ReferenceStorage; + + //members for the filter pipeline + mitk::TrackingDeviceSource::Pointer m_MeasurementTrackingDeviceSource; + mitk::NavigationDataObjectVisualizationFilter::Pointer m_MeasurementToolVisualizationFilter; + mitk::NavigationDataRecorderDeprecated::Pointer m_MeasurementLoggingFilterXML; + mitk::NavigationDataRecorderDeprecated::Pointer m_MeasurementLoggingFilterCSV; + mitk::TrackingDeviceSource::Pointer m_ReferenceTrackingDeviceSource; + mitk::NavigationDataRecorderDeprecated::Pointer m_ReferenceLoggingFilterXML; + mitk::NavigationDataRecorderDeprecated::Pointer m_ReferenceLoggingFilterCSV; + + //members for file name list + std::vector m_FilenameVector; + int m_NextFile; + + //help methods + mitk::NavigationToolStorage::Pointer ReadStorage(std::string file); + void MessageBox(std::string s); + void DisableAllButtons(); + void EnableAllButtons(); + void FinishMeasurement(); + void StartLoggingAdditionalCSVFile(std::string filePostfix); + void LogAdditionalCSVFile(); + void StopLoggingAdditionalCSVFile(); + + //timer + QTimer* m_Timer; + + //memebers for reference checking + std::vector m_ReferenceStartPositions; + bool m_referenceValid; + + //logging members + int m_loggedFrames; + bool m_logging; + std::fstream m_logFileCSV; + + //event filter for key presses + bool eventFilter(QObject *obj, QEvent *ev); + + //results members + mitk::PointSet::Pointer m_MeanPoints; + std::vector m_RMSValues; + mitk::NavigationDataEvaluationFilter::Pointer m_EvaluationFilter; + + bool m_tracking; +}; + +#endif // _QMITKIGTTRACKINGSEMIAUTOMATICMEASUREMENTVIEW_H_INCLUDED diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementViewControls.ui b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementViewControls.ui new file mode 100644 index 0000000000..b85fd511cb --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/QmitkIGTTrackingSemiAutomaticMeasurementViewControls.ui @@ -0,0 +1,588 @@ + + + QmitkIGTTrackingSemiAutomaticMeasurementViewControls + + + + 0 + 0 + 419 + 931 + + + + + 0 + 0 + + + + QmitkTemplate + + + + + + 0 + + + + Tracking Initialization + + + + + + Measurement Tracking System + + + + + + + + + + + Tool Storage: <none> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Load Tool Storage + + + + + + + + + + + + Reference Trackingsystem + + + + + + + + + + + Tool Storage: <none> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Load Tool Storage + + + + + + + + + + + + Start Tracking + + + + + + + Stop Tracking + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Measurement + + + + + + Measurement List: + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Load List + + + + + + + + + + + Output Path: + + + + + + + C:/temp/ + + + + + + + + + Last Measurement: <none> + + + + + + + Next Measurement: <none> + + + + + + + Collected Samples: <none> + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 200 + 0 + + + + Start Next Measurement + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 200 + 0 + + + + Repeat Last Measurement + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 200 + 0 + + + + Create Results + + + + + + + + + Qt::Vertical + + + + 20 + 281 + + + + + + + + Qt::Horizontal + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600; text-decoration: underline;">Measurement Sensors:</span></p></body></html> + + + + + + + + + + Qt::Horizontal + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600; text-decoration: underline;">Reference Sensors:</span></p></body></html> + + + + + + + Set Reference + + + + + + + Reference Postion: <none> + + + + + + + Current Positions: <none> + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:28pt; font-weight:600;">OK</span></p></body></html> + + + Qt::AlignCenter + + + + + + + + Settings + + + + + + + + Sampling Rate (Times Per Second): + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 999.000000000000000 + + + 15.000000000000000 + + + + + + + + + + + Samples Per Measurement: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 1000 + + + 150 + + + + + + + + + + + Threshold For Reference Tools: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 1.000000000000000 + + + + + + + + + Use Reference Tracking System + + + true + + + + + + + Tracking Volume + + + + + + Small Volume (3 X 4 Positions) + + + + + + + Medium Volume (5 X 5 Positions) + + + true + + + + + + + Standard Volume (10 X 9 Positions) + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + QmitkToolTrackingStatusWidget + QWidget +
QmitkToolTrackingStatusWidget.h
+ 1 +
+ + QmitkTrackingDeviceConfigurationWidget + QWidget +
QmitkTrackingDeviceConfigurationWidget.h
+ 1 +
+
+ + +
diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkHummelProtocolEvaluation.cpp b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkHummelProtocolEvaluation.cpp new file mode 100644 index 0000000000..e025986bc2 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkHummelProtocolEvaluation.cpp @@ -0,0 +1,454 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ +#define _USE_MATH_DEFINES +#include "mitkHummelProtocolEvaluation.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +bool mitk::HummelProtocolEvaluation::Evaluate15cmDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results) +{ + if (m != mitk::HummelProtocolEvaluation::standard) { MITK_WARN << "15 cm distances are only evaluated for standard volumes, aborting!"; return false; } + + MITK_INFO << "########### 15 cm distance errors #############"; + + //convert measurements to matrix + std::array ,9> matrix = ParseMatrixStandardVolume(p); + + //these are the variables for the results: + std::vector distances; + std::vector descriptions; + + //evaluation of rows + int distanceCounter = 0; + for (int row = 0; row < 9; row++) //rows + for (int distance = 0; distance < 7; distance++) + { + distanceCounter++; + mitk::Point3D point1 = p->GetPoint(row * 10 + distance); + mitk::Point3D point2 = p->GetPoint(row * 10 + distance + 3); + distances.push_back(point1.EuclideanDistanceTo(point2)); + std::stringstream description; + description << "Distance(" << distanceCounter << ") " << (row + 1) << "/" << (distance + 1) << " to " << (row + 1) << "/" << (distance + 4); + descriptions.push_back(description.str()); + } + + //evaluation of columns + for (int column = 0; column < 10; column++) + for (int row = 0; row < 6; row++) + { + distanceCounter++; + mitk::Point3D point1 = matrix[row][column]; + mitk::Point3D point2 = matrix[row + 3][column]; + distances.push_back(point1.EuclideanDistanceTo(point2)); + std::stringstream description; + description << "Distance(" << distanceCounter << ") " << (row + 1) << "/" << (column + 1) << " to " << (row + 4) << "/" << (column + 1); + descriptions.push_back(description.str()); + } + + //compute all errors + for (int i = 0; i < distances.size(); i++) + { + HummelProtocolDistanceError currentError; + currentError.distanceError = 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 statistics = mitk::HummelProtocolEvaluation::ComputeStatistics(Results); + for (auto currentError : statistics) + { + Results.push_back(currentError); + MITK_INFO << currentError.description << " : " << currentError.distanceError; + } + + return true; +} + +bool mitk::HummelProtocolEvaluation::Evaluate30cmDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results) +{ + if (m != mitk::HummelProtocolEvaluation::standard) { MITK_WARN << "30 cm distances are only evaluated for standard volumes, aborting!"; return false; } + MITK_INFO << "########### 30 cm distance errors #############"; + + //convert measurements to matrix + std::array ,9> matrix = ParseMatrixStandardVolume(p); + + //these are the variables for the results: + std::vector distances; + std::vector descriptions; + + //evaluation of rows + int distanceCounter = 0; + for (int row = 0; row < 9; row++) //rows + for (int distance = 0; distance < 4; distance++) + { + distanceCounter++; + mitk::Point3D point1 = p->GetPoint(row * 10 + distance); + mitk::Point3D point2 = p->GetPoint(row * 10 + distance + 6); + distances.push_back(point1.EuclideanDistanceTo(point2)); + std::stringstream description; + description << "Distance(" << distanceCounter << ") " << (row + 1) << "/" << (distance + 1) << " to " << (row + 1) << "/" << (distance + 7); + descriptions.push_back(description.str()); + } + + //evaluation of columns + for (int column = 0; column < 10; column++) + for (int row = 0; row < 3; row++) + { + distanceCounter++; + mitk::Point3D point1 = matrix[row][column]; + mitk::Point3D point2 = matrix[row + 6][column]; + distances.push_back(point1.EuclideanDistanceTo(point2)); + std::stringstream description; + description << "Distance(" << distanceCounter << ") " << (row + 1) << "/" << (column + 1) << " to " << (row + 7) << "/" << (column + 1); + descriptions.push_back(description.str()); + } + + //compute all errors + for (int i = 0; i < distances.size(); i++) + { + HummelProtocolDistanceError currentError; + currentError.distanceError = 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 statistics = mitk::HummelProtocolEvaluation::ComputeStatistics(Results); + for (auto currentError : statistics) + { + Results.push_back(currentError); + MITK_INFO << currentError.description << " : " << currentError.distanceError; + } + + return true; +} + +bool mitk::HummelProtocolEvaluation::EvaluateAccumulatedDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results) +{ + if (m != mitk::HummelProtocolEvaluation::standard) { MITK_WARN << "Accumulated distances are only evaluated for standard volumes, aborting!"; return false; } + //convert measurements to matrix + std::array ,9> matrix = ParseMatrixStandardVolume(p); + + MITK_INFO << "########### accumulated distance errors #############"; + + int distanceCounter = 0; + + //evaluation of rows + for (int row = 0; row < 9; row++) //rows + for (int distance = 0; distance < 9; distance++) + { + distanceCounter++; + mitk::Point3D point1 = p->GetPoint(row * 10); + mitk::Point3D point2 = p->GetPoint(row * 10 + distance + 1); + std::stringstream description; + description << "Distance(" << distanceCounter << ") " << (row + 1) << "/1 to " << (row + 1) << "/" << (distance + 2); + //compute error + HummelProtocolDistanceError currentError; + currentError.distanceError = 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 statistics = mitk::HummelProtocolEvaluation::ComputeStatistics(Results); + for (auto currentError : statistics) + { + Results.push_back(currentError); + MITK_INFO << currentError.description << " : " << currentError.distanceError; + } + +return true; +} + + + +bool mitk::HummelProtocolEvaluation::Evaluate5cmDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results) +{ +MITK_INFO << "########### 5 cm distance errors #############"; +std::vector distances; +std::vector descriptions; +switch (m) +{ +case small: + if (p->GetSize() != 12) { + MITK_WARN << "Wrong number of points: " << p->GetSize() << " (expected 12), aborting"; + return false; + } + MITK_INFO << "Computing Hummel protocol distance errors for small measurement volumes (12 points)..."; + + //row 1 + distances.push_back(p->GetPoint(0).EuclideanDistanceTo(p->GetPoint(1))); //0 + descriptions.push_back("Distance 4/4 to 4/5"); + distances.push_back(p->GetPoint(1).EuclideanDistanceTo(p->GetPoint(2))); //1 + descriptions.push_back("Distance 4/5 to 4/6"); + distances.push_back(p->GetPoint(2).EuclideanDistanceTo(p->GetPoint(3))); //2 + descriptions.push_back("Distance 4/6 to 4/7"); + //row 2 + distances.push_back(p->GetPoint(4).EuclideanDistanceTo(p->GetPoint(5))); //3 + descriptions.push_back("Distance 5/4 to 5/5"); + distances.push_back(p->GetPoint(5).EuclideanDistanceTo(p->GetPoint(6))); //4 + descriptions.push_back("Distance 5/5 to 5/6"); + distances.push_back(p->GetPoint(6).EuclideanDistanceTo(p->GetPoint(7))); //5 + descriptions.push_back("Distance 5/6 to 5/7"); + //row 3 + distances.push_back(p->GetPoint(8).EuclideanDistanceTo(p->GetPoint(9))); //6 + descriptions.push_back("Distance 6/4 to 6/5"); + distances.push_back(p->GetPoint(9).EuclideanDistanceTo(p->GetPoint(10))); //7 + descriptions.push_back("Distance 6/5 to 6/6"); + distances.push_back(p->GetPoint(10).EuclideanDistanceTo(p->GetPoint(11))); //8 + descriptions.push_back("Distance 6/6 to 6/7"); + //column 1 + distances.push_back(p->GetPoint(0).EuclideanDistanceTo(p->GetPoint(4))); //9 + descriptions.push_back("Distance 4/4 to 5/4"); + distances.push_back(p->GetPoint(4).EuclideanDistanceTo(p->GetPoint(8))); //10 + descriptions.push_back("Distance 5/4 to 6/4"); + //column 2 + distances.push_back(p->GetPoint(1).EuclideanDistanceTo(p->GetPoint(5))); //11 + descriptions.push_back("Distance 4/5 to 5/5"); + distances.push_back(p->GetPoint(5).EuclideanDistanceTo(p->GetPoint(9))); //12 + descriptions.push_back("Distance 5/5 to 6/5"); + //column 3 + distances.push_back(p->GetPoint(2).EuclideanDistanceTo(p->GetPoint(6))); //13 + descriptions.push_back("Distance 4/6 to 5/6"); + distances.push_back(p->GetPoint(6).EuclideanDistanceTo(p->GetPoint(10))); //14 + descriptions.push_back("Distance 5/6 to 6/6"); + //column 4 + distances.push_back(p->GetPoint(3).EuclideanDistanceTo(p->GetPoint(7))); //15 + descriptions.push_back("Distance 4/7 to 5/7"); + distances.push_back(p->GetPoint(7).EuclideanDistanceTo(p->GetPoint(11))); //16 + descriptions.push_back("Distance 5/7 to 6/7"); + +break; + +case 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, 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 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, 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 (int 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 statistics = mitk::HummelProtocolEvaluation::ComputeStatistics(Results); +for (auto currentError : statistics) +{ + Results.push_back(currentError); + MITK_INFO << currentError.description << " : " << currentError.distanceError; +} + +return true; +} + +std::array, 9> mitk::HummelProtocolEvaluation::ParseMatrixStandardVolume(mitk::PointSet::Pointer p) +{ + + std::array ,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, 5> mitk::HummelProtocolEvaluation::ParseMatrixMediumVolume(mitk::PointSet::Pointer p) +{ + + std::array, 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::ComputeStatistics(std::vector values) +{ + std::vector returnValue; + + //convert input values to boost / using boost accumulators for statistics + boost::accumulators::accumulator_set + > > acc; + for (mitk::HummelProtocolEvaluation::HummelProtocolDistanceError each : values) + { + acc(each.distanceError); + } + + returnValue.push_back({ values.size(), "N" }); + returnValue.push_back({ boost::accumulators::mean(acc), "Mean" }); + //double quantile25th = boost::accumulators::quantile(acc, boost::accumulators::quantile_probability = 0.25); + //returnValue.push_back({ boost::accumulators::median(acc), "Median" }); + //returnValue.push_back({ boost::accumulators::variance(acc), "Variance" }); + returnValue.push_back({ boost::accumulators::min(acc), "Min" }); + returnValue.push_back({ boost::accumulators::max(acc), "Max" }); + + //don't get the boost stuff working correctly, so computing the quantiles, median and standard deviation by myself: + std::vector quantile; + for (mitk::HummelProtocolEvaluation::HummelProtocolDistanceError each : values) + {quantile.push_back(each.distanceError);} + + auto const Q1 = quantile.size() / 4; + auto const Q2 = quantile.size() / 2; + auto const Q3 = Q1 + Q2; + + std::sort(quantile.begin(),quantile.end()); + + returnValue.push_back({ quantile[Q1], "Quartile 1" }); + returnValue.push_back({ quantile[Q2], "Median" }); + returnValue.push_back({ quantile[Q3], "Quartile 3" }); + + double mean = boost::accumulators::mean(acc); + double errorSum = 0; + for (mitk::HummelProtocolEvaluation::HummelProtocolDistanceError each : values) + { + double error = pow((each.distanceError - mean),2); + errorSum += error; + } + double variance = errorSum / values.size(); + double sampleVariance = errorSum / (values.size()-1); + double standardDev = sqrt(variance); + double sampleStandardDev = sqrt(sampleVariance); + + returnValue.push_back({ variance, "Variance" }); + returnValue.push_back({ sampleVariance, "Sample Variance" }); + returnValue.push_back({ standardDev, "Standard Deviation" }); + returnValue.push_back({ sampleStandardDev, "Sample Standard Deviation" }); + + + return returnValue; + +} diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkHummelProtocolEvaluation.h b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkHummelProtocolEvaluation.h new file mode 100644 index 0000000000..d08a7d1c8c --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkHummelProtocolEvaluation.h @@ -0,0 +1,96 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + + +#ifndef MITKHummelProtocolEvaluation_H_HEADER_INCLUDED_ +#define MITKHummelProtocolEvaluation_H_HEADER_INCLUDED_ + +#include +#include + + + + +namespace mitk +{ + + /**Documentation + * \brief Static methods for evaluations according to the assessment protocol + * for EM trackers published by Hummel et al. 2005 [1]. + * + * [1] Hummel, J. et al. - Design and application of an assessment protocol for electromagnetic tracking systems. Med Phys 32(7), July 2005 + * + * \ingroup IGT + */ + class HummelProtocolEvaluation + + { + public: + + /** Distance error with description. */ + struct HummelProtocolDistanceError {double distanceError; std::string description;}; + /** Tracking volumes for evaluation. + * standard: The standard volume of 9 x 10 measurment points as described in [1] + * small: A small volume in the center 3 x 4 measurement points, for smaller field generators [2] + * [2] Maier-Hein, L. et al. - Standardized assessment of new electromagnetic field generators in an interventional radiology setting. Med Phys 39(6), June 2012 + */ + enum HummelProtocolMeasurementVolume { small, medium, standard }; + /** Evaluates the 5 cm distances as defined by the Hummel protocol [1,2]. + * @return Returns true if evaluation was successfull, false if not. + * @param[out] Results Please give an empty vector. The results will be added to this vector. + */ + static bool Evaluate5cmDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results); + + /** Evaluates the 15 cm distances as defined by the Hummel protocol [1,2]. + * @return Returns true if evaluation was successfull, false if not. + * @param[out] Results Please give an empty vector. The results will be added to this vector. + */ + static bool Evaluate15cmDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results); + + /** Evaluates the 30 cm distances as defined by the Hummel protocol [1,2]. + * @return Returns true if evaluation was successfull, false if not. + * @param[out] Results Please give an empty vector. The results will be added to this vector. + */ + static bool Evaluate30cmDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results); + + /** Evaluates the accumulated distances as defined by the Hummel protocol [1,2]. + * @return Returns true if evaluation was successfull, false if not. + * @param[out] Results Please give an empty vector. The results will be added to this vector. + */ + static bool EvaluateAccumulatedDistances(mitk::PointSet::Pointer p, HummelProtocolMeasurementVolume m, std::vector &Results); + + /** Computes statistics (as mean, standard deviation, quantiles, min, max, etc.) on the given values. + * The results are stored inside the return value. + */ + static std::vector ComputeStatistics(std::vector values); + + protected: + /** Converts a pointset holding all measurement points of the hummel protocol in line-by-line order + * to an array representing the hummel board. + */ + static std::array ,9> ParseMatrixStandardVolume(mitk::PointSet::Pointer p); + //It would be really wonderfull if we could replace std::array ,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 compile + //even the usage of a double pointer (eg mitk::Matrix) 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 >’ + //Under Windows this error does not appear there everything compiles fine. + + static std::array, 5> ParseMatrixMediumVolume(mitk::PointSet::Pointer p); + + }; +} // namespace mitk + +#endif /* MITKHummelProtocolEvaluation_H_HEADER_INCLUDED_ */ diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkNavigationDataCSVSequentialPlayer.cpp b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkNavigationDataCSVSequentialPlayer.cpp new file mode 100644 index 0000000000..aeb1b92954 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkNavigationDataCSVSequentialPlayer.cpp @@ -0,0 +1,413 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ +#define _USE_MATH_DEFINES +#include "mitkNavigationDataCSVSequentialPlayer.h" +#include +#include +#include +#include +#include + +mitk::NavigationDataCSVSequentialPlayer::NavigationDataCSVSequentialPlayer() + : mitk::NavigationDataPlayerBase() +{ + m_NavigationDatas = std::vector(); + m_CurrentPos = 0; + m_Filetype = mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV; + +} + +mitk::NavigationDataCSVSequentialPlayer::~NavigationDataCSVSequentialPlayer() +{ +} + +bool mitk::NavigationDataCSVSequentialPlayer::IsAtEnd() +{ + if (m_CurrentPos >= m_NavigationDatas.size()) return true; + else return false; +} + +void mitk::NavigationDataCSVSequentialPlayer:: +SetFileName(const std::string& fileName) +{ + this->SetNumberOfOutputs(1); + FillOutputEmpty(0); + + MITK_INFO << "Reading file: " << fileName; + m_NavigationDatas = GetNavigationDatasFromFile(fileName); + + this->Modified(); +} + +void mitk::NavigationDataCSVSequentialPlayer::FillOutputEmpty(int number) +{ + this->SetNthOutput(number, GetEmptyNavigationData()); +} + +mitk::NavigationData::Pointer mitk::NavigationDataCSVSequentialPlayer::GetEmptyNavigationData() +{ + mitk::NavigationData::Pointer emptyNd = mitk::NavigationData::New(); + mitk::NavigationData::PositionType position; + mitk::NavigationData::OrientationType orientation(0.0, 0.0, 0.0, 0.0); + position.Fill(0.0); + + emptyNd->SetPosition(position); + emptyNd->SetOrientation(orientation); + emptyNd->SetDataValid(false); + return emptyNd; +} +int mitk::NavigationDataCSVSequentialPlayer::GetNumberOfSnapshots() +{ + return m_NavigationDatas.size(); +} +void mitk::NavigationDataCSVSequentialPlayer::GenerateData() +{ + for (unsigned int index = 0; index < this->GetNumberOfOutputs(); index++) + { + mitk::NavigationData* output = this->GetOutput(index); + + if (m_CurrentPos > m_NavigationDatas.size()) + { + FillOutputEmpty(index); + return; + } + + output->Graft(this->m_NavigationDatas.at(m_CurrentPos)); + m_CurrentPos++; + } +} + +void mitk::NavigationDataCSVSequentialPlayer::UpdateOutputInformation() +{ + this->Modified(); // make sure that we need to be updated + Superclass::UpdateOutputInformation(); +} + +std::vector mitk::NavigationDataCSVSequentialPlayer::GetNavigationDatasFromFile(std::string filename) +{ + std::vector returnValue = std::vector(); + std::vector fileContentLineByLine = GetFileContentLineByLine(filename); + int i; + if (m_HeaderRow) //file has a header row, so it has to be skipped when reading the NavigationDatas + { + i = 1; + } + else + { + i = 0; //file has no header row, so no need to skip the first row + } + for (i; (i < fileContentLineByLine.size()); i++) + { + returnValue.push_back(GetNavigationDataOutOfOneLine(fileContentLineByLine.at(i))); + } + + return returnValue; +} + +std::vector mitk::NavigationDataCSVSequentialPlayer::GetFileContentLineByLine(std::string filename) +{ + std::vector readData = std::vector(); + + //save old locale + char * oldLocale; + oldLocale = setlocale(LC_ALL, 0); + + //define own locale + std::locale C("C"); + setlocale(LC_ALL, "C"); + + //read file + std::ifstream file; + file.open(filename.c_str(), std::ios::in); + if (file.good()) + { + //read out file + file.seekg(0L, std::ios::beg); // move to begin of file + + int count = 0; + //int count2 = 0; + while (!file.eof()) + { + std::string buffer; + std::getline(file, buffer); // read out file line by line + + readData.push_back(buffer); + /* + //for Polhemus tracker: just take every 24th sample + if (count == 0) if (buffer.size() > 0) + { + //MITK_INFO << "read(" << count2 << "): " << buffer.substr(0,30); + //count2++; + readData.push_back(buffer); + } + */ + + count++; if (count == m_SampleCount) count = 0; + } + } + + file.close(); + + //switch back to old locale + setlocale(LC_ALL, oldLocale); + + return readData; +} + +mitk::NavigationData::Pointer mitk::NavigationDataCSVSequentialPlayer::GetNavigationDataOutOfOneLine(std::string line) +{ + mitk::NavigationData::Pointer returnValue = mitk::NavigationData::New(); + + QString myLine = QString(line.c_str()); + + QStringList myLineList = myLine.split(m_SeparatorSign); + + mitk::Point3D position; + mitk::Quaternion orientation; + bool valid = false; + double time; + + //this is for custom csv files. You have adapt the column numbers to correctly + //interpret your csv file. + if (m_Filetype = mitk::NavigationDataCSVSequentialPlayer::ManualLoggingCSV) + { + if (myLineList.size() < m_MinNumberOfColumns) + { + MITK_ERROR << "Error: cannot read line: only found " << myLineList.size() << " fields. Last field: " << myLineList.at(myLineList.size() - 1).toStdString(); + returnValue = GetEmptyNavigationData(); + return returnValue; + } + + valid = true; //if no valid flag is given: simply set to true + + + //############# Variant for the Aurora measurements ############### + //############# (CUSTOM .csv files from MITK) ############### + + position[0] = myLineList.at(3).toDouble(); + position[1] = myLineList.at(4).toDouble(); + position[2] = myLineList.at(5).toDouble(); + + orientation[0] = myLineList.at(6).toDouble(); //qx + orientation[1] = myLineList.at(7).toDouble(); //qy + orientation[2] = myLineList.at(8).toDouble(); //qz + orientation[3] = myLineList.at(9).toDouble(); //qr + + + //Variant for the polhemus measurements in August 2016 + //(.csv files from the polhemus software) + + //Important: due to the documentation, Polhemus uses + //a left handed coordinate system while MITK uses a + //right handed. A conversion is not included in this + //read in method yet, because this is not required + //for this special rotation evaliation (no matter + //if it turns 11.25 degree to left or right). For + //other usage this might be important to adapt! + + /* + position[0] = myLineList.at(m_XPos).toDouble(); + position[1] = myLineList.at(m_YPos).toDouble(); + position[2] = myLineList.at(m_ZPos).toDouble(); + */ + + if(!m_RightHanded) //MITK uses a right handed coordinate system, so the position needs to be converted + { + position[0] = position[0]*(-1); + } + + if (m_UseQuats) //Use Quaternions to construct the orientation of the NavigationData + { + orientation[0] = myLineList.at(m_Qx).toDouble(); //qx + orientation[1] = myLineList.at(m_Qy).toDouble(); //qy + orientation[2] = myLineList.at(m_Qz).toDouble(); //qz + orientation[3] = myLineList.at(m_Qr).toDouble(); //qr + } + else //Use the Euler Angles to construct the orientation of the NavigationData + { + double azimuthAngle; + double elevationAngle; + double rollAngle; + if(m_Azimuth < 0) //azimuth is not defined so set him to zero + { + azimuthAngle = 0; + } + else + { + azimuthAngle = myLineList.at(m_Azimuth).toDouble(); + } + if(m_Elevation < 0)// elevation is not defined so set him to zero + { + elevationAngle = 0; + } + else + { + elevationAngle = myLineList.at(m_Elevation).toDouble(); + } + if(m_Roll < 0) //roll is not defined so set him to zero + { + rollAngle = 0; + } + else + { + rollAngle = myLineList.at(m_Roll).toDouble(); + } + + + if (!m_EulersInRadiants) //the Euler Angles are in Degrees but MITK uses radiants so they need to be converted + { + azimuthAngle = azimuthAngle / 180 * M_PI; + elevationAngle = elevationAngle / 180 * M_PI; + rollAngle = rollAngle / 180 * M_PI; + } + vnl_quaternion eulerQuat(rollAngle, elevationAngle, azimuthAngle); + orientation = eulerQuat; + } + + + //Doesn't work... don't know how to interpret the + //Polhemus quaternions. They are seem to different + //different to other quaternions (NDI, Claron, etc.) + //http://www.mathepedia.de/Quaternionen.aspx + + /* + double qr = myLineList.at(7).toDouble(); + double qx = myLineList.at(8).toDouble(); + double qy = myLineList.at(9).toDouble(); + double qz = myLineList.at(10).toDouble(); + + vnl_quaternion newQuat(qx, qy, qz, qr); + + orientation = newQuat; + orientation.normalize();*/ + + /* + orientation[3] = qr; //qr + orientation[0] = qx; //qx + orientation[1] = qy; //qy + orientation[2] = qz; //qz + + orientation.normalize(); + */ + + + /* +// //Using Euler angles instead does work +// //azimuth: rotation about Z axis of reference frame +// double azimuthAngle = (myLineList.at(11).toDouble() / 180 * M_PI); +// //elevation: rotation about Y' axis (transformed Y axis of sonsor frame) +// double elevationAngle = (myLineList.at(12).toDouble() / 180 * M_PI); +// //roll: rotation about X axis of sensor frame +// double rollAngle = (myLineList.at(13).toDouble() / 180 * M_PI); +// vnl_quaternion eulerQuat(rollAngle, elevationAngle, azimuthAngle); +// orientation = eulerQuat; + */ + + /* + //code block for conversion from axis-angular representation + double rotationAngle = myLineList.at(7).toDouble(); + double rotationAxis[3]; + rotationAxis[0] = myLineList.at(8).toDouble(); + rotationAxis[1] = myLineList.at(9).toDouble(); + rotationAxis[2] = myLineList.at(10).toDouble(); + + double betragRotationAxis = sqrt(pow(rotationAxis[0], 2) + pow(rotationAxis[1], 2) + pow(rotationAxis[2], 2)); + rotationAngle /= betragRotationAxis; + rotationAxis[0] /= betragRotationAxis; + rotationAxis[1] /= betragRotationAxis; + rotationAxis[2] /= betragRotationAxis; + + + double qr = cos(rotationAngle/2); + double qx = rotationAxis[0] * sin(rotationAngle/2); + double qy = rotationAxis[1] * sin(rotationAngle/2); + double qz = rotationAxis[1] * sin(rotationAngle/2); + */ + + if(!m_RightHanded) //MITK uses a right handed coordinate system, so the orientation needs to be converted + { + //code block for conversion from left-handed to right-handed + mitk::Quaternion linksZuRechtsdrehend; + double rotationAngle = -M_PI; + double rotationAxis[3]; + rotationAxis[0] = 0; + rotationAxis[1] = 0; + rotationAxis[2] = 1; + + linksZuRechtsdrehend[3] = cos(rotationAngle / 2); + linksZuRechtsdrehend[0] = rotationAxis[0] * sin(rotationAngle / 2); + linksZuRechtsdrehend[1] = rotationAxis[1] * sin(rotationAngle / 2); + linksZuRechtsdrehend[2] = rotationAxis[2] * sin(rotationAngle / 2); + + orientation = orientation * linksZuRechtsdrehend; + } + + } + //this is for MITK csv files that have been recorded with the MITK + //navigation data recorder. You can also use the navigation data player + //class from the MITK-IGT module instead. + else if (m_Filetype = mitk::NavigationDataCSVSequentialPlayer::NavigationDataCSV) + { + if (myLineList.size() < 8) + { + MITK_ERROR << "Error: cannot read line: only found " << myLineList.size() << " fields. Last field: " << myLineList.at(myLineList.size() - 1).toStdString(); + returnValue = GetEmptyNavigationData(); + return returnValue; + } + + time = myLineList.at(2).toDouble(); + + if (myLineList.at(3).toStdString() == "1") valid = true; + + position[0] = myLineList.at(2).toDouble(); + position[1] = myLineList.at(3).toDouble(); + position[2] = myLineList.at(4).toDouble(); + + orientation[0] = myLineList.at(5).toDouble(); //qx + orientation[1] = myLineList.at(6).toDouble(); //qy + orientation[2] = myLineList.at(7).toDouble(); //qz + orientation[3] = myLineList.at(8).toDouble(); //qr + } + + //returnValue->SetTimeStamp(time); //DOES NOT WORK ANY MORE... CANNOT SET TIME TO itk::timestamp CLASS + returnValue->SetDataValid(valid); + returnValue->SetPosition(position); + returnValue->SetOrientation(orientation); + + return returnValue; +} +void mitk::NavigationDataCSVSequentialPlayer::SetOptions(bool rightHanded, char separatorSign, int sampleCount, bool headerRow, int xPos, int yPos, + int zPos, bool useQuats, int qx, int qy, int qz, int qr, int azimuth, int elevation, int roll, + bool eulerInRadiants, int minNumberOfColumns) +{ + m_RightHanded = rightHanded; + m_SeparatorSign = separatorSign; + m_SampleCount = sampleCount; + m_HeaderRow = headerRow; + m_XPos = xPos; + m_YPos = yPos; + m_ZPos = zPos; + m_UseQuats = useQuats; + m_Qx = qx; + m_Qy = qy; + m_Qz = qz; + m_Qr = qr; + m_Azimuth = azimuth; + m_Elevation = elevation; + m_Roll = roll; + m_EulersInRadiants = eulerInRadiants; + m_MinNumberOfColumns = minNumberOfColumns; +} diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkNavigationDataCSVSequentialPlayer.h b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkNavigationDataCSVSequentialPlayer.h new file mode 100644 index 0000000000..6976f05997 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkNavigationDataCSVSequentialPlayer.h @@ -0,0 +1,154 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + + +#ifndef MITKNavigationDataCSVSequentialPlayer_H_HEADER_INCLUDED_ +#define MITKNavigationDataCSVSequentialPlayer_H_HEADER_INCLUDED_ + +#include +#include "tinyxml.h" + + +namespace mitk +{ + + /**Documentation + * \brief This class is a NavigationDataPlayer which can play CSV formatted + * files in sequential order, which means it doesn't care about timestamps and just + * outputs the navigationdatas in their sequential order. + * + * It is thought to interpret custom csv files. To do so please adapt the column + * numbers of position and orientation in the internal method GetNavigationDataOutOfOneLine(). + * + * So far only one (the first) tool is read in as required for the hummel protocol measurements. + * + * This class can also interpret MITK style csv files (set filetype to NavigationDataCSV), but + * you can also use the MITK navigation data player class inside the MITK-IGT module which + * is newer and better maintained. + * + * \ingroup IGT + */ + class NavigationDataCSVSequentialPlayer + : public NavigationDataPlayerBase + { + public: + + mitkClassMacro(NavigationDataCSVSequentialPlayer, NavigationDataPlayerBase); + itkNewMacro(Self); + + /** + * \brief sets the file name and path (if XMLString is set, this is neglected) + */ + void SetFileName(const std::string& _FileName); + + /** + * @brief SetOptions sets the options for reading out the data out of the correct postions of the file. They need to be set before using the player + * @param rightHanded true if the used coordinate System is right handed, false if it is left handed + * @param seperatorSign symbol that is used to separate the values in the .csv file + * @param sampleCount every n-th sample in the file that should be used + * @param headerRow true if the .csv file has a header row otherwise false + * @param xPos number of the colum in the .csv file for the x-coordinates of the position + * @param yPos number of the colum in the .csv file for the y-coordinates of the position + * @param zPos number of the colum in the .csv file for the z-coordinates of the position + * @param useQuats true if Quaternions are used to construct the orientation, false if Euler Angles are used + * @param qx number of the column in the .csv file for the x component of the quaternion + * @param qy number of the column in the .csv file for the y component of the quaternion + * @param qz number of the column in the .csv file for the z component of the quaternion + * @param qr number of the column in the .csv file for the r component of the quaternion + * @param azimuth number of the colum in the .csv file for Azimuth (Euler Angles). Set < 0 if angle is not defined + * @param elevatino number of the colum in the .csv file for Elevation (Euler Angles) Set < 0 if angle is not defined + * @param roll number of the colum in the .csv file for Roll (Euler Angles) Set < 0 if angle is not defined + * @param eulerInRadiants true if the Euler Angles in the .csv file are in radiants, false if they are in degrees + * @param minNumberOfColumns + */ + void SetOptions(bool rightHanded, char seperatorSign, int sampleCount, bool headerRow, int xPos, int yPos, int zPos, bool useQuats, + int qx, int qy, int qz, int qr, int azimuth, int elevatino, int roll, bool eulerInRadiants, int minNumberOfColums); + + /** + * \brief returns the file name and path + */ + itkGetStringMacro(FileName); + + enum Filetype + { + NavigationDataCSV, //for csv files from the MITK navigation data player + ManualLoggingCSV //for custum csv files + }; + /** + * \brief Sets the file type. ManualLoggingCSV is default and is thought for your + * custom csv files. You can also set it to NavigationDataCSV, then this + * player interprets MITK style csv files. + */ + itkSetMacro(Filetype, Filetype); + + /** + * \return Returns true if the player reached the end of the file. + */ + bool IsAtEnd(); + + /** + * \brief Used for pipeline update just to tell the pipeline + * that we always have to update + */ + virtual void UpdateOutputInformation(); + + int GetNumberOfSnapshots(); + + protected: + NavigationDataCSVSequentialPlayer(); + virtual ~NavigationDataCSVSequentialPlayer(); + + /// + /// do the work here + /// + virtual void GenerateData(); + + std::string m_FileName; + + int m_CurrentPos; + Filetype m_Filetype; + + //member for the navigation datas which were read (only one output is supported at the moment) + std::vector m_NavigationDatas; + + std::vector GetNavigationDatasFromFile(std::string filename); + std::vector GetFileContentLineByLine(std::string filename); + mitk::NavigationData::Pointer GetNavigationDataOutOfOneLine(std::string line); + + void FillOutputEmpty(int number); + mitk::NavigationData::Pointer GetEmptyNavigationData(); + + bool m_RightHanded; //true if the used coordinate System is right handed, false if it is left handed + char m_SeparatorSign; //symbol that is used to separate the values in the .csv file + int m_SampleCount; //every n-th sample in the file that should be used + bool m_HeaderRow; //true if the .csv file has a header row otherwise false + int m_XPos; //number of the colum in the .csv file for the x-coordinates of the position + int m_YPos; //number of the colum in the .csv file for the y-coordinates of the position + int m_ZPos; //number of the colum in the .csv file for the z-coordinates of the position + bool m_UseQuats; //true if Quaternions are used to construct the orientation, false if Euler Angles are used + int m_Qx; //number of the column in the .csv file for the x component of the quaternion + int m_Qy; //number of the column in the .csv file for the y component of the quaternion + int m_Qz; //number of the column in the .csv file for the z component of the quaternion + int m_Qr; //number of the column in the .csv file for the r component of the quaternion + int m_Azimuth; //number of the colum in the .csv file for Azimuth (Euler Angles) + int m_Elevation; //number of the colum in the .csv file for Elevation (Euler Angles) + int m_Roll; //number of the colum in the .csv file for Roll (Euler Angles) + bool m_EulersInRadiants; // true if the Euler Angles in the .csv file are in radiants, false if they are in degrees + int m_MinNumberOfColumns; //minimal number of columns the file has to have (e.g. if you read data from column 8 the file has to have at least 8 columns) + }; +} // namespace mitk + +#endif /* MITKNavigationDataCSVSequentialPlayer_H_HEADER_INCLUDED_ */ diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkPluginActivator.cpp b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkPluginActivator.cpp new file mode 100644 index 0000000000..daf9692487 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkPluginActivator.cpp @@ -0,0 +1,39 @@ +/*========================================================================= + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +=========================================================================*/ + +#include "mitkPluginActivator.h" + +#include + +#include "QmitkIGTTrackingSemiAutomaticMeasurementView.h" +#include "QmitkIGTTrackingDataEvaluationView.h" + +namespace mitk { + void PluginActivator::start(ctkPluginContext* context) + { + BERRY_REGISTER_EXTENSION_CLASS(QmitkIGTTrackingSemiAutomaticMeasurementView, context) + BERRY_REGISTER_EXTENSION_CLASS(QmitkIGTTrackingDataEvaluationView, context) + } + + void PluginActivator::stop(ctkPluginContext* context) + { + Q_UNUSED(context) + } +} + +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +Q_EXPORT_PLUGIN2(org_mitk_gui_qt_igttrackingsemiautomaticmeasurement, mitk::PluginActivator) +#endif diff --git a/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkPluginActivator.h b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkPluginActivator.h new file mode 100644 index 0000000000..08f307e628 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.igt.app.hummelprotocolmeasurements/src/internal/mitkPluginActivator.h @@ -0,0 +1,43 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + + +#ifndef MITKPLUGINACTIVATOR_H +#define MITKPLUGINACTIVATOR_H + +#include + +namespace mitk { + +class PluginActivator : + public QObject, public ctkPluginActivator +{ + Q_OBJECT + #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + Q_PLUGIN_METADATA(IID "org_mitk_gui_qt_igtapphummelprotocolmeasurements") + #endif + Q_INTERFACES(ctkPluginActivator) + +public: + + void start(ctkPluginContext* context); + void stop(ctkPluginContext* context); + +}; // PluginActivator + +} + +#endif // MITKPLUGINACTIVATOR_H