diff --git a/src/examples/source/SmartPeakGUI.cpp b/src/examples/source/SmartPeakGUI.cpp index c769c3349..c095c3858 100644 --- a/src/examples/source/SmartPeakGUI.cpp +++ b/src/examples/source/SmartPeakGUI.cpp @@ -706,6 +706,7 @@ int main(int argc, char** argv) if (ImGui::MenuItem("Reset Window Layout")) { split_window.reset_layout_ = true; + calibrators_line_plot_->reset_layout_ = true; } ImGui::EndMenu(); } @@ -882,12 +883,9 @@ int main(int argc, char** argv) // calibrators if (calibrators_line_plot_->visible_) { - exceeding_plot_points_ = !session_handler_.setCalibratorsScatterLinePlot(application_handler_.sequenceHandler_); - calibrators_line_plot_->setValues(&session_handler_.calibrators_conc_fit_data, &session_handler_.calibrators_feature_fit_data, - &session_handler_.calibrators_conc_raw_data, &session_handler_.calibrators_feature_raw_data, &session_handler_.calibrators_series_names, - session_handler_.calibrators_x_axis_title, session_handler_.calibrators_y_axis_title, session_handler_.calibrators_conc_min, session_handler_.calibrators_conc_max, - session_handler_.calibrators_feature_min, session_handler_.calibrators_feature_max, - "CalibratorsMainWindow"); + SessionHandler::CalibrationData calibration_data; + exceeding_plot_points_ = !session_handler_.setCalibratorsScatterLinePlot(application_handler_.sequenceHandler_, calibration_data); + calibrators_line_plot_->setValues(calibration_data, "CalibratorsMainWindow"); } // ====================================== diff --git a/src/smartpeak/include/SmartPeak/core/SequenceSegmentHandler.h b/src/smartpeak/include/SmartPeak/core/SequenceSegmentHandler.h index 66e14418e..eb73f0bcb 100644 --- a/src/smartpeak/include/SmartPeak/core/SequenceSegmentHandler.h +++ b/src/smartpeak/include/SmartPeak/core/SequenceSegmentHandler.h @@ -112,7 +112,17 @@ namespace SmartPeak const std::map>& getComponentsToConcentrations() const; -private: + void setOutlierComponentsToConcentrations( + const std::map> components_to_concentrations + ); + + std::map>& + getOutlierComponentsToConcentrations(); + + const std::map>& + getOutlierComponentsToConcentrations() const; + + private: std::string sequence_segment_name_; std::vector sample_indices_; ///< The indices of each injection; this could be replaced with `std::shared_ptr` to save the map lookup std::vector standards_concentrations_; @@ -126,5 +136,6 @@ namespace SmartPeak std::shared_ptr feature_rsd_estimations_ = nullptr; ///< Percent RSD estimations; shared between all raw data handlers in the sequence segment std::shared_ptr feature_background_estimations_ = nullptr; ///< Background interference estimations; shared between all raw data handlers in the sequence segment std::map> components_to_concentrations_; + std::map> outlier_components_to_concentrations_; }; } \ No newline at end of file diff --git a/src/smartpeak/include/SmartPeak/core/SessionHandler.h b/src/smartpeak/include/SmartPeak/core/SessionHandler.h index 949655fe8..33d67fdb9 100644 --- a/src/smartpeak/include/SmartPeak/core/SessionHandler.h +++ b/src/smartpeak/include/SmartPeak/core/SessionHandler.h @@ -336,6 +336,27 @@ namespace SmartPeak */ void getHeatMap(const SequenceHandler& sequence_handler, HeatMapData& result, const std::string& feature_name); + /* + @brief Calibration data structure, result of call to setCalibratorsScatterLinePlot + */ + struct CalibrationData + { + std::vector> conc_raw_data; + std::vector> feature_raw_data; + std::vector> outlier_conc_raw_data; + std::vector> outlier_feature_raw_data; + std::vector> conc_fit_data; + std::vector> feature_fit_data; + std::vector series_names; + std::string x_axis_title; + std::string y_axis_title; + float conc_min; + float conc_max; + float feature_min; + float feature_max; + std::vector quant_methods; + }; + /* @brief Sets the data used for rendering the calibrators @@ -343,7 +364,7 @@ namespace SmartPeak @returns true if all points were added and false if points were omitted due to performance */ - bool setCalibratorsScatterLinePlot(const SequenceHandler& sequence_handler); + bool setCalibratorsScatterLinePlot(const SequenceHandler& sequence_handler, CalibrationData& result); Eigen::Tensor getInjectionExplorerHeader(); Eigen::Tensor getInjectionExplorerBody(); @@ -403,13 +424,6 @@ namespace SmartPeak std::string feat_line_y_axis_title; float feat_line_sample_min, feat_line_sample_max, feat_value_min, feat_value_max; Eigen::Tensor feat_row_labels, feat_col_labels; - // data for the calibrators scatter/line plot - std::vector> calibrators_conc_raw_data, calibrators_feature_raw_data; - std::vector> calibrators_conc_fit_data, calibrators_feature_fit_data; - std::vector calibrators_series_names; - std::string calibrators_x_axis_title; - std::string calibrators_y_axis_title; - float calibrators_conc_min , calibrators_conc_max, calibrators_feature_min, calibrators_feature_max; private: int feature_table_unique_samples_transitions_ = 0; // used to decide when to update the feature table data int feature_matrix_unique_transitions_ = 0; // used to decide when to update the feature matrix data diff --git a/src/smartpeak/source/core/SequenceSegmentHandler.cpp b/src/smartpeak/source/core/SequenceSegmentHandler.cpp index a555a651f..48599506e 100644 --- a/src/smartpeak/source/core/SequenceSegmentHandler.cpp +++ b/src/smartpeak/source/core/SequenceSegmentHandler.cpp @@ -53,6 +53,7 @@ namespace SmartPeak if (feature_rsd_estimations_ != nullptr) feature_rsd_estimations_ = std::make_shared(OpenMS::MRMFeatureQC()); if (feature_background_estimations_ != nullptr) feature_background_estimations_ = std::make_shared(OpenMS::MRMFeatureQC()); components_to_concentrations_.clear(); + outlier_components_to_concentrations_.clear(); } void SequenceSegmentHandler::setSequenceSegmentName(const std::string& sequence_segment_name) @@ -343,4 +344,23 @@ namespace SmartPeak { return components_to_concentrations_; } + + void SequenceSegmentHandler::setOutlierComponentsToConcentrations( + const std::map> components_to_concentrations + ) + { + outlier_components_to_concentrations_ = components_to_concentrations; + } + + std::map>& + SequenceSegmentHandler::getOutlierComponentsToConcentrations() + { + return outlier_components_to_concentrations_; + } + + const std::map>& + SequenceSegmentHandler::getOutlierComponentsToConcentrations() const + { + return outlier_components_to_concentrations_; + } } diff --git a/src/smartpeak/source/core/SequenceSegmentProcessors/CalculateCalibration.cpp b/src/smartpeak/source/core/SequenceSegmentProcessors/CalculateCalibration.cpp index 19b443e7f..97b190af4 100644 --- a/src/smartpeak/source/core/SequenceSegmentProcessors/CalculateCalibration.cpp +++ b/src/smartpeak/source/core/SequenceSegmentProcessors/CalculateCalibration.cpp @@ -83,6 +83,7 @@ namespace SmartPeak absoluteQuantitation.setQuantMethods(sequenceSegmentHandler_IO.getQuantitationMethods()); std::map> components_to_concentrations; + std::map> outlier_components_to_concentrations; for (const OpenMS::AbsoluteQuantitationMethod& row : sequenceSegmentHandler_IO.getQuantitationMethods()) { // map standards to features OpenMS::AbsoluteQuantitationStandards absoluteQuantitationStandards; @@ -107,6 +108,9 @@ namespace SmartPeak continue; } + // Keep a copy to compute outer points + auto all_feature_concentrations = feature_concentrations_pruned; + try { absoluteQuantitation.optimizeSingleCalibrationCurve( @@ -124,13 +128,36 @@ namespace SmartPeak LOGW << "Warning: '" << row.getComponentName() << "' cannot be analysed.\n"; continue; } - // find the optimal calibration curve for each component + // Compute outer points + std::vector outlier_feature_concentrations; + for (const auto& feature : all_feature_concentrations) + { + bool found = false; + for (const auto& feature_pruned : feature_concentrations_pruned) + { + if ((feature.IS_feature == feature_pruned.IS_feature) + && (std::abs(feature.actual_concentration - feature_pruned.actual_concentration) < 1e-9) + && (std::abs(feature.IS_actual_concentration - feature_pruned.IS_actual_concentration) < 1e-9) + && (std::abs(feature.dilution_factor - feature_pruned.dilution_factor) < 1e-9)) + { + found = true; + break; + } + } + if (!found) + { + outlier_feature_concentrations.push_back(feature); + } + } components_to_concentrations.erase(row.getComponentName()); - components_to_concentrations.insert({row.getComponentName(), feature_concentrations_pruned}); + components_to_concentrations.insert({row.getComponentName(), feature_concentrations_pruned }); + outlier_components_to_concentrations.erase(row.getComponentName()); + outlier_components_to_concentrations.insert({ row.getComponentName(), outlier_feature_concentrations }); } // store results sequenceSegmentHandler_IO.setComponentsToConcentrations(components_to_concentrations); + sequenceSegmentHandler_IO.setOutlierComponentsToConcentrations(outlier_components_to_concentrations); sequenceSegmentHandler_IO.getQuantitationMethods() = absoluteQuantitation.getQuantMethods(); //sequenceSegmentHandler_IO.setQuantitationMethods(absoluteQuantitation.getQuantMethods()); LOGD << "END optimizeCalibrationCurves"; diff --git a/src/smartpeak/source/core/SessionHandler.cpp b/src/smartpeak/source/core/SessionHandler.cpp index 5a78ff558..2f650303a 100644 --- a/src/smartpeak/source/core/SessionHandler.cpp +++ b/src/smartpeak/source/core/SessionHandler.cpp @@ -1993,9 +1993,8 @@ namespace SmartPeak result.selected_transitions_ = selected_transitions; result.selected_transition_groups_ = selected_transition_groups; } - bool SessionHandler::setCalibratorsScatterLinePlot(const SequenceHandler & sequence_handler) + bool SessionHandler::setCalibratorsScatterLinePlot(const SequenceHandler & sequence_handler, CalibrationData& result) { - const int MAX_POINTS = 9000; // Maximum number of points before either performance drops considerable or IMGUI throws an error int n_points = 0; if (sequence_handler.getSequenceSegments().size() > 0 && sequence_handler.getSequenceSegments().at(0).getQuantitationMethods().size() > 0 && @@ -2008,41 +2007,35 @@ namespace SmartPeak if (!selected_transitions(i).empty()) component_names.insert(selected_transitions(i)); } - if (calibrators_conc_fit_data.size() != component_names.size() && calibrators_conc_raw_data.size() != component_names.size()) + if (result.conc_fit_data.size() != component_names.size() && result.conc_raw_data.size() != component_names.size()) { - LOGD << "Making the calibrators data for plotting"; + // LOGD << "Making the calibrators data for plotting"; // Update the axis titles and clear the data - calibrators_x_axis_title = "Concentration (" + sequence_handler.getSequenceSegments().at(0).getQuantitationMethods().at(0).getConcentrationUnits() + ")"; - calibrators_y_axis_title = sequence_handler.getSequenceSegments().at(0).getQuantitationMethods().at(0).getFeatureName() + " (au)"; - calibrators_conc_min = 1e6; - calibrators_conc_max = 0; - calibrators_feature_min = 1e6; - calibrators_feature_max = 0; - calibrators_conc_fit_data.clear(); - calibrators_feature_fit_data.clear(); - calibrators_conc_raw_data.clear(); - calibrators_feature_raw_data.clear(); - calibrators_series_names.clear(); + result.x_axis_title = "Concentration (" + sequence_handler.getSequenceSegments().at(0).getQuantitationMethods().at(0).getConcentrationUnits() + ")"; + result.y_axis_title = sequence_handler.getSequenceSegments().at(0).getQuantitationMethods().at(0).getFeatureName() + " (au)"; + result.conc_min = 1e6; + result.conc_max = 0; + result.feature_min = 1e6; + result.feature_max = 0; + result.conc_fit_data.clear(); + result.feature_fit_data.clear(); + result.conc_raw_data.clear(); + result.feature_raw_data.clear(); + result.series_names.clear(); for (const auto& sequence_segment : sequence_handler.getSequenceSegments()) { // Extract out raw data used to make the calibrators found in `StandardsConcentrations` std::map, std::vector>> stand_concs_map; // map of x_data and sample_name for a component for (const auto& stand_concs : sequence_segment.getStandardsConcentrations()) { - // Skip components that have not been fitted with a calibration curve - if (sequence_segment.getComponentsToConcentrations().count(stand_concs.component_name) > 0 && - sequence_segment.getComponentsToConcentrations().at(stand_concs.component_name).size() > 0 && - component_names.count(stand_concs.component_name) > 0) // TODO: filter out components that have not been fitted - { - const float x_datum = float(stand_concs.actual_concentration / stand_concs.IS_actual_concentration / stand_concs.dilution_factor); - auto found = stand_concs_map.emplace(stand_concs.component_name, - std::make_pair( - std::vector({ x_datum }), - std::vector({ stand_concs.sample_name }))); - if (!found.second) - { - stand_concs_map.at(stand_concs.component_name).first.push_back(x_datum); - stand_concs_map.at(stand_concs.component_name).second.push_back(stand_concs.sample_name); - } + const float x_datum = float(stand_concs.actual_concentration / stand_concs.IS_actual_concentration / stand_concs.dilution_factor); + auto found = stand_concs_map.emplace(stand_concs.component_name, + std::make_pair( + std::vector({ x_datum }), + std::vector({ stand_concs.sample_name }))); + if (!found.second) + { + stand_concs_map.at(stand_concs.component_name).first.push_back(x_datum); + stand_concs_map.at(stand_concs.component_name).second.push_back(stand_concs.sample_name); } } // Make the line of best fit using the `QuantitationMethods` @@ -2050,58 +2043,80 @@ namespace SmartPeak { // Skip components that have not been fitted with a calibration curve if (sequence_segment.getComponentsToConcentrations().count(quant_method.getComponentName()) > 0 && - sequence_segment.getComponentsToConcentrations().at(quant_method.getComponentName()).size() > 0 && - (double)quant_method.getTransformationModelParams().getValue("slope") != 1.0&& - component_names.count(quant_method.getComponentName()) > 0) // TODO: filter out components that have not been fitted + component_names.count(quant_method.getComponentName()) > 0) { - // Make the line of best fit using the `QuantitationMethods + bool calibration_curve_found = ((double)quant_method.getTransformationModelParams().getValue("slope") != 1.0); + // Make the line of best fit using the `QuantitationMethods` std::vector y_fit_data; - for (const auto& ratio : stand_concs_map.at(quant_method.getComponentName()).first) { - // TODO: encapsulate in its own method e.g. sequenceSegmentProcessor - // TODO: check that the calibration actually found a best fit (and set to all 0 if not) - // calculate the absolute concentration - OpenMS::TransformationModel::DataPoints data; - OpenMS::TransformationDescription tmd(data); - tmd.fitModel(quant_method.getTransformationModel(), quant_method.getTransformationModelParams()); - float calculated_feature_ratio = tmd.apply(ratio); - // check for less than zero - if (calculated_feature_ratio < 0.0) + result.quant_methods.push_back(quant_method); + if (calibration_curve_found) + { + for (const auto& ratio : stand_concs_map.at(quant_method.getComponentName()).first) { + // TODO: encapsulate in its own method e.g. sequenceSegmentProcessor + // TODO: check that the calibration actually found a best fit (and set to all 0 if not) + // calculate the absolute concentration + OpenMS::TransformationModel::DataPoints data; + OpenMS::TransformationDescription tmd(data); + tmd.fitModel(quant_method.getTransformationModel(), quant_method.getTransformationModelParams()); + float calculated_feature_ratio = tmd.apply(ratio); + // check for less than zero + if (calculated_feature_ratio < 0.0) + { + calculated_feature_ratio = 0.0; + } + y_fit_data.push_back(calculated_feature_ratio); + result.conc_min = std::min(ratio, result.conc_min); + result.conc_max = std::max(ratio, result.conc_max); + result.feature_min = std::min(calculated_feature_ratio, result.feature_min); + result.feature_max = std::max(calculated_feature_ratio, result.feature_max); + } + n_points += y_fit_data.size(); + if (n_points < max_nb_points) { + result.conc_fit_data.push_back(stand_concs_map.at(quant_method.getComponentName()).first); + result.feature_fit_data.push_back(y_fit_data); + } + else { - calculated_feature_ratio = 0.0; + LOGD << "Stopped adding points to calibrators plot"; + return false; } - y_fit_data.push_back(calculated_feature_ratio); - calibrators_conc_min = std::min(ratio, calibrators_conc_min); - calibrators_conc_max = std::max(ratio, calibrators_conc_max); - calibrators_feature_min = std::min(calculated_feature_ratio, calibrators_feature_min); - calibrators_feature_max = std::max(calculated_feature_ratio, calibrators_feature_max); - } - n_points += y_fit_data.size(); - if (n_points < MAX_POINTS) { - calibrators_conc_fit_data.push_back(stand_concs_map.at(quant_method.getComponentName()).first); - calibrators_feature_fit_data.push_back(y_fit_data); - } - else - { - LOGD << "Stopped adding points to calibrators plot"; - return false; } // Extract out the points used to make the line of best fit in `ComponentsToConcentrations` std::vector x_raw_data, y_raw_data; OpenMS::AbsoluteQuantitation absQuant; for (const auto& point : sequence_segment.getComponentsToConcentrations().at(quant_method.getComponentName())) { - x_raw_data.push_back(float(point.actual_concentration / point.IS_actual_concentration / point.dilution_factor)); + auto ratio = float(point.actual_concentration / point.IS_actual_concentration / point.dilution_factor); + x_raw_data.push_back(ratio); float y_datum = absQuant.calculateRatio(point.feature, point.IS_feature, quant_method.getFeatureName()); y_raw_data.push_back(y_datum); - calibrators_feature_min = std::min(y_datum, calibrators_feature_min); - calibrators_feature_max = std::max(y_datum, calibrators_feature_max); + result.feature_min = std::min(y_datum, result.feature_min); + result.feature_max = std::max(y_datum, result.feature_max); + result.conc_min = std::min(ratio, result.conc_min); + result.conc_max = std::max(ratio, result.conc_max); } n_points += x_raw_data.size(); - if (n_points < MAX_POINTS) { - calibrators_conc_raw_data.push_back(x_raw_data); - calibrators_feature_raw_data.push_back(y_raw_data); - calibrators_series_names.push_back(quant_method.getComponentName()); + // Extract out the points out of the line of best fit in `ComponentsToConcentrations` + std::vector outlier_x_raw_data, outlier_y_raw_data; + for (const auto& point : sequence_segment.getOutlierComponentsToConcentrations().at(quant_method.getComponentName())) { + auto ratio = float(point.actual_concentration / point.IS_actual_concentration / point.dilution_factor); + outlier_x_raw_data.push_back(ratio); + float y_datum = absQuant.calculateRatio(point.feature, point.IS_feature, quant_method.getFeatureName()); + outlier_y_raw_data.push_back(y_datum); + result.feature_min = std::min(y_datum, result.feature_min); + result.feature_max = std::max(y_datum, result.feature_max); + result.conc_min = std::min(ratio, result.conc_min); + result.conc_max = std::max(ratio, result.conc_max); + } + n_points += outlier_x_raw_data.size(); + // add points + if (n_points < max_nb_points) { + result.conc_raw_data.push_back(x_raw_data); + result.feature_raw_data.push_back(y_raw_data); + result.outlier_conc_raw_data.push_back(outlier_x_raw_data); + result.outlier_feature_raw_data.push_back(outlier_y_raw_data); + result.series_names.push_back(quant_method.getComponentName()); } - else + else { LOGD << "Stopped adding points to calibrators plot"; return false; @@ -2110,10 +2125,10 @@ namespace SmartPeak } } // Sort data - for (int j = 0; j < calibrators_conc_fit_data.size(); ++j) + for (int j = 0; j < result.conc_fit_data.size(); ++j) { - auto& fit_data_x = calibrators_conc_fit_data[j]; - auto& fit_data_y = calibrators_feature_fit_data[j]; + auto& fit_data_x = result.conc_fit_data[j]; + auto& fit_data_y = result.feature_fit_data[j]; std::vector> sorting_vector; for (int i = 0; i < fit_data_x.size(); ++i) { @@ -2137,7 +2152,7 @@ namespace SmartPeak } } } - return (n_points < MAX_POINTS); + return (n_points < max_nb_points); } Eigen::Tensor SessionHandler::getInjectionExplorerHeader() { diff --git a/src/tests/class_tests/smartpeak/source/SequenceSegmentHandler_test.cpp b/src/tests/class_tests/smartpeak/source/SequenceSegmentHandler_test.cpp index f4d2e2bbe..9af37ec2e 100644 --- a/src/tests/class_tests/smartpeak/source/SequenceSegmentHandler_test.cpp +++ b/src/tests/class_tests/smartpeak/source/SequenceSegmentHandler_test.cpp @@ -422,6 +422,39 @@ TEST(SequenceSegmentHandler, set_get_ComponentsToConcentrations) EXPECT_EQ(m3.at(foo)[0].IS_actual_concentration, ac2); } +TEST(SequenceSegmentHandler, set_get_OutlierComponentsToConcentrations) +{ + SequenceSegmentHandler ssh; + OpenMS::AbsoluteQuantitationStandards::featureConcentration fc; + const string foo{ "foo" }; + const double ac1{ 4.5 }; + fc.actual_concentration = ac1; + vector fc1; + fc1.push_back(fc); + map> m1; + m1.insert({ foo, fc1 }); + + ssh.setOutlierComponentsToConcentrations(m1); + + const map>& + m2 = ssh.getOutlierComponentsToConcentrations(); + EXPECT_EQ(m2.size(), 1); + EXPECT_EQ(m2.count(foo), 1); + EXPECT_EQ(m2.at(foo)[0].actual_concentration, ac1); + + const double ac2{ 7.3 }; + fc1[0].IS_actual_concentration = ac2; + ssh.getOutlierComponentsToConcentrations().erase(foo); + ssh.getOutlierComponentsToConcentrations().insert({ foo, fc1 }); + + map>& + m3 = ssh.getOutlierComponentsToConcentrations(); + EXPECT_EQ(m3.size(), 1); + EXPECT_EQ(m3.count(foo), 1); + EXPECT_EQ(m3.at(foo)[0].actual_concentration, ac1); + EXPECT_EQ(m3.at(foo)[0].IS_actual_concentration, ac2); +} + TEST(SequenceSegmentHandler, clear) { SequenceSegmentHandler ssh; @@ -454,12 +487,14 @@ TEST(SequenceSegmentHandler, clear) map> m1; m1.insert({"foo", fc1}); ssh.setComponentsToConcentrations(m1); + ssh.setOutlierComponentsToConcentrations(m1); EXPECT_FALSE(ssh.getSequenceSegmentName().empty()); EXPECT_FALSE(ssh.getSampleIndices().empty()); EXPECT_FALSE(ssh.getStandardsConcentrations().empty()); EXPECT_FALSE(ssh.getQuantitationMethods().empty()); EXPECT_FALSE(ssh.getComponentsToConcentrations().empty()); + EXPECT_FALSE(ssh.getOutlierComponentsToConcentrations().empty()); EXPECT_FALSE(ssh.getFeatureFilter().component_qcs.empty()); EXPECT_FALSE(ssh.getFeatureQC().component_qcs.empty()); EXPECT_FALSE(ssh.getFeatureRSDFilter().component_qcs.empty()); @@ -476,6 +511,7 @@ TEST(SequenceSegmentHandler, clear) EXPECT_TRUE(ssh.getStandardsConcentrations().empty()); EXPECT_TRUE(ssh.getQuantitationMethods().empty()); EXPECT_TRUE(ssh.getComponentsToConcentrations().empty()); + EXPECT_TRUE(ssh.getOutlierComponentsToConcentrations().empty()); EXPECT_TRUE(ssh.getFeatureFilter().component_qcs.empty()); EXPECT_TRUE(ssh.getFeatureQC().component_qcs.empty()); EXPECT_TRUE(ssh.getFeatureRSDFilter().component_qcs.empty()); diff --git a/src/tests/class_tests/smartpeak/source/SequenceSegmentProcessor_test.cpp b/src/tests/class_tests/smartpeak/source/SequenceSegmentProcessor_test.cpp index 465f80fa6..0e6dbcca8 100644 --- a/src/tests/class_tests/smartpeak/source/SequenceSegmentProcessor_test.cpp +++ b/src/tests/class_tests/smartpeak/source/SequenceSegmentProcessor_test.cpp @@ -723,6 +723,26 @@ TEST(SequenceSegmentProcessor, processCalculateCalibration) EXPECT_NEAR(static_cast(AQMs_rdh[2].getCorrelationCoefficient()), 0.9993200722867581, 1e-6); EXPECT_NEAR(static_cast(AQMs_rdh[2].getLLOQ()), 0.04, 1e-6); EXPECT_NEAR(static_cast(AQMs_rdh[2].getULOQ()), 200.0, 1e-6); + + const auto& component_to_concentrations = sequenceSegmentHandler.getComponentsToConcentrations(); + EXPECT_EQ(component_to_concentrations.size(), 3); + ASSERT_EQ(component_to_concentrations.count("ser-L.ser-L_1.Light"), 1); + const auto& component_to_concentration = component_to_concentrations.at("ser-L.ser-L_1.Light"); + ASSERT_EQ(component_to_concentration.size(), 11); + EXPECT_FLOAT_EQ(component_to_concentration[0].actual_concentration, 0.039999999); + EXPECT_EQ(component_to_concentration[0].concentration_units, std::string("uM")); + EXPECT_FLOAT_EQ(component_to_concentration[0].dilution_factor, 1); + EXPECT_FLOAT_EQ(component_to_concentration[0].IS_actual_concentration, 1); + + const auto& outlier_component_to_concentrations = sequenceSegmentHandler.getOutlierComponentsToConcentrations(); + ASSERT_EQ(outlier_component_to_concentrations.size(), 3); + ASSERT_EQ(outlier_component_to_concentrations.count("ser-L.ser-L_1.Light"), 1); + const auto& outlier_component_to_concentration = outlier_component_to_concentrations.at("ser-L.ser-L_1.Light"); + ASSERT_EQ(outlier_component_to_concentration.size(), 3); + EXPECT_FLOAT_EQ(outlier_component_to_concentration[0].actual_concentration, 0.0099999998); + EXPECT_EQ(outlier_component_to_concentration[0].concentration_units, std::string("uM")); + EXPECT_FLOAT_EQ(outlier_component_to_concentration[0].dilution_factor, 1); + EXPECT_FLOAT_EQ(outlier_component_to_concentration[0].IS_actual_concentration, 1); } /** diff --git a/src/tests/class_tests/smartpeak/source/SessionHandler_test.cpp b/src/tests/class_tests/smartpeak/source/SessionHandler_test.cpp index a4a962a09..2095027ae 100644 --- a/src/tests/class_tests/smartpeak/source/SessionHandler_test.cpp +++ b/src/tests/class_tests/smartpeak/source/SessionHandler_test.cpp @@ -717,7 +717,17 @@ TEST(SessionHandler, setCalibratorsScatterLinePlot1) { TestData testData; SessionHandler session_handler; - session_handler.setCalibratorsScatterLinePlot(testData.application_handler.sequenceHandler_); + SessionHandler::CalibrationData calibrator_data; + session_handler.setCalibratorsScatterLinePlot(testData.application_handler.sequenceHandler_, calibrator_data); + EXPECT_EQ(calibrator_data.conc_fit_data.size(), 0); + EXPECT_EQ(calibrator_data.conc_raw_data.size(), 0); + EXPECT_EQ(calibrator_data.feature_raw_data.size(), 0); + EXPECT_EQ(calibrator_data.outlier_conc_raw_data.size(), 0); + EXPECT_EQ(calibrator_data.outlier_feature_raw_data.size(), 0); + EXPECT_EQ(calibrator_data.quant_methods.size(), 0); + EXPECT_EQ(calibrator_data.series_names.size(), 0); + EXPECT_EQ(calibrator_data.x_axis_title, std::string("")); + EXPECT_EQ(calibrator_data.y_axis_title, std::string("")); } TEST(SessionHandler, getHeatMap) { diff --git a/src/tests/class_tests/smartpeak/source/Widget_test.cpp b/src/tests/class_tests/smartpeak/source/Widget_test.cpp index 1bf4ff0fd..ae5abbb08 100644 --- a/src/tests/class_tests/smartpeak/source/Widget_test.cpp +++ b/src/tests/class_tests/smartpeak/source/Widget_test.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -881,3 +882,52 @@ TEST(SetInputOutputWidget, cancel) EXPECT_EQ(set_input_output_widget_test.on_input_output_cancel_counter, 1); } +class CalibratorsPlotWidget_Test : + public CalibratorsPlotWidget +{ +public: + CalibratorsPlotWidget_Test() : + CalibratorsPlotWidget() + {}; + +public: + + bool& get_reset_zoom_() + { + return reset_zoom_; + } + + const std::vector& get_components_() + { + return components_; + } + +}; + +TEST(CalibratorsPlotWidget, setValue) +{ + CalibratorsPlotWidget_Test calibrator_widget; + EXPECT_EQ(calibrator_widget.get_reset_zoom_(), true); + + SessionHandler::CalibrationData calibrator_data; + calibrator_data.series_names = { "on", "two", "three" }; + calibrator_data.x_axis_title = "x_axis_title"; + calibrator_data.y_axis_title = "y_axis_title"; + calibrator_data.conc_min = 1.0f; + calibrator_data.conc_max = 100.0f; + calibrator_data.feature_min = 10.0f; + calibrator_data.feature_max = 1000.0f; + + calibrator_widget.setValues(calibrator_data, "test"); + EXPECT_EQ(calibrator_widget.get_reset_zoom_(), true); + + // a draw operation would unflag the reset_zoom + calibrator_widget.get_reset_zoom_() = false; + + // same data should not involve a zoom reset + calibrator_widget.setValues(calibrator_data, "test"); + EXPECT_EQ(calibrator_widget.get_reset_zoom_(), false); + + auto components = calibrator_widget.get_components_(); + EXPECT_EQ(components, calibrator_data.series_names); +}; diff --git a/src/widgets/include/SmartPeak/ui/CalibratorsPlotWidget.h b/src/widgets/include/SmartPeak/ui/CalibratorsPlotWidget.h index 077dc4e80..35a04869d 100644 --- a/src/widgets/include/SmartPeak/ui/CalibratorsPlotWidget.h +++ b/src/widgets/include/SmartPeak/ui/CalibratorsPlotWidget.h @@ -23,6 +23,7 @@ #pragma once +#include #include #include #include @@ -39,39 +40,25 @@ namespace SmartPeak { public: CalibratorsPlotWidget(const std::string title = ""): GenericGraphicWidget(title) {}; - void setValues( - const std::vector>* x_fit_data, const std::vector>* y_fit_data, - const std::vector>* x_raw_data, const std::vector>* y_raw_data, const std::vector* series_names, - const std::string& x_axis_title, const std::string& y_axis_title, const float& x_min, const float& x_max, const float& y_min, const float& y_max, - const std::string& plot_title) - { - x_fit_data_ = x_fit_data; - y_fit_data_ = y_fit_data; - x_raw_data_ = x_raw_data; - y_raw_data_ = y_raw_data; - series_names_ = series_names; - x_axis_title_ = x_axis_title; - y_axis_title_ = y_axis_title; - x_min_ = x_min; - x_max_ = x_max; - y_min_ = y_min; - y_max_ = y_max; - plot_title_ = plot_title; - } + void setValues(const SessionHandler::CalibrationData& calibration_data, const std::string& plot_title); void draw() override; + + bool reset_layout_ = true; + protected: - const std::vector>* x_fit_data_ = nullptr; - const std::vector>* y_fit_data_ = nullptr; - const std::vector>* x_raw_data_ = nullptr; - const std::vector>* y_raw_data_ = nullptr; - const std::vector* series_names_ = nullptr; - std::string x_axis_title_; - std::string y_axis_title_; - float x_min_; - float x_max_; - float y_min_; - float y_max_; + void displayParameters(); + void displayPlot(); + SessionHandler::CalibrationData calibration_data_; std::string plot_title_; // used as the ID of the plot as well so this should be unique across the different Widgets + bool show_legend_ = true; + bool show_fit_line_ = true; + bool show_points_ = true; + bool show_outlier_points_ = true; + std::string current_component_; + std::vector components_; + std::vector component_cstr_; + int selected_component_ = 0; + bool reset_zoom_ = true; }; } \ No newline at end of file diff --git a/src/widgets/source/ui/CalibratorsPlotWidget.cpp b/src/widgets/source/ui/CalibratorsPlotWidget.cpp index 4be6ba093..af120880a 100644 --- a/src/widgets/source/ui/CalibratorsPlotWidget.cpp +++ b/src/widgets/source/ui/CalibratorsPlotWidget.cpp @@ -26,29 +26,225 @@ namespace SmartPeak { - void CalibratorsPlotWidget::draw() + void CalibratorsPlotWidget::displayParameters() { - if (!x_raw_data_) + ImGui::Begin("Calibrator Parameters"); + + ImGui::Combo("Component", &selected_component_, &component_cstr_[0], component_cstr_.size()); + + const auto& quantitation_methods = calibration_data_.quant_methods[selected_component_]; + + ImGuiTableFlags table_flags = ImGuiTableFlags_None; + if (ImGui::BeginTable("Calibrator Parameters Table", 2, table_flags)) { - return; + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("IS name"); + ImGui::TableSetColumnIndex(1); + ImGui::Text(quantitation_methods.getISName().c_str()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("llod"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%f", quantitation_methods.getLLOD()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("ulod"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%f", quantitation_methods.getULOD()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("lloq"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%f", quantitation_methods.getLLOQ()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("uloq"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%f", quantitation_methods.getULOQ()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("correlation coefficient"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%f", quantitation_methods.getCorrelationCoefficient()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("nb points"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%d", quantitation_methods.getNPoints()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("transformation model"); + ImGui::TableSetColumnIndex(1); + ImGui::Text(quantitation_methods.getTransformationModel().c_str()); + + const auto& params = quantitation_methods.getTransformationModelParams(); + for (const auto& param : params) + { + const auto& value = param.value; + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text(param.name.c_str()); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s", value.toString().c_str()); + } + ImGui::EndTable(); } - showQuickHelpToolTip("CalibratorsPlotWidget"); + ImGui::End(); + } + + void CalibratorsPlotWidget::displayPlot() + { + ImGui::Begin("Calibrator Plot"); + ImGui::Checkbox("Legend", &show_legend_); + ImGui::SameLine(); + ImGui::Checkbox("Fit line", &show_fit_line_); + ImGui::SameLine(); + ImGui::Checkbox("Points", &show_points_); + ImGui::SameLine(); + ImGui::Checkbox("Outlier points", &show_outlier_points_); + ImGui::SameLine(); + if (ImGui::Button("Fit Zoom")) + { + reset_zoom_ = true; + } + // Main graphic - ImPlot::SetNextPlotLimits(x_min_, x_max_, y_min_, y_max_, ImGuiCond_Always); + ImGuiCond cond; + if (reset_zoom_) + { + cond = ImGuiCond_Always; + reset_zoom_ = false; + } + else + { + cond = ImGuiCond_Once; + } + // add margin around the plot + float x_margin = (calibration_data_.conc_max - calibration_data_.conc_min) * 0.05; + float y_margin = (calibration_data_.feature_max - calibration_data_.feature_min) * 0.05; + ImPlot::SetNextPlotLimits(calibration_data_.conc_min - x_margin, + calibration_data_.conc_max + x_margin, + calibration_data_.feature_min - y_margin, + calibration_data_.feature_max + y_margin, + cond); auto window_size = ImGui::GetWindowSize(); - if (ImPlot::BeginPlot(plot_title_.c_str(), x_axis_title_.c_str(), y_axis_title_.c_str(), ImVec2(window_size.x - 25, window_size.y - 40))) { - for (int i = 0; i < x_raw_data_->size(); ++i) { - assert(x_raw_data_->at(i).size() == y_raw_data_->at(i).size()); - ImPlot::PushStyleVar(ImPlotStyleVar_Marker, ImPlotMarker_Circle); - ImPlot::PlotScatter((series_names_->at(i) + "-pts").c_str(), x_raw_data_->at(i).data(), y_raw_data_->at(i).data(), x_raw_data_->at(i).size()); + ImPlotFlags plotFlags = show_legend_ ? ImPlotFlags_Default | ImPlotFlags_Legend : ImPlotFlags_Default & ~ImPlotFlags_Legend; + if (ImPlot::BeginPlot(plot_title_.c_str(), + calibration_data_.x_axis_title.c_str(), + calibration_data_.y_axis_title.c_str(), + ImVec2(window_size.x - 25, window_size.y - 58), + plotFlags)) { + if (show_fit_line_) + { + for (int i = 0; i < calibration_data_.conc_fit_data.size(); ++i) { + assert(calibration_data_.conc_fit_data.at(i).size() == calibration_data_.feature_fit_data.at(i).size()); + ImPlot::PushStyleVar(ImPlotStyleVar_LineWeight, ImPlot::GetStyle().LineWeight); + ImPlot::PushStyleVar(ImPlotStyleVar_Marker, ImPlotMarker_None); + ImPlot::PlotLine((calibration_data_.series_names.at(i)).c_str(), + calibration_data_.conc_fit_data.at(i).data(), + calibration_data_.feature_fit_data.at(i).data(), + calibration_data_.conc_fit_data.at(i).size()); + } + } + if (show_points_) + { + for (int i = 0; i < calibration_data_.conc_raw_data.size(); ++i) { + assert(calibration_data_.conc_raw_data.at(i).size() == calibration_data_.feature_raw_data.at(i).size()); + ImPlot::PushStyleVar(ImPlotStyleVar_Marker, ImPlotMarker_Circle); + ImPlot::PlotScatter((calibration_data_.series_names.at(i)).c_str(), + calibration_data_.conc_raw_data.at(i).data(), + calibration_data_.feature_raw_data.at(i).data(), + calibration_data_.conc_raw_data.at(i).size()); + } + } + if (show_outlier_points_) + { + for (int i = 0; i < calibration_data_.conc_raw_data.size(); ++i) { + assert(calibration_data_.outlier_conc_raw_data.at(i).size() == calibration_data_.outlier_feature_raw_data.at(i).size()); + ImPlot::PushStyleVar(ImPlotStyleVar_Marker, ImPlotMarker_Cross); + ImPlot::PlotScatter((calibration_data_.series_names.at(i)).c_str(), + calibration_data_.outlier_conc_raw_data.at(i).data(), + calibration_data_.outlier_feature_raw_data.at(i).data(), + calibration_data_.outlier_conc_raw_data.at(i).size()); + } } - for (int i = 0; i < x_fit_data_->size(); ++i) { - assert(x_fit_data_->at(i).size() == y_fit_data_->at(i).size()); - ImPlot::PushStyleVar(ImPlotStyleVar_LineWeight, ImPlot::GetStyle().LineWeight); - ImPlot::PushStyleVar(ImPlotStyleVar_Marker, ImPlotMarker_Circle); - ImPlot::PlotLine((series_names_->at(i) + "-fit").c_str(), x_fit_data_->at(i).data(), y_fit_data_->at(i).data(), x_fit_data_->at(i).size()); + // legend hover management + int i = 0; + for (const auto& serie_name : calibration_data_.series_names) + { + if (ImPlot::IsLegendEntryHovered(serie_name.c_str())) + { + selected_component_ = i; + } + ++i; } + ImPlot::EndPlot(); } + ImGui::End(); + } + + void CalibratorsPlotWidget::draw() + { + if (!calibration_data_.conc_raw_data.size()) + { + return; + } + showQuickHelpToolTip("CalibratorsPlotWidget"); + + // Build default docking + ImGuiID dockspace_id = ImGui::GetID("CalibratorsPlotWidgetDockSpace"); + if (reset_layout_) + { + ImGuiID left_node; + ImGuiID right_node; + ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace); + ImGui::DockBuilderSetNodeSize(dockspace_id, ImGui::GetCurrentWindow()->Size); + ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.5f, &left_node, &right_node); // The second parameter defines the direction of the split + ImGui::DockBuilderDockWindow("Calibrator Parameters", left_node); + ImGui::DockBuilderDockWindow("Calibrator Plot", right_node); + ImGui::DockBuilderFinish(dockspace_id); + reset_layout_ = false; + } + ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_AutoHideTabBar); + + displayParameters(); + displayPlot(); + + } + + void CalibratorsPlotWidget::setValues( + const SessionHandler::CalibrationData& calibration_data, + const std::string& plot_title) + { + if ((calibration_data_.series_names != calibration_data.series_names) + || (std::abs(calibration_data_.conc_min - calibration_data.conc_min) > 1e-9) + || (std::abs(calibration_data_.conc_max - calibration_data.conc_max) > 1e-9) + || (std::abs(calibration_data_.feature_min - calibration_data.feature_min) > 1e-9) + || (std::abs(calibration_data_.feature_max - calibration_data.feature_max) > 1e-9) + ) + { + reset_zoom_ = true; + int i = 0; + components_.clear(); + component_cstr_.clear(); + for (const auto& component : calibration_data.series_names) + { + components_.push_back(component); + component_cstr_.push_back(components_.at(i).c_str()); + ++i; + } + selected_component_ = 0; + } + calibration_data_ = calibration_data; + plot_title_ = plot_title; } }