From 1ef7a303181b9bde12508f63715115012ebd4084 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Mon, 19 Apr 2021 16:36:23 -0400 Subject: [PATCH 01/21] ENH: Allow import .xml file to visualize S-Rep --- src/ShapePopulationData.cxx | 75 ++++++++++++++++++- src/ShapePopulationData.h | 7 +- src/ShapePopulationMainWindowQT.cxx | 1 + src/ShapePopulationQT.cxx | 29 +++++++ src/ShapePopulationQT.h | 2 + src/ShapePopulationQT.ui | 5 ++ ...licerShapePopulationViewerModuleWidget.cxx | 10 ++- ...qSlicerShapePopulationViewerModuleWidget.h | 1 + 8 files changed, 124 insertions(+), 6 deletions(-) diff --git a/src/ShapePopulationData.cxx b/src/ShapePopulationData.cxx index e63f56e..a886eee 100644 --- a/src/ShapePopulationData.cxx +++ b/src/ShapePopulationData.cxx @@ -39,11 +39,31 @@ 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 + { + return 0; + } return m_PolyData; } @@ -96,4 +116,53 @@ 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; + } + + // merge poly data + vtkNew append; + append->AddInputData(upPD); + append->AddInputData(downPD); + append->AddInputData(crestPD); + 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 || dim == 3 ) + { + std::string AttributeString = AttributeName; + m_AttributeList.push_back(AttributeString); + } + if( dim == 3) + { + //Vectors + vtkPVPostFilter * getVectors = vtkPVPostFilter::New(); + std::ostringstream strs; + strs.str(""); + strs.clear(); + strs << AttributeName << "_mag" << std::endl; + getVectors->DoAnyNeededConversions(m_PolyData,strs.str().c_str(),vtkDataObject::FIELD_ASSOCIATION_POINTS, AttributeName, "Magnitude"); + getVectors->Delete(); + } + } + std::sort(m_AttributeList.begin(),m_AttributeList.end()); +} + diff --git a/src/ShapePopulationData.h b/src/ShapePopulationData.h index 15400b8..a8b9295 100644 --- a/src/ShapePopulationData.h +++ b/src/ShapePopulationData.h @@ -3,12 +3,14 @@ #include +#include #include +#include #include +#include #include +#include #include -#include -#include #include "vtkPVPostFilter.h" @@ -26,6 +28,7 @@ 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); vtkSmartPointer ReadPolyData(std::string a_filePath); vtkSmartPointer GetPolyData() {return m_PolyData;} 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..3947926 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -84,6 +84,7 @@ 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(actionLoad_CSV,SIGNAL(triggered()),this,SLOT(loadCSV())); connect(m_CSVloaderDialog,SIGNAL(sig_itemsSelected(QFileInfoList)),this,SLOT(slot_itemsSelected(QFileInfoList))); connect(actionDelete,SIGNAL(triggered()),this,SLOT(deleteSelection())); @@ -202,6 +203,11 @@ void ShapePopulationQT::loadVTKFilesCLP(QFileInfoList a_fileList) this->CreateWidgets(a_fileList); } +void ShapePopulationQT::loadSRepFilesCLP(QFileInfoList a_fileList) +{ + this->CreateWidgets(a_fileList); +} + void ShapePopulationQT::loadModel(vtkMRMLModelNode* modelNode) { #ifdef ShapePopulationViewer_BUILD_SLICER_EXTENSION @@ -345,6 +351,28 @@ void ShapePopulationQT::openFiles() this->CreateWidgets(fileInfos); } +void ShapePopulationQT::openSRepFiles() +{ + QStringList stringList = QFileDialog::getOpenFileNames(this,tr("Open Files"),m_lastDirectory,"S-Rep Files (*.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); +} void ShapePopulationQT::loadCSV() { @@ -1198,6 +1226,7 @@ void ShapePopulationQT::CreateWidgets(const QList& renderWindo 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"); /* DISPLAY INFOS */ this->updateInfo_QT(); diff --git a/src/ShapePopulationQT.h b/src/ShapePopulationQT.h index 7732013..e1c95dd 100644 --- a/src/ShapePopulationQT.h +++ b/src/ShapePopulationQT.h @@ -46,6 +46,7 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S ~ShapePopulationQT(); void loadVTKFilesCLP(QFileInfoList a_fileList); + void loadSRepFilesCLP(QFileInfoList a_fileList); void loadModel(vtkMRMLModelNode* modelNode); void loadModel(vtkPolyData* polyData, const QString& modelName); void loadCSVFileCLP(QFileInfo file); @@ -109,6 +110,7 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S //FILE void openDirectory(); void openFiles(); + void openSRepFiles(); void loadCSV(); void slot_itemsSelected(QFileInfoList fileList); void deleteAll(); diff --git a/src/ShapePopulationQT.ui b/src/ShapePopulationQT.ui index ac25097..4065d70 100644 --- a/src/ShapePopulationQT.ui +++ b/src/ShapePopulationQT.ui @@ -1656,6 +1656,11 @@ p, li { white-space: pre-wrap; } Load CSV Files + + + Open S-Rep Files + + Delete Selection diff --git a/src/qSlicerShapePopulationViewerModuleWidget.cxx b/src/qSlicerShapePopulationViewerModuleWidget.cxx index b8c5d1c..28a744c 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.cxx +++ b/src/qSlicerShapePopulationViewerModuleWidget.cxx @@ -191,7 +191,8 @@ void qSlicerShapePopulationViewerModuleWidget::setup() << d->ShapePopulationWidget->actionOpen_Directory << d->ShapePopulationWidget->actionOpen_VTK_Files << d->ShapePopulationWidget->actionLoad_CSV - ) + << d->ShapePopulationWidget->actionOpen_SRep_Files + ) { QToolButton* button = new QToolButton(); button->setDefaultAction(action); @@ -273,6 +274,13 @@ 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::deleteModels() { diff --git a/src/qSlicerShapePopulationViewerModuleWidget.h b/src/qSlicerShapePopulationViewerModuleWidget.h index d0e8ed8..eed02e1 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.h +++ b/src/qSlicerShapePopulationViewerModuleWidget.h @@ -53,6 +53,7 @@ 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 deleteModels(); From 8c4e99954dc9b2ebf71159b1fa4d4848772c65f1 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Tue, 20 Apr 2021 21:55:09 -0400 Subject: [PATCH 02/21] ENH: Visualize spokes and assign point data for coloring --- src/ShapePopulationData.cxx | 79 +++++++++++++++++++++++++++-------- src/ShapePopulationData.h | 9 ++++ src/ShapePopulationQT.cxx | 7 +++- src/ShapePopulationViewer.xml | 9 ++++ 4 files changed, 86 insertions(+), 18 deletions(-) diff --git a/src/ShapePopulationData.cxx b/src/ShapePopulationData.cxx index a886eee..6fa4729 100644 --- a/src/ShapePopulationData.cxx +++ b/src/ShapePopulationData.cxx @@ -129,14 +129,16 @@ void ShapePopulationData::ReadSRep(vtkPolyData* upPD, vtkPolyData* downPD, vtkPo m_FileName = a_filePath; } - // merge poly data + // Create spoke geometries and merge poly data vtkNew append; - append->AddInputData(upPD); - append->AddInputData(downPD); - append->AddInputData(crestPD); + 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 + // Update the class members m_PolyData = append->GetOutput(); int numAttributes = m_PolyData->GetPointData()->GetNumberOfArrays(); @@ -145,24 +147,67 @@ void ShapePopulationData::ReadSRep(vtkPolyData* upPD, vtkPolyData* downPD, vtkPo int dim = m_PolyData->GetPointData()->GetArray(j)->GetNumberOfComponents(); const char * AttributeName = m_PolyData->GetPointData()->GetArrayName(j); - if (dim == 1 || dim == 3 ) + if (dim == 1) { std::string AttributeString = AttributeName; m_AttributeList.push_back(AttributeString); } - if( dim == 3) - { - //Vectors - vtkPVPostFilter * getVectors = vtkPVPostFilter::New(); - std::ostringstream strs; - strs.str(""); - strs.clear(); - strs << AttributeName << "_mag" << std::endl; - getVectors->DoAnyNeededConversions(m_PolyData,strs.str().c_str(),vtkDataObject::FIELD_ASSOCIATION_POINTS, AttributeName, "Magnitude"); - getVectors->Delete(); - } } 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; +} diff --git a/src/ShapePopulationData.h b/src/ShapePopulationData.h index a8b9295..3594db6 100644 --- a/src/ShapePopulationData.h +++ b/src/ShapePopulationData.h @@ -4,11 +4,17 @@ #include #include +#include +#include +#include +#include #include #include +#include #include #include #include +#include #include #include @@ -38,6 +44,9 @@ 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; diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index 3947926..279e01b 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -1643,6 +1643,11 @@ void ShapePopulationQT::dropEvent(QDropEvent* Qevent) fileList.append(QFileInfo(filePath)); load = true; } + else if(filePath.endsWith(".xml") && QFileInfo(filePath).exists()) + { + fileList.append(QFileInfo(filePath)); + load = true; + } else if(filePath.endsWith(".csv") && QFileInfo(filePath).exists()) { this->loadCSVFileCLP(QFileInfo(filePath)); @@ -1662,7 +1667,7 @@ void ShapePopulationQT::dropEvent(QDropEvent* Qevent) } if(load == true) { - this->loadVTKFilesCLP(fileList); + this->CreateWidgets(fileList); } } } diff --git a/src/ShapePopulationViewer.xml b/src/ShapePopulationViewer.xml index df9de0c..2a7c43a 100644 --- a/src/ShapePopulationViewer.xml +++ b/src/ShapePopulationViewer.xml @@ -21,6 +21,15 @@ ShapePopulationViewer is a software that allows you to dynamically interact with + + SRepFiles + + input + --srepfiles + -v + + + CSVFile From 415fc213e2d5314a825550e5b2b8f2bb457c2ce7 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Wed, 21 Apr 2021 18:40:54 -0400 Subject: [PATCH 03/21] ENH: Load customized colormap when reading SReps --- src/ShapePopulationQT.cxx | 8 ++++++++ src/ShapePopulationViewer.qrc | 1 + src/resources/sRepColorMap.spvcm | 35 ++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 src/resources/sRepColorMap.spvcm diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index 279e01b..76d677c 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -372,6 +372,13 @@ void ShapePopulationQT::openSRepFiles() //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::loadCSV() @@ -426,6 +433,7 @@ void ShapePopulationQT::deleteAll() //Initialize Menu actions actionOpen_Directory->setText("Open Directory"); actionOpen_VTK_Files->setText("Open VTK Files"); + actionOpen_SRep_Files->setText("Open SRep Files"); actionLoad_CSV->setText("Load CSV File"); //Empty the meshes FileInfo List diff --git a/src/ShapePopulationViewer.qrc b/src/ShapePopulationViewer.qrc index 569b0e5..dadeacb 100644 --- a/src/ShapePopulationViewer.qrc +++ b/src/ShapePopulationViewer.qrc @@ -7,5 +7,6 @@ resources/eyeClosedDisabled.png resources/eyeOpenDisabled.png resources/logo.png + resources/sRepColorMap.spvcm 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 + + + From 472c234ff2cf9f356005f52bfb2316bfb7d0bbe7 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Thu, 22 Apr 2021 14:47:12 -0400 Subject: [PATCH 04/21] ENH: Allow SPV to Load SReps from CLI --- src/ShapePopulationQT.cxx | 7 +++++++ src/ShapePopulationViewer.cxx | 24 ++++++++++++++++++++++++ src/ShapePopulationViewer.xml | 2 +- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index 76d677c..4089bc9 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -206,6 +206,13 @@ void ShapePopulationQT::loadVTKFilesCLP(QFileInfoList 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::loadModel(vtkMRMLModelNode* modelNode) diff --git a/src/ShapePopulationViewer.cxx b/src/ShapePopulationViewer.cxx index 8cd71ec..689e92f 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")) wrongFileFormat(SRepFiles[i],"xml", &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.xml b/src/ShapePopulationViewer.xml index 2a7c43a..05c0ce2 100644 --- a/src/ShapePopulationViewer.xml +++ b/src/ShapePopulationViewer.xml @@ -26,7 +26,7 @@ ShapePopulationViewer is a software that allows you to dynamically interact with input --srepfiles - -v + -s From 410d271f02ad1ee98c4da47a852ef1524daa458a Mon Sep 17 00:00:00 2001 From: Ye Han Date: Wed, 23 Jun 2021 12:06:24 -0400 Subject: [PATCH 05/21] ENH: Allow reading s-rep from csv files --- src/CSVloaderQT.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CSVloaderQT.cxx b/src/CSVloaderQT.cxx index db05d05..f05cb09 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")) { 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 file."<< std::endl; QMessageBox::critical(this,"Wrong file format",QString(strs.str().c_str()), QMessageBox::Ok); } else if(!fileList[i].exists()) From 79b5bf2ea869409e9cddcd27ddcfb51919d2edd6 Mon Sep 17 00:00:00 2001 From: Connor Bowley Date: Fri, 4 Mar 2022 12:58:09 -0500 Subject: [PATCH 06/21] ENH: Update to support new .srep.json files --- src/CMakeLists.txt | 4 + src/ShapePopulationData.cxx | 134 +++++++++++++++++++++ src/ShapePopulationData.h | 2 + src/ShapePopulationQT.cxx | 4 +- src/ShapePopulationViewer.cxx | 2 +- src/ShapePopulationViewer.xml | 4 +- src/qSlicerShapePopulationViewerModule.cxx | 2 +- 7 files changed, 146 insertions(+), 6 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 46e770b..3b9602c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -90,6 +90,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 +111,8 @@ if(ShapePopulationViewer_BUILD_SLICER_EXTENSION) ) set(MODULE_TARGET_LIBRARIES ${LOCAL_PROJECT_NAME}Widget + vtkSlicerSRepModuleMRML + vtkSlicerSRepModuleLogic ) set(MODULE_RESOURCES ) diff --git a/src/ShapePopulationData.cxx b/src/ShapePopulationData.cxx index 6fa4729..bcce7d5 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(); @@ -60,6 +96,10 @@ vtkSmartPointer ShapePopulationData::ReadMesh(std::string a_filePat ReadSRep(upPD, downPD, crestPD, a_filePath); } + else if (endswith(a_filePath, ".srep.json")) + { + ReadSRep(a_filePath); + } else { return 0; @@ -211,3 +251,97 @@ vtkSmartPointer ShapePopulationData::AttachCellType(vtkPolyData *po 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 3594db6..3e9b61c 100644 --- a/src/ShapePopulationData.h +++ b/src/ShapePopulationData.h @@ -51,6 +51,8 @@ class ShapePopulationData std::string m_FileName; std::string m_FileDir; std::vector m_AttributeList; +private: + void ReadSRep(const std::string& a_filePath); }; diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index 4089bc9..8a52429 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -360,7 +360,7 @@ void ShapePopulationQT::openFiles() void ShapePopulationQT::openSRepFiles() { - QStringList stringList = QFileDialog::getOpenFileNames(this,tr("Open Files"),m_lastDirectory,"S-Rep Files (*.xml)"); + QStringList stringList = QFileDialog::getOpenFileNames(this,tr("Open Files"),m_lastDirectory,"S-Rep Files (*.srep.json *.xml)"); if(stringList.isEmpty()) { return ; @@ -1658,7 +1658,7 @@ void ShapePopulationQT::dropEvent(QDropEvent* Qevent) fileList.append(QFileInfo(filePath)); load = true; } - else if(filePath.endsWith(".xml") && QFileInfo(filePath).exists()) + else if((filePath.endsWith(".xml") || filePath.endsWith(".srep.json")) && QFileInfo(filePath).exists()) { fileList.append(QFileInfo(filePath)); load = true; diff --git a/src/ShapePopulationViewer.cxx b/src/ShapePopulationViewer.cxx index 689e92f..ed01968 100644 --- a/src/ShapePopulationViewer.cxx +++ b/src/ShapePopulationViewer.cxx @@ -96,7 +96,7 @@ int main( int argc, char** argv ) { QString QFilePath(SRepFiles[i].c_str()); QFileInfo SRepFileInfo(QFilePath); - if (!QFilePath.endsWith(".xml")) wrongFileFormat(SRepFiles[i],"xml", &window); // Control the files format + 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 { diff --git a/src/ShapePopulationViewer.xml b/src/ShapePopulationViewer.xml index 05c0ce2..642e0ac 100644 --- a/src/ShapePopulationViewer.xml +++ b/src/ShapePopulationViewer.xml @@ -21,13 +21,13 @@ ShapePopulationViewer is a software that allows you to dynamically interact with - + SRepFiles input --srepfiles -s - + 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"; } //----------------------------------------------------------------------------- From fe97b590f0d0f61d8e08472cdf6502f641709e48 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Fri, 10 Sep 2021 15:36:20 -0400 Subject: [PATCH 07/21] ENH: Allow SPV to read .fcsv fiducial files --- src/ShapePopulationData.cxx | 89 +++++++++++++++++++ src/ShapePopulationData.h | 6 ++ src/ShapePopulationQT.cxx | 32 +++++++ src/ShapePopulationQT.h | 2 + src/ShapePopulationQT.ui | 5 ++ src/ShapePopulationViewer.xml | 9 ++ ...licerShapePopulationViewerModuleWidget.cxx | 8 ++ ...qSlicerShapePopulationViewerModuleWidget.h | 1 + 8 files changed, 152 insertions(+) diff --git a/src/ShapePopulationData.cxx b/src/ShapePopulationData.cxx index bcce7d5..319b91f 100644 --- a/src/ShapePopulationData.cxx +++ b/src/ShapePopulationData.cxx @@ -100,6 +100,10 @@ vtkSmartPointer ShapePopulationData::ReadMesh(std::string a_filePat { ReadSRep(a_filePath); } + else if(endswith(a_filePath, ".fcsv")) + { + ReadFiducial(a_filePath); + } else { return 0; @@ -196,6 +200,91 @@ void ShapePopulationData::ReadSRep(vtkPolyData* upPD, vtkPolyData* downPD, vtkPo std::sort(m_AttributeList.begin(),m_AttributeList.end()); } +void ShapePopulationData::ReadFiducial(const std::string& 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(); diff --git a/src/ShapePopulationData.h b/src/ShapePopulationData.h index 3e9b61c..f6eb6bf 100644 --- a/src/ShapePopulationData.h +++ b/src/ShapePopulationData.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,10 @@ #include #include +#include +#include +#include + class ShapePopulationData { public : @@ -35,6 +40,7 @@ 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;} diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index 8a52429..69c62bb 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -85,6 +85,7 @@ ShapePopulationQT::ShapePopulationQT(QWidget* parent) : QWidget(parent) 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(m_CSVloaderDialog,SIGNAL(sig_itemsSelected(QFileInfoList)),this,SLOT(slot_itemsSelected(QFileInfoList))); connect(actionDelete,SIGNAL(triggered()),this,SLOT(deleteSelection())); @@ -215,6 +216,12 @@ void ShapePopulationQT::loadSRepFilesCLP(QFileInfoList a_fileList) this->updateColorbar_QT(); } +void ShapePopulationQT::loadFiducialFilesCLP(QFileInfoList a_fileList) +{ + + this->CreateWidgets(a_fileList); +} + void ShapePopulationQT::loadModel(vtkMRMLModelNode* modelNode) { #ifdef ShapePopulationViewer_BUILD_SLICER_EXTENSION @@ -388,6 +395,29 @@ void ShapePopulationQT::openSRepFiles() 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() { // get directory @@ -441,6 +471,7 @@ void ShapePopulationQT::deleteAll() 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 @@ -1242,6 +1273,7 @@ void ShapePopulationQT::CreateWidgets(const QList& renderWindo 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(); diff --git a/src/ShapePopulationQT.h b/src/ShapePopulationQT.h index e1c95dd..907d1aa 100644 --- a/src/ShapePopulationQT.h +++ b/src/ShapePopulationQT.h @@ -47,6 +47,7 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S 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); @@ -111,6 +112,7 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S void openDirectory(); void openFiles(); void openSRepFiles(); + void openFiducialFiles(); void loadCSV(); void slot_itemsSelected(QFileInfoList fileList); void deleteAll(); diff --git a/src/ShapePopulationQT.ui b/src/ShapePopulationQT.ui index 4065d70..bdb7bce 100644 --- a/src/ShapePopulationQT.ui +++ b/src/ShapePopulationQT.ui @@ -1661,6 +1661,11 @@ p, li { white-space: pre-wrap; } Open S-Rep Files + + + Open Fiducial Files + + Delete Selection diff --git a/src/ShapePopulationViewer.xml b/src/ShapePopulationViewer.xml index 642e0ac..4156a6b 100644 --- a/src/ShapePopulationViewer.xml +++ b/src/ShapePopulationViewer.xml @@ -30,6 +30,15 @@ ShapePopulationViewer is a software that allows you to dynamically interact with + + FiducialFiles + + input + --fiducialfiles + -f + + + CSVFile diff --git a/src/qSlicerShapePopulationViewerModuleWidget.cxx b/src/qSlicerShapePopulationViewerModuleWidget.cxx index 28a744c..4b1592e 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.cxx +++ b/src/qSlicerShapePopulationViewerModuleWidget.cxx @@ -192,6 +192,7 @@ void qSlicerShapePopulationViewerModuleWidget::setup() << d->ShapePopulationWidget->actionOpen_VTK_Files << d->ShapePopulationWidget->actionLoad_CSV << d->ShapePopulationWidget->actionOpen_SRep_Files + << d->ShapePopulationWidget->actionOpen_Fiducial_Files ) { QToolButton* button = new QToolButton(); @@ -281,6 +282,13 @@ void qSlicerShapePopulationViewerModuleWidget::loadSRep(const QString &filePath) d->ShapePopulationWidget->loadSRepFilesCLP(QFileInfoList() << QFileInfo(filePath)); } +//----------------------------------------------------------------------------- +void qSlicerShapePopulationViewerModuleWidget::loadFiducial(const QString &filePath) +{ + Q_D(qSlicerShapePopulationViewerModuleWidget); + d->ShapePopulationWidget->loadFiducialFilesCLP(QFileInfoList() << QFileInfo(filePath)); +} + //----------------------------------------------------------------------------- void qSlicerShapePopulationViewerModuleWidget::deleteModels() { diff --git a/src/qSlicerShapePopulationViewerModuleWidget.h b/src/qSlicerShapePopulationViewerModuleWidget.h index eed02e1..b610e89 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.h +++ b/src/qSlicerShapePopulationViewerModuleWidget.h @@ -54,6 +54,7 @@ public slots: void loadModel(vtkPolyData* polydata, const QString& modelName); void loadCSVFile(const QString& filePath); void loadSRep(const QString& filePath); + void loadFiducial(const QString& filePath); void deleteModels(); From d3923d4db0843eccb931e05c1be2005968f9c87d Mon Sep 17 00:00:00 2001 From: Ye Han Date: Tue, 21 Nov 2023 20:56:23 -0500 Subject: [PATCH 08/21] ENH: Change Import layout to vertical to reduce module area --- src/qSlicerShapePopulationViewerModuleWidget.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qSlicerShapePopulationViewerModuleWidget.ui b/src/qSlicerShapePopulationViewerModuleWidget.ui index 4fcfce9..0730cc2 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.ui +++ b/src/qSlicerShapePopulationViewerModuleWidget.ui @@ -55,7 +55,7 @@ Import - + From 4d1a0249a6385dacf9b15d87c6603072040aa2d4 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Tue, 21 Nov 2023 21:39:18 -0500 Subject: [PATCH 09/21] BUG: Fix filename info for fiducial --- src/ShapePopulationData.cxx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ShapePopulationData.cxx b/src/ShapePopulationData.cxx index 319b91f..be39a04 100644 --- a/src/ShapePopulationData.cxx +++ b/src/ShapePopulationData.cxx @@ -202,6 +202,17 @@ void ShapePopulationData::ReadSRep(vtkPolyData* upPD, vtkPolyData* downPD, vtkPo 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)) From 488f43a25ef844cc711f7adb1ad3e547ed375565 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Mon, 25 Mar 2024 17:11:32 -0400 Subject: [PATCH 10/21] STYLE: Rearrange ui layout --- ...licerShapePopulationViewerModuleWidget.cxx | 33 +---- ...SlicerShapePopulationViewerModuleWidget.ui | 138 +++++++++++++++++- 2 files changed, 138 insertions(+), 33 deletions(-) diff --git a/src/qSlicerShapePopulationViewerModuleWidget.cxx b/src/qSlicerShapePopulationViewerModuleWidget.cxx index 4b1592e..7f2048c 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.cxx +++ b/src/qSlicerShapePopulationViewerModuleWidget.cxx @@ -172,33 +172,16 @@ 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 - << d->ShapePopulationWidget->actionOpen_SRep_Files - << d->ShapePopulationWidget->actionOpen_Fiducial_Files - ) - { - 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_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 diff --git a/src/qSlicerShapePopulationViewerModuleWidget.ui b/src/qSlicerShapePopulationViewerModuleWidget.ui index 0730cc2..a935283 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.ui +++ b/src/qSlicerShapePopulationViewerModuleWidget.ui @@ -6,14 +6,14 @@ 0 0 - 525 - 319 + 438 + 450 Form - + @@ -50,12 +50,130 @@ + + + + + + + 0 + 0 + + + + Delete Selection + + + + + + + + 0 + 0 + + + + Delete All + + + + + Import - + + + + + + 0 + 0 + + + + Add VTK/VTP Files + + + + + + + + 0 + 0 + + + + Add S-Rep Files + + + + + + + + 0 + 0 + + + + Load CSV + + + + + + + 0 + + + 1000.000000000000000 + + + + + + + + 0 + 0 + + + + Load Time Series + + + + + + + + 0 + 0 + + + + Add Directory + + + + + + + + 0 + 0 + + + + Add Fiducial Files + + + @@ -64,8 +182,7 @@ Export - - + @@ -94,8 +211,8 @@ - 0 - 0 + 13 + 108 @@ -120,6 +237,11 @@
ctkCollapsibleButton.h
1 + + ctkSliderWidget + QWidget +
ctkSliderWidget.h
+
From 03461f0d545ae3b41d0b91755423a4731ec0ffe9 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Mon, 25 Mar 2024 17:29:41 -0400 Subject: [PATCH 11/21] ENH: make csv loader accept new s-rep format --- src/CSVloaderQT.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CSVloaderQT.cxx b/src/CSVloaderQT.cxx index f05cb09..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") && !QFilePath.endsWith(".xml")) + 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/xml 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()) From b1d8aa71eabc0115d688fa57df9cb0e0ee5640ad Mon Sep 17 00:00:00 2001 From: Ye Han Date: Mon, 25 Mar 2024 18:17:09 -0400 Subject: [PATCH 12/21] ENH: Intial commit for visualizing time series data --- src/CMakeLists.txt | 3 + src/ShapePopulationQT.cxx | 45 +++++++++++ src/ShapePopulationQT.h | 5 ++ src/ShapePopulationQT.ui | 5 ++ ...licerShapePopulationViewerModuleWidget.cxx | 1 + src/timeSeriesLoaderQT.cxx | 80 +++++++++++++++++++ src/timeSeriesLoaderQT.h | 44 ++++++++++ src/timeSeriesLoaderQT.ui | 78 ++++++++++++++++++ 8 files changed, 261 insertions(+) create mode 100644 src/timeSeriesLoaderQT.cxx create mode 100644 src/timeSeriesLoaderQT.h create mode 100644 src/timeSeriesLoaderQT.ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3b9602c..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 diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index 69c62bb..0cc6acd 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -23,6 +23,7 @@ ShapePopulationQT::ShapePopulationQT(QWidget* parent) : QWidget(parent) 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); @@ -87,7 +88,9 @@ ShapePopulationQT::ShapePopulationQT(QWidget* parent) : QWidget(parent) 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(QFileInfoList)),this,SLOT(slot_timeSeriesSelected(QFileInfoList))); connect(actionDelete,SIGNAL(triggered()),this,SLOT(deleteSelection())); connect(actionDelete_All,SIGNAL(triggered()),this,SLOT(deleteAll())); connect(actionCameraConfig,SIGNAL(triggered()),this,SLOT(showCameraConfigWindow())); @@ -168,6 +171,7 @@ ShapePopulationQT::~ShapePopulationQT() delete m_cameraDialog; delete m_backgroundDialog; delete m_CSVloaderDialog; + delete m_timeSeriesLoaderDialog; delete m_customizeColorMapByDirectionDialog; } @@ -293,6 +297,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 @@ -441,6 +459,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) { @@ -448,6 +487,12 @@ void ShapePopulationQT::slot_itemsSelected(QFileInfoList fileList) if(!fileList.isEmpty()) this->CreateWidgets(fileList); } +void ShapePopulationQT::slot_timeSeriesSelected(QFileInfoList fileList) +{ + // Display widgets + if(!fileList.isEmpty()) this->CreateWidgets(fileList); +} + void ShapePopulationQT::deleteAll() { //clear any Content from the layout diff --git a/src/ShapePopulationQT.h b/src/ShapePopulationQT.h index 907d1aa..0a9c4d0 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 @@ -51,6 +52,7 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S 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); @@ -74,6 +76,7 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S cameraDialogQT * m_cameraDialog; backgroundDialogQT * m_backgroundDialog; CSVloaderQT * m_CSVloaderDialog; + timeSeriesLoaderQT * m_timeSeriesLoaderDialog; customizeColorMapByDirectionDialogQT* m_customizeColorMapByDirectionDialog; void CreateWidgets(const QFileInfoList& files); @@ -114,7 +117,9 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S void openSRepFiles(); void openFiducialFiles(); void loadCSV(); + void loadTimeSeries(); void slot_itemsSelected(QFileInfoList fileList); + void slot_timeSeriesSelected(QFileInfoList fileList); void deleteAll(); void deleteSelection(); diff --git a/src/ShapePopulationQT.ui b/src/ShapePopulationQT.ui index bdb7bce..0607ac6 100644 --- a/src/ShapePopulationQT.ui +++ b/src/ShapePopulationQT.ui @@ -1656,6 +1656,11 @@ p, li { white-space: pre-wrap; } Load CSV Files
+ + + Load Time Series + + Open S-Rep Files diff --git a/src/qSlicerShapePopulationViewerModuleWidget.cxx b/src/qSlicerShapePopulationViewerModuleWidget.cxx index 7f2048c..7acec3f 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.cxx +++ b/src/qSlicerShapePopulationViewerModuleWidget.cxx @@ -180,6 +180,7 @@ void qSlicerShapePopulationViewerModuleWidget::setup() 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); diff --git a/src/timeSeriesLoaderQT.cxx b/src/timeSeriesLoaderQT.cxx new file mode 100644 index 0000000..4eef156 --- /dev/null +++ b/src/timeSeriesLoaderQT.cxx @@ -0,0 +1,80 @@ +#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 + vtkSmartPointer tableView = vtkSmartPointer::New(); + tableView->AddRepresentationFromInput(m_table); + tableView->SetSelectionBehavior(0); //to select single items + 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 + vtkSmartPointer items = vtkSmartPointer::New(); + m_tableView->GetSelectedItems(items); + + //Get selected Items + QFileInfoList fileList; + for(int i = 0 ; i < items->GetNumberOfTuples(); i++) + { + double* cell = items->GetTuple(i); + vtkVariant test = m_table->GetValue(cell[0],cell[1]); + QString relativePath = test.ToString().c_str(); + fileList.append(QFileInfo(m_directory,relativePath)); + } + + // Control the files format + for (int i = 0; i < fileList.size(); i++) + { + QString QFilePath = fileList[i].absoluteFilePath(); + if (!QFilePath.endsWith(".vtk") && !QFilePath.endsWith(".vtp") && !QFilePath.endsWith(".xml")) + { + fileList.removeAt(i); + i--; + std::ostringstream strs; + strs << QFilePath.toStdString() << std::endl + << "This is not a vtk/vtp/xml file."<< std::endl; + QMessageBox::critical(this,"Wrong file format",QString(strs.str().c_str()), QMessageBox::Ok); + } + else if(!fileList[i].exists()) + { + fileList.removeAt(i); + i--; + 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(fileList); + +} diff --git a/src/timeSeriesLoaderQT.h b/src/timeSeriesLoaderQT.h new file mode 100644 index 0000000..5ca2ab1 --- /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(QFileInfoList fileList); + +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..50cb72f --- /dev/null +++ b/src/timeSeriesLoaderQT.ui @@ -0,0 +1,78 @@ + + + timeSeriesLoaderQT + + + + 0 + 0 + 1024 + 311 + + + + Dialog + + + + + 30 + 260 + 964 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 30 + 30 + 964 + 200 + + + + + + + + buttonBox + accepted() + timeSeriesLoaderQT + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + timeSeriesLoaderQT + reject() + + + 316 + 260 + + + 286 + 274 + + + + + From e5c8ca337f564d95d2fa5dcfb01c72492a7aa665 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Tue, 26 Mar 2024 14:37:42 -0400 Subject: [PATCH 13/21] ENH: timeSeriesLoader visualizes first frames --- src/ShapePopulationQT.cxx | 327 +++++++++++++++++++------------------ src/ShapePopulationQT.h | 3 +- src/timeSeriesLoaderQT.cxx | 71 ++++---- src/timeSeriesLoaderQT.h | 2 +- src/timeSeriesLoaderQT.ui | 41 ++--- 5 files changed, 227 insertions(+), 217 deletions(-) diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index 0cc6acd..78d1ced 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -90,7 +90,7 @@ ShapePopulationQT::ShapePopulationQT(QWidget* parent) : QWidget(parent) 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(QFileInfoList)),this,SLOT(slot_timeSeriesSelected(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())); @@ -487,10 +487,25 @@ void ShapePopulationQT::slot_itemsSelected(QFileInfoList fileList) if(!fileList.isEmpty()) this->CreateWidgets(fileList); } -void ShapePopulationQT::slot_timeSeriesSelected(QFileInfoList fileList) +void ShapePopulationQT::slot_timeSeriesSelected(QList timeSeries) { // Display widgets - if(!fileList.isEmpty()) this->CreateWidgets(fileList); + if(!timeSeries.isEmpty()) + { + // todo: store new time series and reset time step. + QFileInfoList fileList0; + for (auto fileList : timeSeries) fileList0.append(fileList.at(0)); + this->CreateWidgets(fileList0); + } +} + +void ShapePopulationQT::slot_timeIndicesChanged(int index) +{ + // Update widgets + // this->m_timeSeriesLoaderDialog->m_tableView // filelist + // m_meshList; + initializationAllWidgets(); + computeCommonAttributes(); } void ShapePopulationQT::deleteAll() @@ -513,11 +528,11 @@ void ShapePopulationQT::deleteAll() m_optionsActions->setDisabled(true); //Initialize Menu actions - 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"); +// 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(); @@ -550,189 +565,187 @@ 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_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]) - { - this->displayColorMapByMagnitude(true); - } - else if(m_displayColorMapByDirection[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->displayColorMapByDirection(true); + this->displayVectorsByMagnitude(true); } - // VECTORS previous selected - if(m_displayVectors[j]) + else if(m_displayVectorsByDirection[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->displayVectorsByDirection(true); } - pushButton_VIEW_reset->click(); - } - m_selectedIndex.clear(); + 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); - 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(); + } } @@ -1314,11 +1327,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_SRep_Files->setText("Add S-Rep files"); - this->actionOpen_Fiducial_Files->setText("Add Fiducial files"); +// 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(); diff --git a/src/ShapePopulationQT.h b/src/ShapePopulationQT.h index 0a9c4d0..068e032 100644 --- a/src/ShapePopulationQT.h +++ b/src/ShapePopulationQT.h @@ -119,7 +119,8 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S void loadCSV(); void loadTimeSeries(); void slot_itemsSelected(QFileInfoList fileList); - void slot_timeSeriesSelected(QFileInfoList fileList); + void slot_timeSeriesSelected(QList fileList); + void slot_timeIndicesChanged(int index); void deleteAll(); void deleteSelection(); diff --git a/src/timeSeriesLoaderQT.cxx b/src/timeSeriesLoaderQT.cxx index 4eef156..0217380 100644 --- a/src/timeSeriesLoaderQT.cxx +++ b/src/timeSeriesLoaderQT.cxx @@ -22,9 +22,9 @@ void timeSeriesLoaderQT::displayTable(vtkSmartPointer table, QDir dire m_directory = directory; //Translate to QT - vtkSmartPointer tableView = vtkSmartPointer::New(); + vtkNew tableView; tableView->AddRepresentationFromInput(m_table); - tableView->SetSelectionBehavior(0); //to select single items + 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(); @@ -38,43 +38,50 @@ void timeSeriesLoaderQT::displayTable(vtkSmartPointer table, QDir dire void timeSeriesLoaderQT::on_buttonBox_accepted() { //Get rows and columns - vtkSmartPointer items = vtkSmartPointer::New(); - m_tableView->GetSelectedItems(items); + vtkNew rows; + m_tableView->GetSelectedItems(rows); + auto columns = m_table->GetNumberOfColumns(); - //Get selected Items - QFileInfoList fileList; - for(int i = 0 ; i < items->GetNumberOfTuples(); i++) + //Get selected rows + QList timeSeries; + for(int i = 0 ; i < rows->GetNumberOfTuples(); i++) { - double* cell = items->GetTuple(i); - vtkVariant test = m_table->GetValue(cell[0],cell[1]); - QString relativePath = test.ToString().c_str(); - fileList.append(QFileInfo(m_directory,relativePath)); + 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 < fileList.size(); i++) + for (int i = 0; i < timeSeries.size(); i++) { - QString QFilePath = fileList[i].absoluteFilePath(); - if (!QFilePath.endsWith(".vtk") && !QFilePath.endsWith(".vtp") && !QFilePath.endsWith(".xml")) - { - fileList.removeAt(i); - i--; - std::ostringstream strs; - strs << QFilePath.toStdString() << std::endl - << "This is not a vtk/vtp/xml file."<< std::endl; - QMessageBox::critical(this,"Wrong file format",QString(strs.str().c_str()), QMessageBox::Ok); - } - else if(!fileList[i].exists()) + for (int j = 0; j < timeSeries.at(i).size(); j++) { - fileList.removeAt(i); - i--; - 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); + 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(fileList); - + emit sig_timeSeriesSelected(timeSeries); } diff --git a/src/timeSeriesLoaderQT.h b/src/timeSeriesLoaderQT.h index 5ca2ab1..87e896f 100644 --- a/src/timeSeriesLoaderQT.h +++ b/src/timeSeriesLoaderQT.h @@ -30,7 +30,7 @@ private slots: void on_buttonBox_accepted(); signals: - void sig_timeSeriesSelected(QFileInfoList fileList); + void sig_timeSeriesSelected(QList timeSeries); private: vtkSmartPointer m_tableView; diff --git a/src/timeSeriesLoaderQT.ui b/src/timeSeriesLoaderQT.ui index 50cb72f..3737f61 100644 --- a/src/timeSeriesLoaderQT.ui +++ b/src/timeSeriesLoaderQT.ui @@ -13,32 +13,21 @@ Dialog - - - - 30 - 260 - 964 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 30 - 30 - 964 - 200 - - - + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + From 456890d910bac22e0cb7c8d892d32bb413464774 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Tue, 26 Mar 2024 17:16:22 -0400 Subject: [PATCH 14/21] ENH: connect time series slider signals --- src/ShapePopulationQT.cxx | 16 ++++++++++------ src/ShapePopulationQT.h | 4 +++- src/qSlicerShapePopulationViewerModuleWidget.cxx | 3 +++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index 78d1ced..0878b9d 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -20,6 +20,7 @@ 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); @@ -492,20 +493,20 @@ void ShapePopulationQT::slot_timeSeriesSelected(QList timeSeries) // Display widgets if(!timeSeries.isEmpty()) { - // todo: store new time series and reset time step. + // todo: check time series length (and reset time step). + m_timeSeries.append(timeSeries); + QFileInfoList fileList0; for (auto fileList : timeSeries) fileList0.append(fileList.at(0)); this->CreateWidgets(fileList0); + emit sig_loadTimeSeries(true); } } -void ShapePopulationQT::slot_timeIndicesChanged(int index) +void ShapePopulationQT::slot_timeIndicesChanged(double index) { // Update widgets - // this->m_timeSeriesLoaderDialog->m_tableView // filelist - // m_meshList; - initializationAllWidgets(); - computeCommonAttributes(); + qInfo("%d", (int)index); } void ShapePopulationQT::deleteAll() @@ -540,6 +541,7 @@ void ShapePopulationQT::deleteAll() m_selectedIndex.clear(); m_windowsList.clear(); m_widgetList.clear(); + m_timeSeries.clear(); m_numberOfMeshes = 0; axisColorStruct * axisColor = new axisColorStruct; @@ -557,12 +559,14 @@ void ShapePopulationQT::deleteAll() if(m_customizeColorMapByDirectionDialog->isVisible()) m_customizeColorMapByDirectionDialog->hide(); emit sig_axisColor_value(axisColor, false); + emit sig_loadTimeSeries(false); m_axisColor.clear(); } void ShapePopulationQT::deleteSelection() { + //todo: delete selected time series from m_timeSeries.clear(); if(m_selectedIndex.size() == 0) return; this->scrollArea->setVisible(false); diff --git a/src/ShapePopulationQT.h b/src/ShapePopulationQT.h index 068e032..ee66944 100644 --- a/src/ShapePopulationQT.h +++ b/src/ShapePopulationQT.h @@ -73,6 +73,7 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S QString m_pathSphere; typedef QVTKOpenGLNativeWidget VTKWidgetType; std::vector m_widgetList; + QList m_timeSeries; cameraDialogQT * m_cameraDialog; backgroundDialogQT * m_backgroundDialog; CSVloaderQT * m_CSVloaderDialog; @@ -120,7 +121,7 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S void loadTimeSeries(); void slot_itemsSelected(QFileInfoList fileList); void slot_timeSeriesSelected(QList fileList); - void slot_timeIndicesChanged(int index); + void slot_timeIndicesChanged(double index); void deleteAll(); void deleteSelection(); @@ -239,6 +240,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); private: QActionGroup* m_exportActions; diff --git a/src/qSlicerShapePopulationViewerModuleWidget.cxx b/src/qSlicerShapePopulationViewerModuleWidget.cxx index 7acec3f..1d90a50 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.cxx +++ b/src/qSlicerShapePopulationViewerModuleWidget.cxx @@ -203,6 +203,7 @@ void qSlicerShapePopulationViewerModuleWidget::setup() bool exportVisible = false; #endif d->ExportCTKCollapsibleButton->setVisible(exportVisible); + d->SliderWidget_Load_Time_Series->setEnabled(false); // Settings foreach(QAction* action, QList() @@ -217,6 +218,8 @@ void qSlicerShapePopulationViewerModuleWidget::setup() } connect(d->ModelLoadPushButton, SIGNAL(clicked()), this, SLOT(loadSelectedModel())); + connect(d->SliderWidget_Load_Time_Series, SIGNAL(valueChanged(double)), d->ShapePopulationWidget, SLOT(slot_timeIndicesChanged(double))); + connect(d->ShapePopulationWidget, SIGNAL(sig_loadTimeSeries(bool)), d->SliderWidget_Load_Time_Series, SLOT(setEnabled(bool))); } //----------------------------------------------------------------------------- From 457ec66b1555ec72ea914267723185c2ae9dd4b9 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Fri, 29 Mar 2024 17:19:39 -0400 Subject: [PATCH 15/21] ENH: Add initial time series visualization functionality. --- src/ShapePopulationBase.cxx | 89 +++++++++++++++++++ src/ShapePopulationBase.h | 1 + src/ShapePopulationData.cxx | 1 + src/ShapePopulationQT.cxx | 33 +++++-- src/ShapePopulationQT.h | 1 + ...SlicerShapePopulationViewerModuleWidget.ui | 64 +++++++------ 6 files changed, 159 insertions(+), 30 deletions(-) diff --git a/src/ShapePopulationBase.cxx b/src/ShapePopulationBase.cxx index 2a27047..d384e23 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 + vtkSmartPointer mapper = vtkSmartPointer::New(); + mapper->SetInputData(m_meshList[i]->GetPolyData()); + + //ACTOR + vtkSmartPointer actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + + /* VECTORS */ + //Arrow + vtkSmartPointer arrow = vtkSmartPointer::New(); + 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 + vtkSmartPointer glyphMapper = vtkSmartPointer::New(); + glyphMapper->SetInputData(m_glyphList[i]->GetOutput()); + vtkSmartPointer glyphActor = vtkSmartPointer::New(); + 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) + vtkSmartPointer attributeName = vtkSmartPointer::New(); + attributeName->SetLinearFontScaleFactor(2); + attributeName->SetNonlinearFontScaleFactor(1); + attributeName->SetMaximumFontSize(15); + attributeName->SetText(0," "); + attributeName->GetTextProperty()->SetColor(m_labelColor); + renderer->AddViewProp(attributeName); + + // SCALAR BAR + vtkSmartPointer scalarBar = vtkSmartPointer::New(); + scalarBar->SetLookupTable(mapper->GetLookupTable()); + // scalarBar->SetTitle("Title"); + scalarBar->SetNumberOfLabels(5); + scalarBar->SetMaximumWidthInPixels(60); + + vtkSmartPointer LabelProperty = vtkSmartPointer::New(); + 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 be39a04..f0ba870 100644 --- a/src/ShapePopulationData.cxx +++ b/src/ShapePopulationData.cxx @@ -134,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++) { diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index 0878b9d..693be64 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -493,9 +493,22 @@ void ShapePopulationQT::slot_timeSeriesSelected(QList timeSeries) // Display widgets if(!timeSeries.isEmpty()) { - // todo: check time series length (and reset time step). - m_timeSeries.append(timeSeries); - + 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; + } + } + // todo: sync ctksliderwidget from module + slot_timeIndicesChanged(0.0); + } + // todo: check size of each individual time series + for(auto timeSerie : timeSeries) + m_timeSeries.append(timeSerie); QFileInfoList fileList0; for (auto fileList : timeSeries) fileList0.append(fileList.at(0)); this->CreateWidgets(fileList0); @@ -506,7 +519,16 @@ void ShapePopulationQT::slot_timeSeriesSelected(QList timeSeries) void ShapePopulationQT::slot_timeIndicesChanged(double index) { // Update widgets - qInfo("%d", (int)index); + auto index_i = (int) index; + if (index_i >= 0 && index_i < (int) m_timeSeries[0].size()) + { + for (unsigned int i = 0; i < m_numberOfMeshes; i++) + { + m_meshList[i]->ReadMesh(m_timeSeries[i][index_i].absoluteFilePath().toStdString()); + } + } + //todo: more complete update to the rendering windows. + this->UpdateWindows(); } void ShapePopulationQT::deleteAll() @@ -566,7 +588,7 @@ void ShapePopulationQT::deleteAll() void ShapePopulationQT::deleteSelection() { - //todo: delete selected time series from m_timeSeries.clear(); + //todo: delete selected time series from m_timeSeries if(m_selectedIndex.size() == 0) return; this->scrollArea->setVisible(false); @@ -585,6 +607,7 @@ void ShapePopulationQT::deleteSelection() { 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 diff --git a/src/ShapePopulationQT.h b/src/ShapePopulationQT.h index ee66944..290dcb8 100644 --- a/src/ShapePopulationQT.h +++ b/src/ShapePopulationQT.h @@ -73,6 +73,7 @@ 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; diff --git a/src/qSlicerShapePopulationViewerModuleWidget.ui b/src/qSlicerShapePopulationViewerModuleWidget.ui index a935283..d4aa457 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.ui +++ b/src/qSlicerShapePopulationViewerModuleWidget.ui @@ -86,8 +86,8 @@ Import - - + + 0 @@ -95,12 +95,12 @@ - Add VTK/VTP Files + Add Directory - - + + 0 @@ -108,7 +108,7 @@ - Add S-Rep Files + Add VTK/VTP Files @@ -125,18 +125,8 @@ - - - - 0 - - - 1000.000000000000000 - - - - - + + 0 @@ -144,12 +134,12 @@ - Load Time Series + Add S-Rep Files - - + + 0 @@ -157,12 +147,12 @@ - Add Directory + Add Fiducial Files - - + + 0 @@ -170,10 +160,34 @@ - Add Fiducial Files + Load Time Series + + + + + + Time Step + + + + + + + 0 + + + 1000.000000000000000 + + + false + + + + + From 683688fb153fdfed0b2a9159a8b36916fc6a6464 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Mon, 8 Apr 2024 16:34:28 -0400 Subject: [PATCH 16/21] ENH: Set slider widget on load/delete time series --- src/ShapePopulationQT.cxx | 4 ++-- src/ShapePopulationQT.h | 2 +- src/qSlicerShapePopulationViewerModuleWidget.cxx | 10 +++++++++- src/qSlicerShapePopulationViewerModuleWidget.h | 1 + 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index 693be64..69245af 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -512,7 +512,7 @@ void ShapePopulationQT::slot_timeSeriesSelected(QList timeSeries) QFileInfoList fileList0; for (auto fileList : timeSeries) fileList0.append(fileList.at(0)); this->CreateWidgets(fileList0); - emit sig_loadTimeSeries(true); + emit sig_loadTimeSeries(true, m_timeSeries.at(0).size()); } } @@ -581,7 +581,7 @@ void ShapePopulationQT::deleteAll() if(m_customizeColorMapByDirectionDialog->isVisible()) m_customizeColorMapByDirectionDialog->hide(); emit sig_axisColor_value(axisColor, false); - emit sig_loadTimeSeries(false); + emit sig_loadTimeSeries(false, 0); m_axisColor.clear(); } diff --git a/src/ShapePopulationQT.h b/src/ShapePopulationQT.h index 290dcb8..a8d364b 100644 --- a/src/ShapePopulationQT.h +++ b/src/ShapePopulationQT.h @@ -241,7 +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); + void sig_loadTimeSeries(bool slider_enabled, unsigned int total_time_step); private: QActionGroup* m_exportActions; diff --git a/src/qSlicerShapePopulationViewerModuleWidget.cxx b/src/qSlicerShapePopulationViewerModuleWidget.cxx index 1d90a50..a5a25da 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.cxx +++ b/src/qSlicerShapePopulationViewerModuleWidget.cxx @@ -219,7 +219,7 @@ void qSlicerShapePopulationViewerModuleWidget::setup() connect(d->ModelLoadPushButton, SIGNAL(clicked()), this, SLOT(loadSelectedModel())); connect(d->SliderWidget_Load_Time_Series, SIGNAL(valueChanged(double)), d->ShapePopulationWidget, SLOT(slot_timeIndicesChanged(double))); - connect(d->ShapePopulationWidget, SIGNAL(sig_loadTimeSeries(bool)), d->SliderWidget_Load_Time_Series, SLOT(setEnabled(bool))); + connect(d->ShapePopulationWidget, SIGNAL(sig_loadTimeSeries(bool, unsigned int)), this, SLOT(onLoadTimeSeries(bool, unsigned int))); } //----------------------------------------------------------------------------- @@ -330,3 +330,11 @@ void qSlicerShapePopulationViewerModuleWidget::onMRMLNodeModified(vtkObject *cal this->loadModel(modelNode); qvtkDisconnect(modelNode, vtkCommand::ModifiedEvent, this, SLOT(onMRMLNodeModified(vtkObject*))); } + +void qSlicerShapePopulationViewerModuleWidget::onLoadTimeSeries(bool slider_enabled, unsigned int total_time_step) +{ + Q_D(qSlicerShapePopulationViewerModuleWidget); + d->SliderWidget_Load_Time_Series->setMinimum(0.0); + d->SliderWidget_Load_Time_Series->setMaximum((double)total_time_step - 1.0); + d->SliderWidget_Load_Time_Series->setEnabled(slider_enabled); +} \ No newline at end of file diff --git a/src/qSlicerShapePopulationViewerModuleWidget.h b/src/qSlicerShapePopulationViewerModuleWidget.h index b610e89..5a68e8b 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.h +++ b/src/qSlicerShapePopulationViewerModuleWidget.h @@ -65,6 +65,7 @@ public slots: protected slots: void onMRMLSceneNodeAddedEvent(vtkObject*,vtkObject*); void onMRMLNodeModified(vtkObject*); + void onLoadTimeSeries(bool slider_enabled, unsigned int total_time_step); protected: QScopedPointer d_ptr; From 61ba3b504192695b22729d0be2fa3f8c996595d5 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Mon, 8 Apr 2024 17:09:39 -0400 Subject: [PATCH 17/21] ENH: Sync slider, check time steps on loading time series --- src/ShapePopulationQT.cxx | 12 +++++++++--- src/qSlicerShapePopulationViewerModuleWidget.cxx | 1 + src/qSlicerShapePopulationViewerModuleWidget.ui | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index 69245af..2c5a2f3 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -503,12 +503,18 @@ void ShapePopulationQT::slot_timeSeriesSelected(QList timeSeries) return; } } - // todo: sync ctksliderwidget from module - slot_timeIndicesChanged(0.0); } - // todo: check size of each individual time series + 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); diff --git a/src/qSlicerShapePopulationViewerModuleWidget.cxx b/src/qSlicerShapePopulationViewerModuleWidget.cxx index a5a25da..53e2dbc 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.cxx +++ b/src/qSlicerShapePopulationViewerModuleWidget.cxx @@ -336,5 +336,6 @@ void qSlicerShapePopulationViewerModuleWidget::onLoadTimeSeries(bool slider_enab Q_D(qSlicerShapePopulationViewerModuleWidget); d->SliderWidget_Load_Time_Series->setMinimum(0.0); d->SliderWidget_Load_Time_Series->setMaximum((double)total_time_step - 1.0); + d->SliderWidget_Load_Time_Series->setValue(0.0); d->SliderWidget_Load_Time_Series->setEnabled(slider_enabled); } \ No newline at end of file diff --git a/src/qSlicerShapePopulationViewerModuleWidget.ui b/src/qSlicerShapePopulationViewerModuleWidget.ui index d4aa457..757e0e8 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.ui +++ b/src/qSlicerShapePopulationViewerModuleWidget.ui @@ -182,7 +182,7 @@ 1000.000000000000000 - false + true From 721d7b97f87316330a1e6643e4a4182ca615488b Mon Sep 17 00:00:00 2001 From: Ye Han Date: Wed, 15 May 2024 14:09:50 -0400 Subject: [PATCH 18/21] ENH: keep attribute and colormap when changing time index --- src/ShapePopulationBase.cxx | 14 +++++++------- src/ShapePopulationQT.cxx | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/ShapePopulationBase.cxx b/src/ShapePopulationBase.cxx index d384e23..3b9dc17 100644 --- a/src/ShapePopulationBase.cxx +++ b/src/ShapePopulationBase.cxx @@ -263,7 +263,7 @@ void ShapePopulationBase::UpdateWindows() for (i = 0; i < m_meshList.size(); i++) { //MAPPER - vtkSmartPointer mapper = vtkSmartPointer::New(); + vtkNew mapper; mapper->SetInputData(m_meshList[i]->GetPolyData()); //ACTOR @@ -272,7 +272,7 @@ void ShapePopulationBase::UpdateWindows() /* VECTORS */ //Arrow - vtkSmartPointer arrow = vtkSmartPointer::New(); + vtkNew arrow; m_glyphList[i]->SetSourceConnection(arrow->GetOutputPort()); m_glyphList[i]->SetInputData(m_meshList[i]->GetPolyData()); m_glyphList[i]->ScalingOn(); @@ -284,9 +284,9 @@ void ShapePopulationBase::UpdateWindows() m_glyphList[i]->Update(); //Mapper & Actor - vtkSmartPointer glyphMapper = vtkSmartPointer::New(); + vtkNew glyphMapper; glyphMapper->SetInputData(m_glyphList[i]->GetOutput()); - vtkSmartPointer glyphActor = vtkSmartPointer::New(); + vtkNew glyphActor; glyphActor->SetMapper(glyphMapper); /* END VECTORS */ @@ -315,7 +315,7 @@ void ShapePopulationBase::UpdateWindows() renderer->AddViewProp(fileName); //ANNOTATIONS (attribute name) - vtkSmartPointer attributeName = vtkSmartPointer::New(); + vtkNew attributeName; attributeName->SetLinearFontScaleFactor(2); attributeName->SetNonlinearFontScaleFactor(1); attributeName->SetMaximumFontSize(15); @@ -324,13 +324,13 @@ void ShapePopulationBase::UpdateWindows() renderer->AddViewProp(attributeName); // SCALAR BAR - vtkSmartPointer scalarBar = vtkSmartPointer::New(); + vtkNew scalarBar; scalarBar->SetLookupTable(mapper->GetLookupTable()); // scalarBar->SetTitle("Title"); scalarBar->SetNumberOfLabels(5); scalarBar->SetMaximumWidthInPixels(60); - vtkSmartPointer LabelProperty = vtkSmartPointer::New(); + vtkNew LabelProperty; LabelProperty->SetFontSize(12); LabelProperty->SetColor(m_labelColor); diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index 2c5a2f3..6b5fd08 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -533,8 +533,19 @@ void ShapePopulationQT::slot_timeIndicesChanged(double index) m_meshList[i]->ReadMesh(m_timeSeries[i][index_i].absoluteFilePath().toStdString()); } } - //todo: more complete update to the rendering windows. this->UpdateWindows(); + + /* ATTRIBUTES & COLORBARS */ + ShapePopulationBase::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()); } void ShapePopulationQT::deleteAll() @@ -594,7 +605,6 @@ void ShapePopulationQT::deleteAll() void ShapePopulationQT::deleteSelection() { - //todo: delete selected time series from m_timeSeries if(m_selectedIndex.size() == 0) return; this->scrollArea->setVisible(false); From 4c72a8fe990ca6d3f62a81de73451fda2a4515d6 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Mon, 20 May 2024 15:44:19 -0400 Subject: [PATCH 19/21] ENH: align shapes and keep cam config when loading/changing time index --- src/ShapePopulationQT.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index 6b5fd08..c654b3c 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -518,6 +518,8 @@ void ShapePopulationQT::slot_timeSeriesSelected(QList timeSeries) 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()); } } @@ -534,6 +536,8 @@ void ShapePopulationQT::slot_timeIndicesChanged(double index) } } this->UpdateWindows(); + comboBox_alignment->setCurrentIndex(1); + on_comboBox_alignment_currentIndexChanged(); /* ATTRIBUTES & COLORBARS */ ShapePopulationBase::SelectAll(); From 84a755530ba31e7e202b9147f05760529a438001 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Wed, 26 Jun 2024 16:06:36 -0400 Subject: [PATCH 20/21] ENH: Add play button actions --- src/ShapePopulationQT.cxx | 13 +- src/ShapePopulationQT.h | 2 +- src/ShapePopulationViewer.qrc | 5 + ...licerShapePopulationViewerModuleWidget.cxx | 59 +++++++-- ...qSlicerShapePopulationViewerModuleWidget.h | 3 + ...SlicerShapePopulationViewerModuleWidget.ui | 117 +++++++++++------- src/resources/pqVcrBack.svg | 1 + src/resources/pqVcrFirst.svg | 1 + src/resources/pqVcrForward.svg | 1 + src/resources/pqVcrLast.svg | 1 + src/resources/pqVcrPlay.svg | 1 + 11 files changed, 144 insertions(+), 60 deletions(-) create mode 100644 src/resources/pqVcrBack.svg create mode 100644 src/resources/pqVcrFirst.svg create mode 100644 src/resources/pqVcrForward.svg create mode 100644 src/resources/pqVcrLast.svg create mode 100644 src/resources/pqVcrPlay.svg diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index c654b3c..f30b969 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -524,15 +524,14 @@ void ShapePopulationQT::slot_timeSeriesSelected(QList timeSeries) } } -void ShapePopulationQT::slot_timeIndicesChanged(double index) +void ShapePopulationQT::slot_timeIndicesChanged(int index) { // Update widgets - auto index_i = (int) index; - if (index_i >= 0 && index_i < (int) m_timeSeries[0].size()) + 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_i].absoluteFilePath().toStdString()); + m_meshList[i]->ReadMesh(m_timeSeries[i][index].absoluteFilePath().toStdString()); } } this->UpdateWindows(); @@ -540,12 +539,12 @@ void ShapePopulationQT::slot_timeIndicesChanged(double index) on_comboBox_alignment_currentIndexChanged(); /* ATTRIBUTES & COLORBARS */ - ShapePopulationBase::SelectAll(); + 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); @@ -602,7 +601,7 @@ void ShapePopulationQT::deleteAll() if(m_customizeColorMapByDirectionDialog->isVisible()) m_customizeColorMapByDirectionDialog->hide(); emit sig_axisColor_value(axisColor, false); - emit sig_loadTimeSeries(false, 0); + emit sig_loadTimeSeries(false, 1); m_axisColor.clear(); } diff --git a/src/ShapePopulationQT.h b/src/ShapePopulationQT.h index a8d364b..acaa08f 100644 --- a/src/ShapePopulationQT.h +++ b/src/ShapePopulationQT.h @@ -122,7 +122,7 @@ class ShapePopulationQT : public QWidget, public Ui::ShapePopulationQT, public S void loadTimeSeries(); void slot_itemsSelected(QFileInfoList fileList); void slot_timeSeriesSelected(QList fileList); - void slot_timeIndicesChanged(double index); + void slot_timeIndicesChanged(int index); void deleteAll(); void deleteSelection(); diff --git a/src/ShapePopulationViewer.qrc b/src/ShapePopulationViewer.qrc index dadeacb..c43dd70 100644 --- a/src/ShapePopulationViewer.qrc +++ b/src/ShapePopulationViewer.qrc @@ -8,5 +8,10 @@ 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/qSlicerShapePopulationViewerModuleWidget.cxx b/src/qSlicerShapePopulationViewerModuleWidget.cxx index 53e2dbc..59f2762 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.cxx +++ b/src/qSlicerShapePopulationViewerModuleWidget.cxx @@ -22,6 +22,7 @@ #include #include #include +#include // MRML includes #include @@ -203,7 +204,7 @@ void qSlicerShapePopulationViewerModuleWidget::setup() bool exportVisible = false; #endif d->ExportCTKCollapsibleButton->setVisible(exportVisible); - d->SliderWidget_Load_Time_Series->setEnabled(false); + d->spinBox_Time_Step->setEnabled(false); // Settings foreach(QAction* action, QList() @@ -217,8 +218,29 @@ 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->SliderWidget_Load_Time_Series, SIGNAL(valueChanged(double)), d->ShapePopulationWidget, SLOT(slot_timeIndicesChanged(double))); + 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))); } @@ -331,11 +353,34 @@ void qSlicerShapePopulationViewerModuleWidget::onMRMLNodeModified(vtkObject *cal qvtkDisconnect(modelNode, vtkCommand::ModifiedEvent, this, SLOT(onMRMLNodeModified(vtkObject*))); } -void qSlicerShapePopulationViewerModuleWidget::onLoadTimeSeries(bool slider_enabled, unsigned int total_time_step) +void qSlicerShapePopulationViewerModuleWidget::onLoadTimeSeries(bool spin_box_enabled, unsigned int total_time_step) { Q_D(qSlicerShapePopulationViewerModuleWidget); - d->SliderWidget_Load_Time_Series->setMinimum(0.0); - d->SliderWidget_Load_Time_Series->setMaximum((double)total_time_step - 1.0); - d->SliderWidget_Load_Time_Series->setValue(0.0); - d->SliderWidget_Load_Time_Series->setEnabled(slider_enabled); + 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 5a68e8b..def1c96 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.h +++ b/src/qSlicerShapePopulationViewerModuleWidget.h @@ -66,6 +66,9 @@ 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 757e0e8..de776e9 100644 --- a/src/qSlicerShapePopulationViewerModuleWidget.ui +++ b/src/qSlicerShapePopulationViewerModuleWidget.ui @@ -6,7 +6,7 @@ 0 0 - 438 + 451 450 @@ -86,8 +86,8 @@ Import - - + + 0 @@ -95,7 +95,7 @@ - Add Directory + Load Time Series @@ -112,19 +112,6 @@ - - - - - 0 - 0 - - - - Load CSV - - - @@ -151,43 +138,88 @@ - - - - - 0 - 0 - - - - Load Time Series - - - - + - + - Time Step + - - - 0 + + + - - 1000.000000000000000 + + + + + + - - true + + + + + + + + + + + + + + + + + + + + + Time Step + + + Qt::AlignCenter + + + + + + + + + + + 0 + 0 + + + + Add Directory + + + + + + + + 0 + 0 + + + + Load CSV + + + @@ -251,11 +283,6 @@
ctkCollapsibleButton.h
1 - - ctkSliderWidget - QWidget -
ctkSliderWidget.h
-
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 @@ + From 6c7a315c1aef5c0032493d3c68a1cddb73d46aa7 Mon Sep 17 00:00:00 2001 From: Ye Han Date: Mon, 4 Nov 2024 13:01:27 -0500 Subject: [PATCH 21/21] BUG: Reset view at each time step. --- src/ShapePopulationQT.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ShapePopulationQT.cxx b/src/ShapePopulationQT.cxx index f30b969..05728f6 100644 --- a/src/ShapePopulationQT.cxx +++ b/src/ShapePopulationQT.cxx @@ -549,6 +549,8 @@ void ShapePopulationQT::slot_timeIndicesChanged(int index) 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()