diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 46e770b..15bacf1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,7 @@ set(CXX_FILES backgroundDialogQT.cxx cameraDialogQT.cxx CSVloaderQT.cxx + timeSeriesLoaderQT.cxx customizeColorMapByDirectionDialogQT.cxx gradientArrow.cxx gradientWidgetQT.cxx @@ -38,6 +39,7 @@ set(UI_FILES backgroundDialogQT.ui cameraDialogQT.ui CSVloaderQT.ui + timeSeriesLoaderQT.ui customizeColorMapByDirectionDialogQT.ui ShapePopulationMainWindowQT.ui ShapePopulationQT.ui @@ -47,6 +49,7 @@ set(QT_WRAP backgroundDialogQT.h cameraDialogQT.h CSVloaderQT.h + timeSeriesLoaderQT.h customizeColorMapByDirectionDialogQT.h gradientWidgetQT.h ShapePopulationGradientWidgetQT.h @@ -90,6 +93,8 @@ if(ShapePopulationViewer_BUILD_SLICER_EXTENSION) # Current_{source,binary} and Slicer_{Libs,Base} already included set(MODULE_INCLUDE_DIRECTORIES + ${vtkSlicerSRepModuleMRML_INCLUDE_DIRS} + ${vtkSlicerSRepModuleLogic_INCLUDE_DIRS} ) set(MODULE_SRCS qSlicer${MODULE_NAME}LayoutViewFactory.cxx @@ -109,6 +114,8 @@ if(ShapePopulationViewer_BUILD_SLICER_EXTENSION) ) set(MODULE_TARGET_LIBRARIES ${LOCAL_PROJECT_NAME}Widget + vtkSlicerSRepModuleMRML + vtkSlicerSRepModuleLogic ) set(MODULE_RESOURCES ) diff --git a/src/CSVloaderQT.cxx b/src/CSVloaderQT.cxx index db05d05..36938f2 100644 --- a/src/CSVloaderQT.cxx +++ b/src/CSVloaderQT.cxx @@ -55,13 +55,13 @@ void CSVloaderQT::on_buttonBox_accepted() for (int i = 0; i < fileList.size(); i++) { QString QFilePath = fileList[i].absoluteFilePath(); - if (!QFilePath.endsWith(".vtk") && !QFilePath.endsWith(".vtp")) + if (!QFilePath.endsWith(".vtk") && !QFilePath.endsWith(".vtp") && !QFilePath.endsWith(".xml") && !QFilePath.endsWith(".srep.json")) { fileList.removeAt(i); i--; std::ostringstream strs; strs << QFilePath.toStdString() << std::endl - << "This is not a vtk/vtp file."<< std::endl; + << "This is not a vtk/vtp/xml/srep.json file."<< std::endl; QMessageBox::critical(this,"Wrong file format",QString(strs.str().c_str()), QMessageBox::Ok); } else if(!fileList[i].exists()) diff --git a/src/ShapePopulationBase.cxx b/src/ShapePopulationBase.cxx index 2a27047..3b9dc17 100644 --- a/src/ShapePopulationBase.cxx +++ b/src/ShapePopulationBase.cxx @@ -257,6 +257,95 @@ vtkRenderWindow* ShapePopulationBase::CreateNewWindow(ShapePopulationData* a_mes return renderWindow; } +void ShapePopulationBase::UpdateWindows() +{ + unsigned int i; + for (i = 0; i < m_meshList.size(); i++) + { + //MAPPER + vtkNew mapper; + mapper->SetInputData(m_meshList[i]->GetPolyData()); + + //ACTOR + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + /* VECTORS */ + //Arrow + vtkNew arrow; + m_glyphList[i]->SetSourceConnection(arrow->GetOutputPort()); + m_glyphList[i]->SetInputData(m_meshList[i]->GetPolyData()); + m_glyphList[i]->ScalingOn(); + m_glyphList[i]->OrientOn(); + m_glyphList[i]->ClampingOff(); + m_glyphList[i]->SetColorModeToColorByVector(); + m_glyphList[i]->SetScaleModeToScaleByVector(); + m_glyphList[i]->SetVectorModeToUseVector(); + m_glyphList[i]->Update(); + + //Mapper & Actor + vtkNew glyphMapper; + glyphMapper->SetInputData(m_glyphList[i]->GetOutput()); + vtkNew glyphActor; + glyphActor->SetMapper(glyphMapper); + + /* END VECTORS */ + + //RENDERER + auto renderer = m_windowsList[i]->GetRenderers()->GetFirstRenderer(); + renderer->RemoveAllViewProps(); + + renderer->AddActor(actor); + renderer->AddActor(glyphActor); + renderer->SetActiveCamera(m_headcam); //set the active camera for this renderer to main camera + renderer->ResetCamera(); + //renderer->SetUseDepthPeeling(true);/*test opacity*/ + +// //INTERACTOR +// vtkSmartPointer interactor = vtkSmartPointer::New(); +// renderWindow->SetInteractor(interactor); + + //ANNOTATIONS (file name) + vtkSmartPointer fileName = vtkSmartPointer::New(); + fileName->SetLinearFontScaleFactor( 2 ); + fileName->SetNonlinearFontScaleFactor( 1 ); + fileName->SetMaximumFontSize( 15 ); + fileName->SetText(2,m_meshList[i]->GetFileName().c_str()); + fileName->GetTextProperty()->SetColor(m_labelColor); + renderer->AddViewProp(fileName); + + //ANNOTATIONS (attribute name) + vtkNew attributeName; + attributeName->SetLinearFontScaleFactor(2); + attributeName->SetNonlinearFontScaleFactor(1); + attributeName->SetMaximumFontSize(15); + attributeName->SetText(0," "); + attributeName->GetTextProperty()->SetColor(m_labelColor); + renderer->AddViewProp(attributeName); + + // SCALAR BAR + vtkNew scalarBar; + scalarBar->SetLookupTable(mapper->GetLookupTable()); + // scalarBar->SetTitle("Title"); + scalarBar->SetNumberOfLabels(5); + scalarBar->SetMaximumWidthInPixels(60); + + vtkNew LabelProperty; + LabelProperty->SetFontSize(12); + LabelProperty->SetColor(m_labelColor); + + scalarBar->SetLabelTextProperty(LabelProperty); + scalarBar->SetTitleTextProperty(LabelProperty); + renderer->AddActor2D(scalarBar); + + //DISPLAY + if (m_displayMeshName == false) fileName->SetVisibility(0); + if (m_displayAttribute == false) attributeName->SetVisibility(0); + if (m_displayColorbar == false) scalarBar->SetVisibility(0); + } + RenderAll(); +} + // * ///////////////////////////////////////////////////////////////////////////////////////////// * // // * SELECTION * // // * ///////////////////////////////////////////////////////////////////////////////////////////// * // diff --git a/src/ShapePopulationBase.h b/src/ShapePopulationBase.h index 2b51cbc..2b5fe5a 100644 --- a/src/ShapePopulationBase.h +++ b/src/ShapePopulationBase.h @@ -89,6 +89,7 @@ class ShapePopulationBase vtkRenderWindow* CreateNewWindow(std::string a_filePath, bool testing = false); vtkRenderWindow* CreateNewWindow(vtkPolyData* a_popyData, std::string a_filePath, bool testing = false); vtkRenderWindow* CreateNewWindow(ShapePopulationData* a_mesh, bool testing = false); + void UpdateWindows(); //SELECTION unsigned int getSelectedIndex(vtkSmartPointer a_selectedWindow); diff --git a/src/ShapePopulationData.cxx b/src/ShapePopulationData.cxx index e63f56e..f0ba870 100644 --- a/src/ShapePopulationData.cxx +++ b/src/ShapePopulationData.cxx @@ -1,5 +1,41 @@ #include "ShapePopulationData.h" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +class ScopedRemove { +public: + ScopedRemove(vtkMRMLNode* toRemove, vtkMRMLScene* scene) + : m_ToRemove(toRemove) + , m_Scene(scene) + {} + + void cancel() { + m_ToRemove = nullptr; + m_Scene = nullptr; + } + + ~ScopedRemove() { + if (m_ToRemove && m_Scene) { + m_Scene->RemoveNode(m_ToRemove); + m_ToRemove = nullptr; + m_Scene = nullptr; + } + } +private: + vtkMRMLNode* m_ToRemove; + vtkMRMLScene* m_Scene; +}; + +} + static bool endswith(std::string file, std::string ext) { int epos = file.length() - ext.length(); @@ -39,11 +75,39 @@ vtkSmartPointer ShapePopulationData::ReadPolyData(std::string a_fil vtkSmartPointer ShapePopulationData::ReadMesh(std::string a_filePath) { - vtkSmartPointer polyData = ReadPolyData(a_filePath); - if(polyData == NULL) return 0; + if (endswith(a_filePath, ".vtp") || endswith(a_filePath, ".vtk")) + { + vtkSmartPointer polyData = ReadPolyData(a_filePath); + if(polyData == NULL) return 0; + + ReadMesh(polyData, a_filePath); + } + else if (endswith(a_filePath, ".xml")) + { + vtkNew parser; + parser->SetFileName(a_filePath.c_str()); + if( parser->Parse() != 1) return 0; - ReadMesh(polyData, a_filePath); + auto* root = parser->GetRootElement(); + vtkSmartPointer upPD = ReadPolyData(root->FindNestedElementWithName("upSpoke")->GetCharacterData()); + vtkSmartPointer downPD = ReadPolyData(root->FindNestedElementWithName("downSpoke")->GetCharacterData()); + vtkSmartPointer crestPD = ReadPolyData(root->FindNestedElementWithName("crestSpoke")->GetCharacterData()); + if(upPD == NULL || downPD == NULL || crestPD == NULL) return 0; + ReadSRep(upPD, downPD, crestPD, a_filePath); + } + else if (endswith(a_filePath, ".srep.json")) + { + ReadSRep(a_filePath); + } + else if(endswith(a_filePath, ".fcsv")) + { + ReadFiducial(a_filePath); + } + else + { + return 0; + } return m_PolyData; } @@ -70,6 +134,7 @@ void ShapePopulationData::ReadMesh(vtkPolyData* polyData, const std::string& a_f //Update the class members m_PolyData = normalGenerator->GetOutput(); + m_AttributeList.clear(); int numAttributes = m_PolyData->GetPointData()->GetNumberOfArrays(); for (int j = 0; j < numAttributes; j++) { @@ -96,4 +161,288 @@ void ShapePopulationData::ReadMesh(vtkPolyData* polyData, const std::string& a_f std::sort(m_AttributeList.begin(),m_AttributeList.end()); } +void ShapePopulationData::ReadSRep(vtkPolyData* upPD, vtkPolyData* downPD, vtkPolyData* crestPD, const std::string& a_filePath) +{ + size_t found = a_filePath.find_last_of("/\\"); + if (found != std::string::npos) + { + m_FileDir = a_filePath.substr(0,found); + m_FileName = a_filePath.substr(found+1); + } + else + { + m_FileName = a_filePath; + } + + // Create spoke geometries and merge poly data + vtkNew append; + append->AddInputData(ExtractSpokes(upPD, 0)); + append->AddInputData(ExtractSpokes(downPD, 4)); + append->AddInputData(ExtractSpokes(crestPD, 2)); + append->AddInputData(AttachCellType(upPD, 1)); + append->AddInputData(AttachCellType(crestPD, 3)); + append->Update(); + + // Update the class members + m_PolyData = append->GetOutput(); + + int numAttributes = m_PolyData->GetPointData()->GetNumberOfArrays(); + for (int j = 0; j < numAttributes; j++) + { + int dim = m_PolyData->GetPointData()->GetArray(j)->GetNumberOfComponents(); + const char * AttributeName = m_PolyData->GetPointData()->GetArrayName(j); + + if (dim == 1) + { + std::string AttributeString = AttributeName; + m_AttributeList.push_back(AttributeString); + } + } + std::sort(m_AttributeList.begin(),m_AttributeList.end()); +} + +void ShapePopulationData::ReadFiducial(const std::string& a_filePath) +{ + size_t found = a_filePath.find_last_of("/\\"); + if (found != std::string::npos) + { + m_FileDir = a_filePath.substr(0,found); + m_FileName = a_filePath.substr(found+1); + } + else + { + m_FileName = a_filePath; + } + + // Read file and record fiducial positions + QFile inputFile(a_filePath.c_str()); + if (!inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) + { + return; + } + QTextStream inputStream(&inputFile); + std::vector> sphereCenters; + while(!inputStream.atEnd()) + { + auto line = inputStream.readLine(); + if (line.startsWith("vtkMRMLMarkupsFiducialNode")) + { + QStringList list = line.split(','); + std::vector center; + center.push_back(list[1].toDouble()); + center.push_back(list[2].toDouble()); + center.push_back(list[3].toDouble()); + sphereCenters.push_back(center); + } + } + inputFile.close(); + + // Check closest distance between fiducials to decide sphere radius + auto closestDistance = VTK_DOUBLE_MAX; + for (int i = 0; i < (int)sphereCenters.size() - 1; ++i) + { + auto centerI = sphereCenters[i]; + for (int j = i + 1; j < (int)sphereCenters.size(); j++) + { + auto centerJ = sphereCenters[j]; + double diff[3] = {centerJ[0] - centerI[0], + centerJ[1] - centerI[1], + centerJ[2] - centerI[2]}; + double distance = std::sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2]); + if (distance < closestDistance) + { + closestDistance = distance; + } + } + } + + // Create geometry for visualization + vtkNew append; + double radius = closestDistance * 0.2; + for (int i = 0; i < (int)sphereCenters.size(); ++i) + { + vtkNew sphere; + sphere->SetRadius(radius); + sphere->SetCenter(sphereCenters[i][0], sphereCenters[i][1], sphereCenters[i][2]); + sphere->Update(); + auto spherePD = sphere->GetOutput(); + vtkNew intArray; + intArray->SetNumberOfComponents(1); + intArray->SetNumberOfValues(spherePD->GetNumberOfPoints()); + intArray->SetName("Fiducial Index"); + for (int j = 0; j < spherePD->GetNumberOfPoints(); ++j) + { + intArray->SetValue(j, i); + } + spherePD->GetPointData()->SetActiveScalars("Fiducial Index"); + spherePD->GetPointData()->SetScalars(intArray); + append->AddInputData(spherePD); + } + append->Update(); + + m_PolyData = append->GetOutput(); + + int numAttributes = m_PolyData->GetPointData()->GetNumberOfArrays(); + for (int j = 0; j < numAttributes; j++) + { + int dim = m_PolyData->GetPointData()->GetArray(j)->GetNumberOfComponents(); + const char * AttributeName = m_PolyData->GetPointData()->GetArrayName(j); + + if (dim == 1) + { + std::string AttributeString = AttributeName; + m_AttributeList.push_back(AttributeString); + } + } + std::sort(m_AttributeList.begin(),m_AttributeList.end()); +} + +vtkSmartPointer ShapePopulationData::ExtractSpokes(vtkPolyData* polyData, int cellType) +{ + int nPoints = polyData->GetNumberOfPoints(); + auto* pointData = polyData->GetPointData(); + auto* arr_length = pointData->GetArray("spokeLength"); + auto* arr_dirs = pointData->GetArray("spokeDirection"); + + vtkNew spokePoints; + vtkNew spokeLines; + vtkNew typeArray; + typeArray->SetName("cellType"); + typeArray->SetNumberOfComponents(1); + + for (int i = 0; i < nPoints; ++i) + { + auto* pt0 = polyData->GetPoint(i); + auto spoke_length = arr_length->GetTuple1(i); + auto* dir = arr_dirs->GetTuple3(i); + double pt1[] = {pt0[0] + spoke_length * dir[0], + pt0[1] + spoke_length * dir[1], + pt0[2] + spoke_length * dir[2]}; + int id0 = spokePoints->InsertNextPoint(pt0); + int id1 = spokePoints->InsertNextPoint(pt1); + + vtkNew arrow; + arrow->GetPointIds()->SetId(0, id0); + arrow->GetPointIds()->SetId(1, id1); + spokeLines->InsertNextCell(arrow); + typeArray->InsertNextValue(cellType); + typeArray->InsertNextValue(cellType); + } + + vtkNew spokePD; + spokePD->SetPoints(spokePoints); + spokePD->SetLines(spokeLines); + spokePD->GetPointData()->SetActiveScalars("cellType"); + spokePD->GetPointData()->SetScalars(typeArray); + return spokePD; +} + +vtkSmartPointer ShapePopulationData::AttachCellType(vtkPolyData *polyData, int cellType) +{ + vtkNew outputType; + outputType->SetName("cellType"); + outputType->SetNumberOfComponents(1); + for (int i = 0; i < polyData->GetNumberOfPoints(); ++i) + { + outputType->InsertNextValue(cellType); + } + + polyData->GetPointData()->SetActiveScalars("cellType"); + polyData->GetPointData()->SetScalars(outputType); + return polyData; +} + +void ShapePopulationData::ReadSRep(const std::string& a_filePath) +{ + size_t found = a_filePath.find_last_of("/\\"); + if (found != std::string::npos) + { + m_FileDir = a_filePath.substr(0,found); + m_FileName = a_filePath.substr(found+1); + } + else + { + m_FileName = a_filePath; + } + + vtkMRMLScene* mrmlScene = qSlicerCoreApplication::application()->applicationLogic()->GetMRMLScene(); + if (!mrmlScene) { + std::cerr << "ShapePopulationData::ReadSRep Unable to get application MRML scene!" << std::endl; + return; + } + vtkMRMLSRepStorageNode* storageNode = vtkMRMLSRepStorageNode::SafeDownCast(mrmlScene->AddNewNodeByClass("vtkMRMLSRepStorageNode")); + if (!storageNode) { + std::cerr << "ShapePopulationData::ReadSRep Unable to get storageNode!" << std::endl; + return; + } + ScopedRemove removeStorage(storageNode, mrmlScene); + storageNode->SetFileName(a_filePath.c_str()); + + vtkMRMLSRepNode* srepNode = storageNode->CreateSRepNode("srep"); + if (!srepNode) { + std::cerr << "ShapePopulationData::ReadSRep Unable to get srepNode!" << std::endl; + return; + } + ScopedRemove removeSRep(srepNode, mrmlScene); + /// removing the srep node will remove its associated storage node + removeStorage.cancel(); + + auto spokeExportProps = vtkSmartPointer::New(); + spokeExportProps->SetIncludeSkeletalSheet(false); + spokeExportProps->SetIncludeCrestCurve(false); + spokeExportProps->SetIncludeSkeletonToCrestConnection(false); + spokeExportProps->SetIncludeSpine(false); + spokeExportProps->SetPointTypeArrayName("cellType"); + spokeExportProps->SetLineTypeArrayName("cellType"); + vtkNew spokeTypeArray; + spokeTypeArray->SetNumberOfTuples(vtkSRepExportPolyDataProperties::NumberOfTypes); + spokeTypeArray->SetValue(vtkSRepExportPolyDataProperties::UpBoundaryPointType, 0); + spokeTypeArray->SetValue(vtkSRepExportPolyDataProperties::UpSkeletalPointType, 0); + spokeTypeArray->SetValue(vtkSRepExportPolyDataProperties::DownBoundaryPointType, 4); + spokeTypeArray->SetValue(vtkSRepExportPolyDataProperties::DownSkeletalPointType, 4); + spokeTypeArray->SetValue(vtkSRepExportPolyDataProperties::CrestBoundaryPointType, 2); + spokeTypeArray->SetValue(vtkSRepExportPolyDataProperties::CrestSkeletalPointType, 2); + spokeTypeArray->SetValue(vtkSRepExportPolyDataProperties::UpSpokeLineType, 0); + spokeTypeArray->SetValue(vtkSRepExportPolyDataProperties::DownSpokeLineType, 4); + spokeTypeArray->SetValue(vtkSRepExportPolyDataProperties::CrestSpokeLineType, 2); + spokeExportProps->SetSRepDataArray(spokeTypeArray); + auto sheetExportProps = vtkSmartPointer::New(); + sheetExportProps->SetIncludeUpSpokes(false); + sheetExportProps->SetIncludeDownSpokes(false); + sheetExportProps->SetIncludeCrestSpokes(false); + sheetExportProps->SetIncludeSpine(false); + sheetExportProps->SetPointTypeArrayName("cellType"); + sheetExportProps->SetLineTypeArrayName("cellType"); + vtkNew typeArray; + typeArray->SetNumberOfTuples(vtkSRepExportPolyDataProperties::NumberOfTypes); + typeArray->SetValue(vtkSRepExportPolyDataProperties::UpSkeletalPointType, 1); + typeArray->SetValue(vtkSRepExportPolyDataProperties::DownSkeletalPointType, 1); + typeArray->SetValue(vtkSRepExportPolyDataProperties::CrestSkeletalPointType, 3); + typeArray->SetValue(vtkSRepExportPolyDataProperties::CrestCurveLineType, 3); + typeArray->SetValue(vtkSRepExportPolyDataProperties::SkeletalSheetLineType, 1); + sheetExportProps->SetSRepDataArray(typeArray); + + // making two different polydatas with overlapping points of different colors + auto logic = vtkSmartPointer::New(); + vtkNew append; + append->AddInputData(logic->SmartExportSRepToPolyData(*srepNode->GetSRep(), *spokeExportProps)); + append->AddInputData(logic->SmartExportSRepToPolyData(*srepNode->GetSRep(), *sheetExportProps)); + append->Update(); + m_PolyData = append->GetOutput(); + + m_PolyData->GetPointData()->SetActiveScalars("cellType"); + int numAttributes = m_PolyData->GetPointData()->GetNumberOfArrays(); + for (int j = 0; j < numAttributes; j++) + { + int dim = m_PolyData->GetPointData()->GetArray(j)->GetNumberOfComponents(); + const char * AttributeName = m_PolyData->GetPointData()->GetArrayName(j); + + if (dim == 1) + { + std::string AttributeString = AttributeName; + m_AttributeList.push_back(AttributeString); + } + } + std::sort(m_AttributeList.begin(),m_AttributeList.end()); +} diff --git a/src/ShapePopulationData.h b/src/ShapePopulationData.h index 15400b8..f6eb6bf 100644 --- a/src/ShapePopulationData.h +++ b/src/ShapePopulationData.h @@ -3,12 +3,21 @@ #include +#include +#include +#include +#include +#include #include +#include +#include #include +#include #include +#include +#include +#include #include -#include -#include #include "vtkPVPostFilter.h" @@ -17,6 +26,10 @@ #include #include +#include +#include +#include + class ShapePopulationData { public : @@ -26,6 +39,8 @@ class ShapePopulationData vtkSmartPointer ReadMesh(std::string a_filePath); void ReadMesh(vtkPolyData* polyData, const std::string& a_filePath); + void ReadSRep(vtkPolyData* upPD, vtkPolyData* downPD, vtkPolyData* crestPD, const std::string& a_filePath); + void ReadFiducial(const std::string& a_filePath); vtkSmartPointer ReadPolyData(std::string a_filePath); vtkSmartPointer GetPolyData() {return m_PolyData;} @@ -35,10 +50,15 @@ class ShapePopulationData protected : + static vtkSmartPointer ExtractSpokes(vtkPolyData* polyData, int cellType); + static vtkSmartPointer AttachCellType(vtkPolyData* polyData, int cellType); + vtkSmartPointer m_PolyData; std::string m_FileName; std::string m_FileDir; std::vector m_AttributeList; +private: + void ReadSRep(const std::string& a_filePath); }; diff --git a/src/ShapePopulationMainWindowQT.cxx b/src/ShapePopulationMainWindowQT.cxx index 19c04e0..836ace3 100644 --- a/src/ShapePopulationMainWindowQT.cxx +++ b/src/ShapePopulationMainWindowQT.cxx @@ -9,6 +9,7 @@ ShapePopulationMainWindowQT::ShapePopulationMainWindowQT() this->menuFile->addAction(this->shapePopulation()->actionOpen_Directory); this->menuFile->addAction(this->shapePopulation()->actionOpen_VTK_Files); this->menuFile->addAction(this->shapePopulation()->actionLoad_CSV); + this->menuFile->addAction(this->shapePopulation()->actionOpen_SRep_Files); this->menuFile->addSeparator(); this->menuFile->addMenu(this->menuExport); this->menuFile->addSeparator(); diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index ba90330..05728f6 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -20,9 +20,11 @@ ShapePopulationQT::ShapePopulationQT(QWidget* parent) : QWidget(parent) m_lastDirectory = ""; m_colormapDirectory = ""; m_exportDirectory = ""; + m_timeSeries.clear(); m_cameraDialog = new cameraDialogQT(this); m_backgroundDialog = new backgroundDialogQT(this); m_CSVloaderDialog = new CSVloaderQT(this); + m_timeSeriesLoaderDialog = new timeSeriesLoaderQT(this); m_customizeColorMapByDirectionDialog = new customizeColorMapByDirectionDialogQT(this); m_exportActions = new QActionGroup(this); m_exportActions->setExclusive(false); @@ -84,8 +86,12 @@ ShapePopulationQT::ShapePopulationQT(QWidget* parent) : QWidget(parent) //Menu signals connect(actionOpen_Directory,SIGNAL(triggered()),this,SLOT(openDirectory())); connect(actionOpen_VTK_Files,SIGNAL(triggered()),this,SLOT(openFiles())); + connect(actionOpen_SRep_Files,SIGNAL(triggered()),this,SLOT(openSRepFiles())); + connect(actionOpen_Fiducial_Files,SIGNAL(triggered()),this,SLOT(openFiducialFiles())); connect(actionLoad_CSV,SIGNAL(triggered()),this,SLOT(loadCSV())); + connect(actionLoad_Time_Series,SIGNAL(triggered()),this,SLOT(loadTimeSeries())); connect(m_CSVloaderDialog,SIGNAL(sig_itemsSelected(QFileInfoList)),this,SLOT(slot_itemsSelected(QFileInfoList))); + connect(m_timeSeriesLoaderDialog,SIGNAL(sig_timeSeriesSelected(QList)),this,SLOT(slot_timeSeriesSelected(QList))); connect(actionDelete,SIGNAL(triggered()),this,SLOT(deleteSelection())); connect(actionDelete_All,SIGNAL(triggered()),this,SLOT(deleteAll())); connect(actionCameraConfig,SIGNAL(triggered()),this,SLOT(showCameraConfigWindow())); @@ -166,6 +172,7 @@ ShapePopulationQT::~ShapePopulationQT() delete m_cameraDialog; delete m_backgroundDialog; delete m_CSVloaderDialog; + delete m_timeSeriesLoaderDialog; delete m_customizeColorMapByDirectionDialog; } @@ -202,6 +209,24 @@ void ShapePopulationQT::loadVTKFilesCLP(QFileInfoList a_fileList) this->CreateWidgets(a_fileList); } +void ShapePopulationQT::loadSRepFilesCLP(QFileInfoList a_fileList) +{ + this->CreateWidgets(a_fileList); + + //Load color map for S-Reps + QString filename = ":/resources/sRepColorMap.spvcm"; + QFileInfo file(filename); + m_colormapDirectory= file.path(); + gradientWidget_VISU->loadColorPointList(filename, &m_usedColorBar->colorPointList); + this->updateColorbar_QT(); +} + +void ShapePopulationQT::loadFiducialFilesCLP(QFileInfoList a_fileList) +{ + + this->CreateWidgets(a_fileList); +} + void ShapePopulationQT::loadModel(vtkMRMLModelNode* modelNode) { #ifdef ShapePopulationViewer_BUILD_SLICER_EXTENSION @@ -273,6 +298,20 @@ void ShapePopulationQT::loadCSVFileCLP(QFileInfo file) m_CSVloaderDialog->displayTable(table,file.absoluteDir()); } +void ShapePopulationQT::loadTimeSeriesCLP(QFileInfo file) +{ + //Read .CSV with VTK + vtkSmartPointer CSVreader = vtkSmartPointer::New(); + CSVreader->SetFieldDelimiterCharacters(","); + CSVreader->SetFileName(file.absoluteFilePath().toStdString().c_str()); + CSVreader->SetHaveHeaders(true); + CSVreader->Update(); + vtkTable* table = CSVreader->GetOutput(); + + //Display in CSVloaderQT + m_timeSeriesLoaderDialog->displayTable(table,file.absoluteDir()); +} + void ShapePopulationQT::loadVTKDirCLP(QDir vtkDir) { //Add to fileList @@ -345,6 +384,58 @@ void ShapePopulationQT::openFiles() this->CreateWidgets(fileInfos); } +void ShapePopulationQT::openSRepFiles() +{ + QStringList stringList = QFileDialog::getOpenFileNames(this,tr("Open Files"),m_lastDirectory,"S-Rep Files (*.srep.json *.xml)"); + if(stringList.isEmpty()) + { + return ; + } + + m_lastDirectory=QFileInfo(stringList.at(0)).path(); + + //Add to fileList + QFileInfoList fileInfos; + + //Control the files format + foreach(const QString& filePath, stringList) + { + fileInfos.append(QFileInfo(filePath)); + } + + //Display widgets + this->CreateWidgets(fileInfos); + + //Load color map for S-Reps + QString filename = ":/resources/sRepColorMap.spvcm"; + QFileInfo file(filename); + m_colormapDirectory= file.path(); + gradientWidget_VISU->loadColorPointList(filename, &m_usedColorBar->colorPointList); + this->updateColorbar_QT(); +} + +void ShapePopulationQT::openFiducialFiles() +{ + QStringList stringList = QFileDialog::getOpenFileNames(this,tr("Open Files"),m_lastDirectory,"Fiducial Files (*.fcsv)"); + if(stringList.isEmpty()) + { + return ; + } + + m_lastDirectory=QFileInfo(stringList.at(0)).path(); + + //Add to fileList + QFileInfoList fileInfos; + + //Control the files format + foreach(const QString& filePath, stringList) + { + fileInfos.append(QFileInfo(filePath)); + } + + //Display widgets + this->CreateWidgets(fileInfos); +} void ShapePopulationQT::loadCSV() { @@ -369,6 +460,27 @@ void ShapePopulationQT::loadCSV() } +void ShapePopulationQT::loadTimeSeries() +{ + // get directory + QString filename = QFileDialog::getOpenFileName(this,tr("Open .csv file"),m_lastDirectory,"CSV file (*.csv)"); + if(filename.isEmpty() || !QFileInfo(filename).exists()) return; + + //MAJ lastDirectory + QFileInfo file(filename); + m_lastDirectory= file.path(); + + //Read .CSV with VTK + vtkSmartPointer CSVreader = vtkSmartPointer::New(); + CSVreader->SetFieldDelimiterCharacters(","); + CSVreader->SetFileName(filename.toStdString().c_str()); + CSVreader->SetHaveHeaders(true); + CSVreader->Update(); + vtkTable* table = CSVreader->GetOutput(); + + //Display in timeSeriesLoaderQT + m_timeSeriesLoaderDialog->displayTable(table,file.absoluteDir()); +} void ShapePopulationQT::slot_itemsSelected(QFileInfoList fileList) { @@ -376,6 +488,71 @@ void ShapePopulationQT::slot_itemsSelected(QFileInfoList fileList) if(!fileList.isEmpty()) this->CreateWidgets(fileList); } +void ShapePopulationQT::slot_timeSeriesSelected(QList timeSeries) +{ + // Display widgets + if(!timeSeries.isEmpty()) + { + if(!m_timeSeries.isEmpty()) + { + for(auto timeSerie : timeSeries) + { + if (m_timeSeries.at(0).size() != timeSerie.size()) + { + QMessageBox::critical(this,"Length of time series does not match.", timeSerie.at(0).absoluteFilePath(), QMessageBox::Ok); + return; + } + } + } + + for(auto timeSerie : timeSeries) + { + m_timeSeries.append(timeSerie); + if (m_timeSeries.at(0).size() != timeSerie.size()) + { + QMessageBox::critical(this,"Length of time series does not match.", timeSerie.at(0).absoluteFilePath(), QMessageBox::Ok); + return; + } + } + + QFileInfoList fileList0; + for (auto fileList : timeSeries) fileList0.append(fileList.at(0)); + this->CreateWidgets(fileList0); + comboBox_alignment->setCurrentIndex(1); + on_comboBox_alignment_currentIndexChanged(); + emit sig_loadTimeSeries(true, m_timeSeries.at(0).size()); + } +} + +void ShapePopulationQT::slot_timeIndicesChanged(int index) +{ + // Update widgets + if (index >= 0 && index < (int) m_timeSeries[0].size()) + { + for (unsigned int i = 0; i < m_numberOfMeshes; i++) + { + m_meshList[i]->ReadMesh(m_timeSeries[i][index].absoluteFilePath().toStdString()); + } + } + this->UpdateWindows(); + comboBox_alignment->setCurrentIndex(1); + on_comboBox_alignment_currentIndexChanged(); + + /* ATTRIBUTES & COLORBARS */ + SelectAll(); + int ind = comboBox_VISU_attribute->currentIndex(); + m_updateOnAttributeChanged = true; + comboBox_VISU_attribute->setCurrentIndex(ind); + emit comboBox_VISU_attribute->currentIndexChanged(ind); + + /* VECTORS UPDATE */ + this->setMeshOpacity((double)this->spinbox_meshOpacity->value()/100.0); + this->setVectorScale((double)this->spinbox_vectorScale->value()/100.0); + this->setVectorDensity(this->spinbox_arrowDens->value()); + + pushButton_VIEW_reset->click(); +} + void ShapePopulationQT::deleteAll() { //clear any Content from the layout @@ -396,9 +573,11 @@ void ShapePopulationQT::deleteAll() m_optionsActions->setDisabled(true); //Initialize Menu actions - actionOpen_Directory->setText("Open Directory"); - actionOpen_VTK_Files->setText("Open VTK Files"); - actionLoad_CSV->setText("Load CSV File"); +// actionOpen_Directory->setText("Open Directory"); +// actionOpen_VTK_Files->setText("Open VTK Files"); +// actionOpen_SRep_Files->setText("Open SRep Files"); +// actionOpen_Fiducial_Files->setText("Open Fiducial Files"); +// actionLoad_CSV->setText("Load CSV File"); //Empty the meshes FileInfo List m_meshList.clear(); @@ -406,6 +585,7 @@ void ShapePopulationQT::deleteAll() m_selectedIndex.clear(); m_windowsList.clear(); m_widgetList.clear(); + m_timeSeries.clear(); m_numberOfMeshes = 0; axisColorStruct * axisColor = new axisColorStruct; @@ -423,6 +603,7 @@ void ShapePopulationQT::deleteAll() if(m_customizeColorMapByDirectionDialog->isVisible()) m_customizeColorMapByDirectionDialog->hide(); emit sig_axisColor_value(axisColor, false); + emit sig_loadTimeSeries(false, 1); m_axisColor.clear(); } @@ -431,189 +612,188 @@ void ShapePopulationQT::deleteSelection() { if(m_selectedIndex.size() == 0) return; - this->scrollArea->setVisible(false); - - // Deleting the selection, the widget, and the data - QGridLayout *Qlayout = (QGridLayout *)this->scrollAreaWidgetContents->layout(); - - // delete markers widgets - deleteAllWidgets(); + this->scrollArea->setVisible(false); + // Deleting the selection, the widget, and the data + QGridLayout *Qlayout = (QGridLayout *)this->scrollAreaWidgetContents->layout(); + // delete markers widgets + deleteAllWidgets(); - for (unsigned int i = 0; i < m_selectedIndex.size(); i++) + for (unsigned int i = 0; i < m_selectedIndex.size(); i++) + { + for(unsigned int j = 0; j < m_widgetList.size(); j++) { - for(unsigned int j = 0; j < m_widgetList.size(); j++) + if( j == m_selectedIndex[i]) { - if( j == m_selectedIndex[i]) - { - delete m_meshList.at(j); - m_meshList.erase(m_meshList.begin()+j); - m_glyphList.erase(m_glyphList.begin()+j); + delete m_meshList.at(j); + m_meshList.erase(m_meshList.begin()+j); + m_timeSeries.erase(m_timeSeries.begin()+j); + m_glyphList.erase(m_glyphList.begin()+j); - m_selectedIndex.erase(m_selectedIndex.begin()+i); // CAREFUL : erase i value not j value, different vector here - for(unsigned int k = 0; k < m_selectedIndex.size() ; k++) - { - if (m_selectedIndex[k] > j) m_selectedIndex[k]-- ; // and then decrement the upper indeces - } + m_selectedIndex.erase(m_selectedIndex.begin()+i); // CAREFUL : erase i value not j value, different vector here + for(unsigned int k = 0; k < m_selectedIndex.size() ; k++) + { + if (m_selectedIndex[k] > j) m_selectedIndex[k]-- ; // and then decrement the upper indeces + } - m_windowsList.erase(m_windowsList.begin()+j); + m_windowsList.erase(m_windowsList.begin()+j); - Qlayout->removeWidget(m_widgetList.at(j)); - delete m_widgetList.at(j); - m_widgetList.erase(m_widgetList.begin()+j); + Qlayout->removeWidget(m_widgetList.at(j)); + delete m_widgetList.at(j); + m_widgetList.erase(m_widgetList.begin()+j); - // - m_displayColorMapByMagnitude.erase(m_displayColorMapByMagnitude.begin()+j); - m_displayColorMapByDirection.erase(m_displayColorMapByDirection.begin()+j); - m_displayVectors.erase(m_displayVectors.begin()+j); - m_displayVectorsByMagnitude.erase(m_displayVectorsByMagnitude.begin()+j); - m_displayVectorsByDirection.erase(m_displayVectorsByDirection.begin()+j); + // + m_displayColorMapByMagnitude.erase(m_displayColorMapByMagnitude.begin()+j); + m_displayColorMapByDirection.erase(m_displayColorMapByDirection.begin()+j); + m_displayVectors.erase(m_displayVectors.begin()+j); + m_displayVectorsByMagnitude.erase(m_displayVectorsByMagnitude.begin()+j); + m_displayVectorsByDirection.erase(m_displayVectorsByDirection.begin()+j); - m_meshOpacity.erase(m_meshOpacity.begin()+j); - m_vectorDensity.erase(m_vectorDensity.begin()+j); - m_vectorScale.erase(m_vectorScale.begin()+j); + m_meshOpacity.erase(m_meshOpacity.begin()+j); + m_vectorDensity.erase(m_vectorDensity.begin()+j); + m_vectorScale.erase(m_vectorScale.begin()+j); - i--; - break; - } + i--; + break; } } + } - m_numberOfMeshes = m_widgetList.size(); - spinBox_DISPLAY_columns->setMaximum(m_numberOfMeshes); + m_numberOfMeshes = m_widgetList.size(); + spinBox_DISPLAY_columns->setMaximum(m_numberOfMeshes); - m_noUpdateVectorsByDirection = true; + m_noUpdateVectorsByDirection = true; - // initialization of all axis, sphere, and titles widgets - initializationAllWidgets(); + // initialization of all axis, sphere, and titles widgets + initializationAllWidgets(); - computeCommonAttributes(); // get the common attributes in m_commonAttributes + computeCommonAttributes(); // get the common attributes in m_commonAttributes - // If no more widgets, do as deleteAll - if(m_numberOfMeshes == 0) + // If no more widgets, do as deleteAll + if(m_numberOfMeshes == 0) + { + deleteAll(); + } + else + { + //Selected of all the meshes + for(unsigned int i = 0; i < m_windowsList.size(); i++) { - deleteAll(); + m_windowsList[i]->GetRenderers()->GetFirstRenderer()->SetActiveCamera(m_headcam); //connect to headcam for synchro + m_windowsList[i]->Render(); } - else - { - //Selected of all the meshes - for(unsigned int i = 0; i < m_windowsList.size(); i++) - { - m_windowsList[i]->GetRenderers()->GetFirstRenderer()->SetActiveCamera(m_headcam); //connect to headcam for synchro - m_windowsList[i]->Render(); - } - for (unsigned int j = 0; j < m_widgetList.size(); j++) + for (unsigned int j = 0; j < m_widgetList.size(); j++) + { + m_selectedIndex.clear(); + m_selectedIndex.push_back(j); + + const char * a_cmap = m_meshList[m_selectedIndex[0]]->GetPolyData()->GetPointData()->GetScalars()->GetName(); + std::string cmap = std::string(a_cmap); + size_t found1 = cmap.rfind("_mag"); + cmap = cmap.substr(0,found1); + found1 = cmap.rfind("_ColorByDirection"); + cmap = cmap.substr(0,found1); + + comboBox_VISU_attribute->clear(); // clear the Attributes in the comboBox + m_colorBarList.clear(); // clear the existing colorbars + int index = 0; + m_magnitude.clear(); + m_updateOnAttributeChanged = false; + for(unsigned int i = 0 ; i < m_commonAttributes.size() ; i++) { - m_selectedIndex.clear(); - m_selectedIndex.push_back(j); - - const char * a_cmap = m_meshList[m_selectedIndex[0]]->GetPolyData()->GetPointData()->GetScalars()->GetName(); - std::string cmap = std::string(a_cmap); - size_t found1 = cmap.rfind("_mag"); - cmap = cmap.substr(0,found1); - found1 = cmap.rfind("_ColorByDirection"); - cmap = cmap.substr(0,found1); - - comboBox_VISU_attribute->clear(); // clear the Attributes in the comboBox - m_colorBarList.clear(); // clear the existing colorbars - int index = 0; - m_magnitude.clear(); - m_updateOnAttributeChanged = false; - for(unsigned int i = 0 ; i < m_commonAttributes.size() ; i++) + if(cmap == m_commonAttributes[i].c_str()) index = i; + colorBarStruct * colorBar = new colorBarStruct; //new colorbar for this attribute + gradientWidget_VISU->reset(); //create points + gradientWidget_VISU->getAllColors(&colorBar->colorPointList); //get the points into the colorbar + this->UpdateAttribute(m_commonAttributes[i].c_str(), m_selectedIndex); //create the range + colorBar->range[0] = m_commonRange[0]; //get the range into the colorbar + colorBar->range[1] = m_commonRange[1]; + m_colorBarList.push_back(colorBar); //add the colorbar to the list + + comboBox_VISU_attribute->addItem(QString(m_commonAttributes[i].c_str())); // Then add the attribute to the comboBox + + // color map by direction + int dimension = m_meshList[0]->GetPolyData()->GetPointData()->GetScalars(m_commonAttributes[i].c_str())->GetNumberOfComponents(); + magnitudStruct * magnitude = new magnitudStruct; + if (dimension == 3 ) { - if(cmap == m_commonAttributes[i].c_str()) index = i; - colorBarStruct * colorBar = new colorBarStruct; //new colorbar for this attribute - gradientWidget_VISU->reset(); //create points - gradientWidget_VISU->getAllColors(&colorBar->colorPointList); //get the points into the colorbar - this->UpdateAttribute(m_commonAttributes[i].c_str(), m_selectedIndex); //create the range - colorBar->range[0] = m_commonRange[0]; //get the range into the colorbar - colorBar->range[1] = m_commonRange[1]; - m_colorBarList.push_back(colorBar); //add the colorbar to the list - - comboBox_VISU_attribute->addItem(QString(m_commonAttributes[i].c_str())); // Then add the attribute to the comboBox - - // color map by direction - int dimension = m_meshList[0]->GetPolyData()->GetPointData()->GetScalars(m_commonAttributes[i].c_str())->GetNumberOfComponents(); - magnitudStruct * magnitude = new magnitudStruct; - if (dimension == 3 ) - { - magnitude->max = m_commonRange[1]; - magnitude->min = m_commonRange[0]; - } - else if (dimension == 1 ) - { - magnitude->min = 0.0; - magnitude->max = 0.0; - } - m_magnitude.push_back(magnitude); + magnitude->max = m_commonRange[1]; + magnitude->min = m_commonRange[0]; + } + else if (dimension == 1 ) + { + magnitude->min = 0.0; + magnitude->max = 0.0; + } + m_magnitude.push_back(magnitude); - if(dimension == 3 ) - { - this->UpdateColorMapByDirection(m_commonAttributes[i].c_str(),i); - } + if(dimension == 3 ) + { + this->UpdateColorMapByDirection(m_commonAttributes[i].c_str(),i); } - m_updateOnAttributeChanged = true; + } + m_updateOnAttributeChanged = true; - // attribute previous selected - comboBox_VISU_attribute->setCurrentIndex(index); - this->UpdateAttribute(m_commonAttributes[index].c_str(), m_selectedIndex); - m_usedColorBar = m_colorBarList[index]; - this->gradientWidget_VISU->setAllColors(&m_usedColorBar->colorPointList); - m_noChange = true; - spinBox_VISU_min->setValue(m_usedColorBar->range[0]); - spinBox_VISU_max->setValue(m_usedColorBar->range[1]); - m_noChange = false; + // attribute previous selected + comboBox_VISU_attribute->setCurrentIndex(index); + this->UpdateAttribute(m_commonAttributes[index].c_str(), m_selectedIndex); + m_usedColorBar = m_colorBarList[index]; + this->gradientWidget_VISU->setAllColors(&m_usedColorBar->colorPointList); + m_noChange = true; + spinBox_VISU_min->setValue(m_usedColorBar->range[0]); + spinBox_VISU_max->setValue(m_usedColorBar->range[1]); + m_noChange = false; - m_noUpdateVectorsByDirection = false; + m_noUpdateVectorsByDirection = false; - this->updateColorbar_QT(); - this->updateArrowPosition(); + this->updateColorbar_QT(); + this->updateArrowPosition(); - // COLOR MAP previous selected - if(m_displayColorMapByMagnitude[j]) + // COLOR MAP previous selected + if(m_displayColorMapByMagnitude[j]) + { + this->displayColorMapByMagnitude(true); + } + else if(m_displayColorMapByDirection[j]) + { + this->displayColorMapByDirection(true); + } + // VECTORS previous selected + if(m_displayVectors[j]) + { + if(m_displayVectorsByMagnitude[j]) { - this->displayColorMapByMagnitude(true); + this->displayVectorsByMagnitude(true); } - else if(m_displayColorMapByDirection[j]) + else if(m_displayVectorsByDirection[j]) { - this->displayColorMapByDirection(true); + this->displayVectorsByDirection(true); } - // VECTORS previous selected - if(m_displayVectors[j]) - { - if(m_displayVectorsByMagnitude[j]) - { - this->displayVectorsByMagnitude(true); - } - else if(m_displayVectorsByDirection[j]) - { - this->displayVectorsByDirection(true); - } - this->spinbox_meshOpacity->setValue(m_meshOpacity[j]); - this->spinbox_arrowDens->setValue(m_vectorDensity[j]); - this->spinbox_vectorScale->setValue(m_vectorScale[j]); - this->setMeshOpacity((double)this->spinbox_meshOpacity->value()/100.0); - this->setVectorDensity(this->spinbox_arrowDens->value()); - this->setVectorScale((double)this->spinbox_vectorScale->value()/100); + this->spinbox_meshOpacity->setValue(m_meshOpacity[j]); + this->spinbox_arrowDens->setValue(m_vectorDensity[j]); + this->spinbox_vectorScale->setValue(m_vectorScale[j]); + this->setMeshOpacity((double)this->spinbox_meshOpacity->value()/100.0); + this->setVectorDensity(this->spinbox_arrowDens->value()); + this->setVectorScale((double)this->spinbox_vectorScale->value()/100); - } - pushButton_VIEW_reset->click(); - } - m_selectedIndex.clear(); - - for (unsigned int j = 0; j < m_widgetList.size(); j++) - { - m_selectedIndex.push_back(j); } + pushButton_VIEW_reset->click(); + } + m_selectedIndex.clear(); - this->UnselectAll(); -// this->updateInfo_QT(); - on_spinBox_DISPLAY_columns_valueChanged(); + for (unsigned int j = 0; j < m_widgetList.size(); j++) + { + m_selectedIndex.push_back(j); } + + this->UnselectAll(); +// this->updateInfo_QT(); + on_spinBox_DISPLAY_columns_valueChanged(); + } } @@ -1195,9 +1375,11 @@ void ShapePopulationQT::CreateWidgets(const QList& renderWindo this->actionDelete->setEnabled(true); this->actionDelete_All->setEnabled(true); this->m_exportActions->setEnabled(true); - this->actionOpen_Directory->setText("Add Directory"); - this->actionOpen_VTK_Files->setText("Add VTK/VTP files"); - this->actionLoad_CSV->setText("Add CSV file"); +// this->actionOpen_Directory->setText("Add Directory"); +// this->actionOpen_VTK_Files->setText("Add VTK/VTP files"); +// this->actionLoad_CSV->setText("Add CSV file"); +// this->actionOpen_SRep_Files->setText("Add S-Rep files"); +// this->actionOpen_Fiducial_Files->setText("Add Fiducial files"); /* DISPLAY INFOS */ this->updateInfo_QT(); @@ -1614,6 +1796,11 @@ void ShapePopulationQT::dropEvent(QDropEvent* Qevent) fileList.append(QFileInfo(filePath)); load = true; } + else if((filePath.endsWith(".xml") || filePath.endsWith(".srep.json")) && QFileInfo(filePath).exists()) + { + fileList.append(QFileInfo(filePath)); + load = true; + } else if(filePath.endsWith(".csv") && QFileInfo(filePath).exists()) { this->loadCSVFileCLP(QFileInfo(filePath)); @@ -1633,7 +1820,7 @@ void ShapePopulationQT::dropEvent(QDropEvent* Qevent) } if(load == true) { - this->loadVTKFilesCLP(fileList); + this->CreateWidgets(fileList); } } } diff --git a/src/ShapePopulationQT.h b/src/ShapePopulationQT.h index 7732013..acaa08f 100644 --- a/src/ShapePopulationQT.h +++ b/src/ShapePopulationQT.h @@ -9,6 +9,7 @@ #include "cameraDialogQT.h" #include "backgroundDialogQT.h" #include "CSVloaderQT.h" +#include "timeSeriesLoaderQT.h" #include "customizeColorMapByDirectionDialogQT.h" #include #include @@ -46,9 +47,12 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S ~ShapePopulationQT(); void loadVTKFilesCLP(QFileInfoList a_fileList); + void loadSRepFilesCLP(QFileInfoList a_fileList); + void loadFiducialFilesCLP(QFileInfoList a_fileList); void loadModel(vtkMRMLModelNode* modelNode); void loadModel(vtkPolyData* polyData, const QString& modelName); void loadCSVFileCLP(QFileInfo file); + void loadTimeSeriesCLP(QFileInfo file); void loadVTKDirCLP(QDir vtkDir); void loadColorMapCLP(std::string a_filePath); void loadCameraCLP(std::string a_filePath); @@ -69,9 +73,12 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S QString m_pathSphere; typedef QVTKOpenGLNativeWidget VTKWidgetType; std::vector m_widgetList; + // todo: store m_timeSeries as shapePopulationData for faster visualization. + QList m_timeSeries; cameraDialogQT * m_cameraDialog; backgroundDialogQT * m_backgroundDialog; CSVloaderQT * m_CSVloaderDialog; + timeSeriesLoaderQT * m_timeSeriesLoaderDialog; customizeColorMapByDirectionDialogQT* m_customizeColorMapByDirectionDialog; void CreateWidgets(const QFileInfoList& files); @@ -109,8 +116,13 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S //FILE void openDirectory(); void openFiles(); + void openSRepFiles(); + void openFiducialFiles(); void loadCSV(); + void loadTimeSeries(); void slot_itemsSelected(QFileInfoList fileList); + void slot_timeSeriesSelected(QList fileList); + void slot_timeIndicesChanged(int index); void deleteAll(); void deleteSelection(); @@ -229,6 +241,7 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S void sig_axisColor_value(axisColorStruct* axisColor, bool dialogOpen); void sig_backgroundColor_valueChanged(double backgroundColor_red, double backgroundColor_green, double backgroundColor_blue, bool dialogOpen); void sig_resetColor(); + void sig_loadTimeSeries(bool slider_enabled, unsigned int total_time_step); private: QActionGroup* m_exportActions; diff --git a/src/ShapePopulationQT.ui b/src/ShapePopulationQT.ui index ac25097..0607ac6 100644 --- a/src/ShapePopulationQT.ui +++ b/src/ShapePopulationQT.ui @@ -1656,6 +1656,21 @@ p, li { white-space: pre-wrap; } Load CSV Files + + + Load Time Series + + + + + Open S-Rep Files + + + + + Open Fiducial Files + + Delete Selection diff --git a/src/ShapePopulationViewer.cxx b/src/ShapePopulationViewer.cxx index 8cd71ec..ed01968 100644 --- a/src/ShapePopulationViewer.cxx +++ b/src/ShapePopulationViewer.cxx @@ -87,6 +87,30 @@ int main( int argc, char** argv ) checkConfigurationFiles(cameraConfig, colormapConfig, shapePopulation); } } + + if(!SRepFiles.empty()) + { + bool load = false; + QFileInfoList fileList; + for(unsigned int i = 0 ; i < SRepFiles.size(); i++) + { + QString QFilePath(SRepFiles[i].c_str()); + QFileInfo SRepFileInfo(QFilePath); + if (!QFilePath.endsWith(".xml") && !QFilePath.endsWith(".srep.json")) wrongFileFormat(SRepFiles[i],"xml/srep.json", &window); // Control the files format + else if(!SRepFileInfo.exists()) fileDoesNotExist(SRepFiles[i], &window); // Control that the file exists + else + { + fileList.append(SRepFileInfo); + load = true; + } + } + if(load == true) + { + shapePopulation->loadSRepFilesCLP(fileList); + checkConfigurationFiles(cameraConfig, colormapConfig, shapePopulation); + } + } + if(!vtkDirectory.empty()) { QDir vtkDir(vtkDirectory.c_str()); diff --git a/src/ShapePopulationViewer.qrc b/src/ShapePopulationViewer.qrc index 569b0e5..c43dd70 100644 --- a/src/ShapePopulationViewer.qrc +++ b/src/ShapePopulationViewer.qrc @@ -7,5 +7,11 @@ resources/eyeClosedDisabled.png resources/eyeOpenDisabled.png resources/logo.png + resources/sRepColorMap.spvcm + resources/pqVcrFirst.svg + resources/pqVcrBack.svg + resources/pqVcrPlay.svg + resources/pqVcrForward.svg + resources/pqVcrLast.svg diff --git a/src/ShapePopulationViewer.xml b/src/ShapePopulationViewer.xml index df9de0c..4156a6b 100644 --- a/src/ShapePopulationViewer.xml +++ b/src/ShapePopulationViewer.xml @@ -21,6 +21,24 @@ ShapePopulationViewer is a software that allows you to dynamically interact with + + SRepFiles + + input + --srepfiles + -s + + + + + FiducialFiles + + input + --fiducialfiles + -f + + + CSVFile diff --git a/src/qSlicerShapePopulationViewerModule.cxx b/src/qSlicerShapePopulationViewerModule.cxx index 8490017..091d278 100644 --- a/src/qSlicerShapePopulationViewerModule.cxx +++ b/src/qSlicerShapePopulationViewerModule.cxx @@ -93,7 +93,7 @@ QStringList qSlicerShapePopulationViewerModule::categories() const //----------------------------------------------------------------------------- QStringList qSlicerShapePopulationViewerModule::dependencies() const { - return QStringList(); + return QStringList() << "SRep"; } //----------------------------------------------------------------------------- diff --git a/src/qSlicerShapePopulationViewerModuleWidget.cxx b/src/qSlicerShapePopulationViewerModuleWidget.cxx index b8c5d1c..59f2762 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.cxx +++ b/src/qSlicerShapePopulationViewerModuleWidget.cxx @@ -22,6 +22,7 @@ #include #include #include +#include // MRML includes #include @@ -172,31 +173,17 @@ void qSlicerShapePopulationViewerModuleWidget::setup() layoutNode->AddLayoutDescription( vtkMRMLLayoutNode::SlicerLayoutUserView, shapePopulationViewerLayout); - // Delete Selection - { - QToolButton* button = new QToolButton(); - button->setDefaultAction(d->ShapePopulationWidget->actionDelete); - d->ModuleWidgetLayout->insertWidget(1, button); - } - - // Delete All - { - QToolButton* button = new QToolButton(); - button->setDefaultAction(d->ShapePopulationWidget->actionDelete_All); - d->ModuleWidgetLayout->insertWidget(2, button); - } + // Delete Selection & All + d->toolButton_Delete_Selection->setDefaultAction(d->ShapePopulationWidget->actionDelete); + d->toolButton_Delete_All->setDefaultAction(d->ShapePopulationWidget->actionDelete_All); // Import - foreach(QAction* action, QList() - << d->ShapePopulationWidget->actionOpen_Directory - << d->ShapePopulationWidget->actionOpen_VTK_Files - << d->ShapePopulationWidget->actionLoad_CSV - ) - { - QToolButton* button = new QToolButton(); - button->setDefaultAction(action); - d->ImportLayout->addWidget(button); - } + d->toolButton_Open_Directory->setDefaultAction(d->ShapePopulationWidget->actionOpen_Directory); + d->toolButton_Open_VTK_Files->setDefaultAction(d->ShapePopulationWidget->actionOpen_VTK_Files); + d->toolButton_Load_CSV->setDefaultAction(d->ShapePopulationWidget->actionLoad_CSV); + d->toolButton_Load_Time_Series->setDefaultAction(d->ShapePopulationWidget->actionLoad_Time_Series); + d->toolButton_Open_SRep_Files->setDefaultAction(d->ShapePopulationWidget->actionOpen_SRep_Files); + d->toolButton_Open_Fiducial_Files->setDefaultAction(d->ShapePopulationWidget->actionOpen_Fiducial_Files); // Export #ifdef ShapePopulationViewer_HAS_EXPORT_SUPPORT @@ -217,6 +204,7 @@ void qSlicerShapePopulationViewerModuleWidget::setup() bool exportVisible = false; #endif d->ExportCTKCollapsibleButton->setVisible(exportVisible); + d->spinBox_Time_Step->setEnabled(false); // Settings foreach(QAction* action, QList() @@ -230,7 +218,30 @@ void qSlicerShapePopulationViewerModuleWidget::setup() d->SettingsLayout->addWidget(button); } + // Play buttons + d->pushButton_First->setIcon(QIcon(QString(":/resources/pqVcrFirst.svg"))); + d->pushButton_First->setIconSize(QSize(16, 16)); + connect(d->pushButton_First, SIGNAL(clicked()), this, SLOT(showFirst())); + + d->pushButton_Back->setIcon(QIcon(QString(":/resources/pqVcrBack.svg"))); + d->pushButton_Back->setIconSize(QSize(16, 16)); + connect(d->pushButton_Back, SIGNAL(clicked()), d->spinBox_Time_Step, SLOT(stepDown())); + + d->pushButton_Play->setIcon(QIcon(QString(":/resources/pqVcrPlay.svg"))); + d->pushButton_Play->setIconSize(QSize(16, 16)); + connect(d->pushButton_Play, SIGNAL(clicked()), this, SLOT(showPlay())); + + d->pushButton_Forward->setIcon(QIcon(QString(":/resources/pqVcrForward.svg"))); + d->pushButton_Forward->setIconSize(QSize(16, 16)); + connect(d->pushButton_Forward, SIGNAL(clicked()), d->spinBox_Time_Step, SLOT(stepUp())); + + d->pushButton_Last->setIcon(QIcon(QString(":/resources/pqVcrLast.svg"))); + d->pushButton_Last->setIconSize(QSize(16, 16)); + connect(d->pushButton_Last, SIGNAL(clicked()), this, SLOT(showLast())); + connect(d->ModelLoadPushButton, SIGNAL(clicked()), this, SLOT(loadSelectedModel())); + connect(d->spinBox_Time_Step, SIGNAL(valueChanged(int)), d->ShapePopulationWidget, SLOT(slot_timeIndicesChanged(int))); + connect(d->ShapePopulationWidget, SIGNAL(sig_loadTimeSeries(bool, unsigned int)), this, SLOT(onLoadTimeSeries(bool, unsigned int))); } //----------------------------------------------------------------------------- @@ -273,6 +284,20 @@ void qSlicerShapePopulationViewerModuleWidget::loadCSVFile(const QString& filePa d->ShapePopulationWidget->loadVTKFilesCLP(modelFileInfoList); } +//----------------------------------------------------------------------------- +void qSlicerShapePopulationViewerModuleWidget::loadSRep(const QString &filePath) +{ + Q_D(qSlicerShapePopulationViewerModuleWidget); + d->ShapePopulationWidget->loadSRepFilesCLP(QFileInfoList() << QFileInfo(filePath)); +} + +//----------------------------------------------------------------------------- +void qSlicerShapePopulationViewerModuleWidget::loadFiducial(const QString &filePath) +{ + Q_D(qSlicerShapePopulationViewerModuleWidget); + d->ShapePopulationWidget->loadFiducialFilesCLP(QFileInfoList() << QFileInfo(filePath)); +} + //----------------------------------------------------------------------------- void qSlicerShapePopulationViewerModuleWidget::deleteModels() { @@ -327,3 +352,35 @@ void qSlicerShapePopulationViewerModuleWidget::onMRMLNodeModified(vtkObject *cal this->loadModel(modelNode); qvtkDisconnect(modelNode, vtkCommand::ModifiedEvent, this, SLOT(onMRMLNodeModified(vtkObject*))); } + +void qSlicerShapePopulationViewerModuleWidget::onLoadTimeSeries(bool spin_box_enabled, unsigned int total_time_step) +{ + Q_D(qSlicerShapePopulationViewerModuleWidget); + d->spinBox_Time_Step->setMinimum(0); + d->spinBox_Time_Step->setMaximum(total_time_step - 1); + d->spinBox_Time_Step->setValue(0); + d->spinBox_Time_Step->setEnabled(spin_box_enabled); +} + +void qSlicerShapePopulationViewerModuleWidget::showFirst() +{ + Q_D(qSlicerShapePopulationViewerModuleWidget); + d->spinBox_Time_Step->setValue(d->spinBox_Time_Step->minimum()); +} + +void qSlicerShapePopulationViewerModuleWidget::showLast() +{ + Q_D(qSlicerShapePopulationViewerModuleWidget); + d->spinBox_Time_Step->setValue(d->spinBox_Time_Step->maximum()); +} + +void qSlicerShapePopulationViewerModuleWidget::showPlay() +{ + Q_D(qSlicerShapePopulationViewerModuleWidget); + if(d->spinBox_Time_Step->value() != d->spinBox_Time_Step->maximum()) + { + d->spinBox_Time_Step->stepUp(); + // todo: avoid recursion and magic number for timer interval + QTimer::singleShot(200, this, SLOT(showPlay())); + } +} \ No newline at end of file diff --git a/src/qSlicerShapePopulationViewerModuleWidget.h b/src/qSlicerShapePopulationViewerModuleWidget.h index d0e8ed8..def1c96 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.h +++ b/src/qSlicerShapePopulationViewerModuleWidget.h @@ -53,6 +53,8 @@ public slots: void loadModel(const QString& filePath); void loadModel(vtkPolyData* polydata, const QString& modelName); void loadCSVFile(const QString& filePath); + void loadSRep(const QString& filePath); + void loadFiducial(const QString& filePath); void deleteModels(); @@ -63,6 +65,10 @@ public slots: protected slots: void onMRMLSceneNodeAddedEvent(vtkObject*,vtkObject*); void onMRMLNodeModified(vtkObject*); + void onLoadTimeSeries(bool slider_enabled, unsigned int total_time_step); + void showFirst(); + void showLast(); + void showPlay(); protected: QScopedPointer d_ptr; diff --git a/src/qSlicerShapePopulationViewerModuleWidget.ui b/src/qSlicerShapePopulationViewerModuleWidget.ui index 4fcfce9..de776e9 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.ui +++ b/src/qSlicerShapePopulationViewerModuleWidget.ui @@ -6,14 +6,14 @@ 0 0 - 525 - 319 + 451 + 450 Form - + @@ -50,12 +50,176 @@ + + + + + + + 0 + 0 + + + + Delete Selection + + + + + + + + 0 + 0 + + + + Delete All + + + + + Import - + + + + + + 0 + 0 + + + + Load Time Series + + + + + + + + 0 + 0 + + + + Add VTK/VTP Files + + + + + + + + 0 + 0 + + + + Add S-Rep Files + + + + + + + + 0 + 0 + + + + Add Fiducial Files + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Time Step + + + Qt::AlignCenter + + + + + + + + + + + + + 0 + 0 + + + + Add Directory + + + + + + + + 0 + 0 + + + + Load CSV + + + @@ -64,8 +228,7 @@ Export - - + @@ -94,8 +257,8 @@ - 0 - 0 + 13 + 108 diff --git a/src/resources/pqVcrBack.svg b/src/resources/pqVcrBack.svg new file mode 100644 index 0000000..2b63c49 --- /dev/null +++ b/src/resources/pqVcrBack.svg @@ -0,0 +1 @@ + diff --git a/src/resources/pqVcrFirst.svg b/src/resources/pqVcrFirst.svg new file mode 100644 index 0000000..9eb72db --- /dev/null +++ b/src/resources/pqVcrFirst.svg @@ -0,0 +1 @@ + diff --git a/src/resources/pqVcrForward.svg b/src/resources/pqVcrForward.svg new file mode 100644 index 0000000..31c042b --- /dev/null +++ b/src/resources/pqVcrForward.svg @@ -0,0 +1 @@ + diff --git a/src/resources/pqVcrLast.svg b/src/resources/pqVcrLast.svg new file mode 100644 index 0000000..0ae3f5e --- /dev/null +++ b/src/resources/pqVcrLast.svg @@ -0,0 +1 @@ + diff --git a/src/resources/pqVcrPlay.svg b/src/resources/pqVcrPlay.svg new file mode 100644 index 0000000..a08a1d5 --- /dev/null +++ b/src/resources/pqVcrPlay.svg @@ -0,0 +1 @@ + diff --git a/src/resources/sRepColorMap.spvcm b/src/resources/sRepColorMap.spvcm new file mode 100644 index 0000000..2217fe9 --- /dev/null +++ b/src/resources/sRepColorMap.spvcm @@ -0,0 +1,35 @@ + + + + + 0 + 1 + 0 + 1 + + + 0.25 + 0 + 0.666667 + 0 + + + 0.5 + 1 + 1 + 0 + + + 0.75 + 1 + 1 + 0 + + + 1 + 0.333333 + 1 + 1 + + + diff --git a/src/timeSeriesLoaderQT.cxx b/src/timeSeriesLoaderQT.cxx new file mode 100644 index 0000000..0217380 --- /dev/null +++ b/src/timeSeriesLoaderQT.cxx @@ -0,0 +1,87 @@ +#include "timeSeriesLoaderQT.h" +#include "ui_timeSeriesLoaderQT.h" + +timeSeriesLoaderQT::timeSeriesLoaderQT(QWidget *Qparent) : + QDialog(Qparent), + ui(new Ui::timeSeriesLoaderQT) +{ + ui->setupUi(this); + m_layout = new QHBoxLayout(ui->tableWidget); +} + +timeSeriesLoaderQT::~timeSeriesLoaderQT() +{ + delete ui; + delete m_layout; +} + + +void timeSeriesLoaderQT::displayTable(vtkSmartPointer table, QDir directory) +{ + m_table = table; + m_directory = directory; + + //Translate to QT + vtkNew tableView; + tableView->AddRepresentationFromInput(m_table); + tableView->SetSelectionBehavior(1); //to select rows + tableView->SetSortingEnabled(false); //to select vertical headers therefore columns + //tableView->SetSplitMultiComponentColumns(true); //to tell Bowser to let princess Peach go + tableView->Update(); + m_tableView = tableView; + + //Display + m_layout->addWidget(tableView->GetWidget()); + this->exec(); +} + +void timeSeriesLoaderQT::on_buttonBox_accepted() +{ + //Get rows and columns + vtkNew rows; + m_tableView->GetSelectedItems(rows); + auto columns = m_table->GetNumberOfColumns(); + + //Get selected rows + QList timeSeries; + for(int i = 0 ; i < rows->GetNumberOfTuples(); i++) + { + auto row = rows->GetTuple1(i); + QFileInfoList fileList; + for(int j = 0; j < columns; j++) + { + vtkVariant test = m_table->GetValue(row,j); + QString relativePath = test.ToString().c_str(); + fileList.append(QFileInfo(m_directory,relativePath)); + } + timeSeries.append(fileList); + } + + // Control the files format + for (int i = 0; i < timeSeries.size(); i++) + { + for (int j = 0; j < timeSeries.at(i).size(); j++) + { + QString QFilePath = timeSeries[i][j].absoluteFilePath(); + if (!QFilePath.endsWith(".vtk") && !QFilePath.endsWith(".vtp") && !QFilePath.endsWith(".xml") && !QFilePath.endsWith(".srep.json")) + { + timeSeries[i].removeAt(j); + j--; + std::ostringstream strs; + strs << QFilePath.toStdString() << std::endl + << "This is not a vtk/vtp/xml/srep.json file."<< std::endl; + QMessageBox::critical(this,"Wrong file format",QString(strs.str().c_str()), QMessageBox::Ok); + } + else if(!timeSeries[i][j].exists()) + { + timeSeries[i].removeAt(j); + j--; + std::ostringstream strs; + strs << QFilePath.toStdString() << std::endl + << "This file does not exist."<< std::endl; + QMessageBox::critical(this,"File not found",QString(strs.str().c_str()), QMessageBox::Ok); + } + } + } + emit sig_timeSeriesSelected(timeSeries); +} diff --git a/src/timeSeriesLoaderQT.h b/src/timeSeriesLoaderQT.h new file mode 100644 index 0000000..87e896f --- /dev/null +++ b/src/timeSeriesLoaderQT.h @@ -0,0 +1,44 @@ +#ifndef TIMESERIESLOADERQT_H +#define TIMESERIESLOADERQT_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Ui { +class timeSeriesLoaderQT; +} + +class timeSeriesLoaderQT : public QDialog +{ + Q_OBJECT + +public: + explicit timeSeriesLoaderQT(QWidget *Qparent = 0); + ~timeSeriesLoaderQT(); + + void displayTable(vtkSmartPointer table, QDir directory); + +private slots: + void on_buttonBox_accepted(); + +signals: + void sig_timeSeriesSelected(QList timeSeries); + +private: + vtkSmartPointer m_tableView; + vtkSmartPointer m_table; + QDir m_directory; + + QHBoxLayout * m_layout; + Ui::timeSeriesLoaderQT *ui; +}; + +#endif // TIMESERIESLOADERQT_H diff --git a/src/timeSeriesLoaderQT.ui b/src/timeSeriesLoaderQT.ui new file mode 100644 index 0000000..3737f61 --- /dev/null +++ b/src/timeSeriesLoaderQT.ui @@ -0,0 +1,67 @@ + + + timeSeriesLoaderQT + + + + 0 + 0 + 1024 + 311 + + + + Dialog + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + timeSeriesLoaderQT + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + timeSeriesLoaderQT + reject() + + + 316 + 260 + + + 286 + 274 + + + + +