diff --git a/Libs/Analyze/StudioMesh.cpp b/Libs/Analyze/StudioMesh.cpp index d5e6eb3eb0..d15ac9e5ec 100644 --- a/Libs/Analyze/StudioMesh.cpp +++ b/Libs/Analyze/StudioMesh.cpp @@ -98,7 +98,7 @@ void StudioMesh::interpolate_scalars_to_mesh(std::string name, Eigen::VectorXd p if (num_points != scalar_values.size()) { std::cerr << "Warning, mismatch of points and scalar values (num_points = " << num_points - << ", scalar_values.size() = " << scalar_values.size() << std::endl; + << ", scalar_values.size() = " << scalar_values.size() << ")\n"; return; } diff --git a/Studio/Analysis/AnalysisTool.cpp b/Studio/Analysis/AnalysisTool.cpp index ab001092e1..7c12e1bd93 100644 --- a/Studio/Analysis/AnalysisTool.cpp +++ b/Studio/Analysis/AnalysisTool.cpp @@ -161,6 +161,23 @@ AnalysisTool::AnalysisTool(Preferences& prefs) : preferences_(prefs) { connect(ui_->group_analysis_combo, qOverload(&QComboBox::currentIndexChanged), this, &AnalysisTool::group_analysis_combo_changed); + + // when one is checked, turn the other off, connect these first + connect(ui_->show_difference_to_predicted_scalar, &QPushButton::clicked, this, [this]() { + if (ui_->show_difference_to_predicted_scalar->isChecked()) { + ui_->show_predicted_scalar->setChecked(false); + } + }); + connect(ui_->show_predicted_scalar, &QPushButton::clicked, this, [this]() { + if (ui_->show_predicted_scalar->isChecked()) { + ui_->show_difference_to_predicted_scalar->setChecked(false); + } + }); + + connect(ui_->show_difference_to_predicted_scalar, &QPushButton::clicked, this, + &AnalysisTool::handle_samples_predicted_scalar_options); + connect(ui_->show_predicted_scalar, &QPushButton::clicked, this, + &AnalysisTool::handle_samples_predicted_scalar_options); } //--------------------------------------------------------------------------- @@ -280,6 +297,9 @@ void AnalysisTool::set_session(QSharedPointer session) { ui_->group2_button->setChecked(false); update_difference_particles(); + ui_->show_predicted_scalar->setChecked(false); + ui_->show_difference_to_predicted_scalar->setChecked(false); + connect(ui_->show_good_bad, &QCheckBox::toggled, session_.data(), &Session::set_show_good_bad_particles); } @@ -1091,6 +1111,11 @@ void AnalysisTool::reset_stats() { ui_->pca_scalar_combo->addItem(QString::fromStdString(feature)); } } + bool has_scalars = ui_->pca_scalar_combo->count() > 0; + if (!has_scalars) { + ui_->show_difference_to_predicted_scalar->setChecked(false); + } + ui_->show_difference_to_predicted_scalar->setEnabled(has_scalars); ui_->shape_scalar_groupbox->setVisible(ui_->pca_scalar_combo->count() > 0); } @@ -1303,6 +1328,15 @@ std::string AnalysisTool::get_display_feature_map() { } } + if (get_analysis_mode() == AnalysisTool::MODE_ALL_SAMPLES_C && + ui_->show_difference_to_predicted_scalar->isChecked()) { + return "predicted_scalar_diff"; + } + + if (get_analysis_mode() == AnalysisTool::MODE_ALL_SAMPLES_C && ui_->show_predicted_scalar->isChecked()) { + return "predicted_scalar"; + } + return feature_map_; } @@ -1763,6 +1797,11 @@ void AnalysisTool::change_pca_analysis_type() { ui_->pca_predict_scalar->setEnabled(ui_->pca_scalar_shape_only->isChecked()); ui_->pca_predict_shape->setEnabled(ui_->pca_scalar_only->isChecked()); + if (ui_->pca_predict_scalar->isChecked()) { + // set the feature map to the target feature + session_->set_feature_map(ui_->pca_scalar_combo->currentText().toStdString()); + } + compute_stats(); Q_EMIT pca_update(); } @@ -1785,6 +1824,30 @@ Eigen::VectorXd AnalysisTool::construct_mean_shape() { return mean_shape; } +//--------------------------------------------------------------------------- +void AnalysisTool::handle_samples_predicted_scalar_options() { + if (ui_->show_difference_to_predicted_scalar->isChecked() || ui_->show_predicted_scalar->isChecked()) { + // iterate over all samples, predict scalars, compute the difference and store as a new scalar field + auto shapes = session_->get_non_excluded_shapes(); + for (auto& shape : shapes) { + auto particles = shape->get_particles(); + auto target_feature = ui_->pca_scalar_combo->currentText(); + auto predicted = + ShapeScalarJob::predict_scalars(session_, target_feature, particles.get_combined_global_particles()); + + // load the mesh and feature + shape->get_meshes(session_->get_display_mode(), true); + shape->load_feature(session_->get_display_mode(), target_feature.toStdString()); + auto scalars = shape->get_point_features(target_feature.toStdString()); + // compute difference + auto diff = predicted - scalars; + shape->set_point_features("predicted_scalar_diff", diff); + shape->set_point_features("predicted_scalar", predicted); + } + } + Q_EMIT update_view(); +} + //--------------------------------------------------------------------------- void AnalysisTool::reconstruction_method_changed() { ui_->reconstruction_options->setVisible(ui_->distance_transform_radio_button->isChecked()); diff --git a/Studio/Analysis/AnalysisTool.h b/Studio/Analysis/AnalysisTool.h index 5678850dc2..19921d7735 100644 --- a/Studio/Analysis/AnalysisTool.h +++ b/Studio/Analysis/AnalysisTool.h @@ -197,6 +197,8 @@ class AnalysisTool : public QWidget { //! Compute the mean shape outside of the PCA in case we are using scalars only Eigen::VectorXd construct_mean_shape(); + void handle_samples_predicted_scalar_options(); + Q_SIGNALS: void update_view(); diff --git a/Studio/Analysis/AnalysisTool.ui b/Studio/Analysis/AnalysisTool.ui index 6067da6599..24370ec049 100644 --- a/Studio/Analysis/AnalysisTool.ui +++ b/Studio/Analysis/AnalysisTool.ui @@ -535,7 +535,7 @@ QWidget#particles_panel { - 2 + 1 @@ -1089,16 +1089,6 @@ QWidget#particles_panel { Samples - - - - false - - - Sample selection - - - @@ -1109,6 +1099,19 @@ QWidget#particles_panel { + + + + false + + + Show median sample + + + Median + + + @@ -1122,19 +1125,62 @@ QWidget#particles_panel { - - + + + + + + + Show difference to predicted scalar + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Show predicted scalar + + + + + + + + false - Show median sample - - - Median + Sample selection + + + + Qt::Vertical + + + + 20 + 40 + + + + diff --git a/Studio/Data/Session.cpp b/Studio/Data/Session.cpp index b7576ce6ba..26b335d644 100644 --- a/Studio/Data/Session.cpp +++ b/Studio/Data/Session.cpp @@ -1138,11 +1138,23 @@ bool Session::get_image_thickness_feature() { return params_.get("image_thicknes void Session::set_feature_map(std::string feature_map) { if (feature_map != get_feature_map() || is_loading()) { params_.set("feature_map", feature_map); + Q_EMIT feature_map_changed(); } } //--------------------------------------------------------------------------- -std::string Session::get_feature_map() { return params_.get("feature_map", ""); } +std::string Session::get_feature_map() { + std::string feature_map = params_.get("feature_map", ""); + + // confirm that this is a valid feature map + auto feature_maps = get_project()->get_feature_names(); + for (const std::string& feature : feature_maps) { + if (feature_map == feature) { + return feature_map; + } + } + return ""; +} //--------------------------------------------------------------------------- bool Session::has_constraints() { @@ -1281,6 +1293,14 @@ Eigen::MatrixXd Session::get_all_scalars(std::string target_feature) { Eigen::VectorXd scalars = shapes[i]->get_point_features(target_feature); + // check that the scalars are the right size + if (scalars.size() != num_particles) { + SW_ERROR("scalars.size() : {}", scalars.size()); + SW_ERROR("num_particles : {}", num_particles); + SW_ERROR("Error: scalars size does not match number of particles"); + return Eigen::MatrixXd(); + } + // write into all_scalars all_scalars.row(i) = scalars.transpose(); } diff --git a/Studio/Data/Session.h b/Studio/Data/Session.h index 69f38e22b4..c1d217bb1c 100644 --- a/Studio/Data/Session.h +++ b/Studio/Data/Session.h @@ -285,6 +285,7 @@ class Session : public QObject, public QEnableSharedFromThis { void planes_changed(); void ffc_changed(); void update_display(); + void feature_map_changed(); void reset_stats(); void new_mesh(); void feature_range_changed(); diff --git a/Studio/Interface/ShapeWorksStudioApp.cpp b/Studio/Interface/ShapeWorksStudioApp.cpp index 375696365a..9156421d40 100644 --- a/Studio/Interface/ShapeWorksStudioApp.cpp +++ b/Studio/Interface/ShapeWorksStudioApp.cpp @@ -423,7 +423,7 @@ void ShapeWorksStudioApp::import_files(QStringList file_names) { if (first_load) { // On first load, we can check if there was an active scalar on loaded meshes - set_feature_map(session_->get_default_feature_map()); + session_->set_feature_map(session_->get_default_feature_map()); } } catch (std::runtime_error& e) { handle_error(e.what()); @@ -970,6 +970,7 @@ void ShapeWorksStudioApp::new_session() { connect(session_.data(), &Session::reset_stats, this, &ShapeWorksStudioApp::handle_reset_stats); connect(session_.data(), &Session::update_display, this, &ShapeWorksStudioApp::handle_display_setting_changed); connect(session_.data(), &Session::update_view_mode, this, &ShapeWorksStudioApp::update_view_mode); + connect(session_.data(), &Session::feature_map_changed, this, &ShapeWorksStudioApp::update_view_mode); connect(session_.data(), &Session::new_mesh, this, &ShapeWorksStudioApp::handle_new_mesh); connect(session_.data(), &Session::reinsert_shapes, this, [&]() { update_display(true); }); connect(session_.data(), &Session::save, this, &ShapeWorksStudioApp::on_action_save_project_triggered); @@ -1087,7 +1088,7 @@ void ShapeWorksStudioApp::update_view_mode() { ui_->view_mode_combobox->setCurrentText(QString::fromStdString(display_mode_to_string(view_mode))); update_view_combo(); - auto feature_map = get_feature_map(); + auto feature_map = session_->get_feature_map(); ui_->features->setCurrentText(QString::fromStdString(feature_map)); if (visualizer_) { @@ -2041,7 +2042,7 @@ QString ShapeWorksStudioApp::get_mesh_file_filter() { //--------------------------------------------------------------------------- void ShapeWorksStudioApp::update_feature_map_selection(int index) { QString feature_map = ui_->features->itemText(index); - set_feature_map(feature_map.toStdString()); + session_->set_feature_map(feature_map.toStdString()); } //--------------------------------------------------------------------------- @@ -2065,30 +2066,6 @@ void ShapeWorksStudioApp::image_combo_changed(int index) { session_->set_image_name(ui_->image_combo_->itemText(index).toStdString()); } -//--------------------------------------------------------------------------- -bool ShapeWorksStudioApp::set_feature_map(std::string feature_map) { - if (feature_map != get_feature_map()) { - session_->set_feature_map(feature_map); - update_view_mode(); - return true; - } - return false; -} - -//--------------------------------------------------------------------------- -std::string ShapeWorksStudioApp::get_feature_map() { - std::string feature_map = session_->get_feature_map(); - - // confirm that this is a valid feature map - auto feature_maps = session_->get_project()->get_feature_names(); - for (const std::string& feature : feature_maps) { - if (feature_map == feature) { - return feature_map; - } - } - return ""; -} - //--------------------------------------------------------------------------- bool ShapeWorksStudioApp::get_feature_uniform_scale() { return session_->parameters().get("feature_uniform_scale", true); diff --git a/Studio/Interface/ShapeWorksStudioApp.h b/Studio/Interface/ShapeWorksStudioApp.h index 6938ece1b0..3c8e8404c8 100644 --- a/Studio/Interface/ShapeWorksStudioApp.h +++ b/Studio/Interface/ShapeWorksStudioApp.h @@ -195,9 +195,6 @@ class ShapeWorksStudioApp : public QMainWindow { void display_mode_shape(); - bool set_feature_map(std::string feature_map); - std::string get_feature_map(); - bool get_feature_uniform_scale(); void set_feature_uniform_scale(bool value);