diff --git a/pwiz_tools/Shared/Common/DataBinding/Internal/AbstractQuery.cs b/pwiz_tools/Shared/Common/DataBinding/Internal/AbstractQuery.cs index 2b4572f58b..0950cfa4e6 100644 --- a/pwiz_tools/Shared/Common/DataBinding/Internal/AbstractQuery.cs +++ b/pwiz_tools/Shared/Common/DataBinding/Internal/AbstractQuery.cs @@ -190,6 +190,7 @@ protected IEnumerable Sort(CancellationToken cancellationToken, DataSch var sortRows = new SortRow[unsortedRows.Count]; for (int iRow = 0; iRow < sortRows.Length; iRow++) { + cancellationToken.ThrowIfCancellationRequested(); sortRows[iRow] = new SortRow(cancellationToken, dataSchema, sortDescriptions, unsortedRows[iRow], iRow); } Array.Sort(sortRows); diff --git a/pwiz_tools/Shared/CommonUtil/Collections/IndexedList.cs b/pwiz_tools/Shared/CommonUtil/Collections/IndexedList.cs index 20b31f5268..b60f4c1837 100644 --- a/pwiz_tools/Shared/CommonUtil/Collections/IndexedList.cs +++ b/pwiz_tools/Shared/CommonUtil/Collections/IndexedList.cs @@ -103,6 +103,10 @@ public int IndexOf(T item) public void Insert(int index, T item) { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } if (index == Count) { Add(item); @@ -128,6 +132,10 @@ public T this[int index] } set { + if (null == value) + { + throw new ArgumentNullException(); + } var oldValue = _items[index]; _itemIndexes.Remove(oldValue); _itemIndexes.Add(value, index); diff --git a/pwiz_tools/Skyline/Controls/Databinding/CandidatePeakForm.cs b/pwiz_tools/Skyline/Controls/Databinding/CandidatePeakForm.cs index 81d5da4bc6..1df8b81b4b 100644 --- a/pwiz_tools/Skyline/Controls/Databinding/CandidatePeakForm.cs +++ b/pwiz_tools/Skyline/Controls/Databinding/CandidatePeakForm.cs @@ -413,7 +413,7 @@ public void DocumentOnChanged(object sender, DocumentChangedEventArgs args) } } - public new bool IsComplete + public override bool IsComplete { get { diff --git a/pwiz_tools/Skyline/Controls/Databinding/DataboundGridForm.cs b/pwiz_tools/Skyline/Controls/Databinding/DataboundGridForm.cs index 409032c816..b74cdfdbc4 100644 --- a/pwiz_tools/Skyline/Controls/Databinding/DataboundGridForm.cs +++ b/pwiz_tools/Skyline/Controls/Databinding/DataboundGridForm.cs @@ -198,7 +198,7 @@ public DataGridViewColumn FindColumn(PropertyPath propertyPath) return databoundGridControl.FindColumn(propertyPath); } - public bool IsComplete + public virtual bool IsComplete { get { diff --git a/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationCurveOptionsDlg.Designer.cs b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationCurveOptionsDlg.Designer.cs new file mode 100644 index 0000000000..178c8c456f --- /dev/null +++ b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationCurveOptionsDlg.Designer.cs @@ -0,0 +1,262 @@ +namespace pwiz.Skyline.Controls.Graphs.Calibration +{ + partial class CalibrationCurveOptionsDlg + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.cbxLogXAxis = new System.Windows.Forms.CheckBox(); + this.cbxLogYAxis = new System.Windows.Forms.CheckBox(); + this.textSizeComboBox = new System.Windows.Forms.ComboBox(); + this.label1 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.checkedListBoxSampleTypes = new System.Windows.Forms.CheckedListBox(); + this.lblShowSampleTypes = new System.Windows.Forms.Label(); + this.cbxSingleBatch = new System.Windows.Forms.CheckBox(); + this.cbxShowLegend = new System.Windows.Forms.CheckBox(); + this.cbxShowFiguresOfMerit = new System.Windows.Forms.CheckBox(); + this.cbxShowBootstrapCurves = new System.Windows.Forms.CheckBox(); + this.btnCancel = new System.Windows.Forms.Button(); + this.btnOk = new System.Windows.Forms.Button(); + this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + this.textLineWidth = new System.Windows.Forms.NumericUpDown(); + ((System.ComponentModel.ISupportInitialize)(this.textLineWidth)).BeginInit(); + this.SuspendLayout(); + // + // cbxLogXAxis + // + this.cbxLogXAxis.AutoSize = true; + this.cbxLogXAxis.Location = new System.Drawing.Point(9, 167); + this.cbxLogXAxis.Name = "cbxLogXAxis"; + this.cbxLogXAxis.Size = new System.Drawing.Size(112, 17); + this.cbxLogXAxis.TabIndex = 4; + this.cbxLogXAxis.Text = "Logarithmic X Axis"; + this.cbxLogXAxis.UseVisualStyleBackColor = true; + // + // cbxLogYAxis + // + this.cbxLogYAxis.AutoSize = true; + this.cbxLogYAxis.Location = new System.Drawing.Point(9, 190); + this.cbxLogYAxis.Name = "cbxLogYAxis"; + this.cbxLogYAxis.Size = new System.Drawing.Size(112, 17); + this.cbxLogYAxis.TabIndex = 5; + this.cbxLogYAxis.Text = "Logarithmic Y Axis"; + this.cbxLogYAxis.UseVisualStyleBackColor = true; + // + // textSizeComboBox + // + this.textSizeComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.textSizeComboBox.FormattingEnabled = true; + this.textSizeComboBox.Items.AddRange(new object[] { + "x-small", + "small", + "normal", + "large", + "x-large"}); + this.textSizeComboBox.Location = new System.Drawing.Point(115, 28); + this.textSizeComboBox.Name = "textSizeComboBox"; + this.textSizeComboBox.Size = new System.Drawing.Size(97, 21); + this.textSizeComboBox.TabIndex = 3; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.ImeMode = System.Windows.Forms.ImeMode.NoControl; + this.label1.Location = new System.Drawing.Point(112, 12); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(52, 13); + this.label1.TabIndex = 2; + this.label1.Text = "&Font size:"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.ImeMode = System.Windows.Forms.ImeMode.NoControl; + this.label3.Location = new System.Drawing.Point(6, 12); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(58, 13); + this.label3.TabIndex = 0; + this.label3.Text = "&Line width:"; + // + // checkedListBoxSampleTypes + // + this.checkedListBoxSampleTypes.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.checkedListBoxSampleTypes.FormattingEnabled = true; + this.checkedListBoxSampleTypes.Location = new System.Drawing.Point(9, 67); + this.checkedListBoxSampleTypes.Name = "checkedListBoxSampleTypes"; + this.checkedListBoxSampleTypes.Size = new System.Drawing.Size(264, 94); + this.checkedListBoxSampleTypes.TabIndex = 7; + // + // lblShowSampleTypes + // + this.lblShowSampleTypes.AutoSize = true; + this.lblShowSampleTypes.Location = new System.Drawing.Point(6, 51); + this.lblShowSampleTypes.Name = "lblShowSampleTypes"; + this.lblShowSampleTypes.Size = new System.Drawing.Size(101, 13); + this.lblShowSampleTypes.TabIndex = 6; + this.lblShowSampleTypes.Text = "Show sample types:"; + // + // cbxSingleBatch + // + this.cbxSingleBatch.AutoSize = true; + this.cbxSingleBatch.Location = new System.Drawing.Point(9, 213); + this.cbxSingleBatch.Name = "cbxSingleBatch"; + this.cbxSingleBatch.Size = new System.Drawing.Size(197, 17); + this.cbxSingleBatch.TabIndex = 8; + this.cbxSingleBatch.Text = "Show replicates from only one batch"; + this.cbxSingleBatch.UseVisualStyleBackColor = true; + // + // cbxShowLegend + // + this.cbxShowLegend.AutoSize = true; + this.cbxShowLegend.Location = new System.Drawing.Point(9, 236); + this.cbxShowLegend.Name = "cbxShowLegend"; + this.cbxShowLegend.Size = new System.Drawing.Size(88, 17); + this.cbxShowLegend.TabIndex = 9; + this.cbxShowLegend.Text = "Show legend"; + this.cbxShowLegend.UseVisualStyleBackColor = true; + // + // cbxShowFiguresOfMerit + // + this.cbxShowFiguresOfMerit.AutoSize = true; + this.cbxShowFiguresOfMerit.Location = new System.Drawing.Point(9, 259); + this.cbxShowFiguresOfMerit.Name = "cbxShowFiguresOfMerit"; + this.cbxShowFiguresOfMerit.Size = new System.Drawing.Size(124, 17); + this.cbxShowFiguresOfMerit.TabIndex = 10; + this.cbxShowFiguresOfMerit.Text = "Show figures of merit"; + this.cbxShowFiguresOfMerit.UseVisualStyleBackColor = true; + // + // cbxShowBootstrapCurves + // + this.cbxShowBootstrapCurves.AutoSize = true; + this.cbxShowBootstrapCurves.Location = new System.Drawing.Point(9, 282); + this.cbxShowBootstrapCurves.Name = "cbxShowBootstrapCurves"; + this.cbxShowBootstrapCurves.Size = new System.Drawing.Size(135, 17); + this.cbxShowBootstrapCurves.TabIndex = 11; + this.cbxShowBootstrapCurves.Text = "Show bootstrap curves"; + this.cbxShowBootstrapCurves.UseVisualStyleBackColor = true; + // + // btnCancel + // + this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.ImeMode = System.Windows.Forms.ImeMode.NoControl; + this.btnCancel.Location = new System.Drawing.Point(198, 331); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(75, 23); + this.btnCancel.TabIndex = 13; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + // + // btnOk + // + this.btnOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnOk.ImeMode = System.Windows.Forms.ImeMode.NoControl; + this.btnOk.Location = new System.Drawing.Point(117, 331); + this.btnOk.Name = "btnOk"; + this.btnOk.Size = new System.Drawing.Size(75, 23); + this.btnOk.TabIndex = 12; + this.btnOk.Text = "OK"; + this.btnOk.UseVisualStyleBackColor = true; + this.btnOk.Click += new System.EventHandler(this.btnOk_Click); + // + // textLineWidth + // + this.textLineWidth.Location = new System.Drawing.Point(9, 28); + this.textLineWidth.Maximum = new decimal(new int[] { + 5, + 0, + 0, + 0}); + this.textLineWidth.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.textLineWidth.Name = "textLineWidth"; + this.textLineWidth.Size = new System.Drawing.Size(100, 20); + this.textLineWidth.TabIndex = 14; + this.textLineWidth.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + // + // CalibrationCurveOptionsDlg + // + this.AcceptButton = this.btnOk; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.btnCancel; + this.ClientSize = new System.Drawing.Size(285, 366); + this.Controls.Add(this.textLineWidth); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnOk); + this.Controls.Add(this.cbxShowBootstrapCurves); + this.Controls.Add(this.cbxShowFiguresOfMerit); + this.Controls.Add(this.cbxShowLegend); + this.Controls.Add(this.cbxSingleBatch); + this.Controls.Add(this.lblShowSampleTypes); + this.Controls.Add(this.checkedListBoxSampleTypes); + this.Controls.Add(this.textSizeComboBox); + this.Controls.Add(this.label1); + this.Controls.Add(this.label3); + this.Controls.Add(this.cbxLogYAxis); + this.Controls.Add(this.cbxLogXAxis); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "CalibrationCurveOptionsDlg"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Calibration Curve Display Options"; + ((System.ComponentModel.ISupportInitialize)(this.textLineWidth)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.CheckBox cbxLogXAxis; + private System.Windows.Forms.CheckBox cbxLogYAxis; + private System.Windows.Forms.ComboBox textSizeComboBox; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.CheckedListBox checkedListBoxSampleTypes; + private System.Windows.Forms.Label lblShowSampleTypes; + private System.Windows.Forms.CheckBox cbxSingleBatch; + private System.Windows.Forms.CheckBox cbxShowLegend; + private System.Windows.Forms.CheckBox cbxShowFiguresOfMerit; + private System.Windows.Forms.CheckBox cbxShowBootstrapCurves; + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.Button btnOk; + private System.Windows.Forms.ToolTip toolTip1; + private System.Windows.Forms.NumericUpDown textLineWidth; + } +} \ No newline at end of file diff --git a/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationCurveOptionsDlg.cs b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationCurveOptionsDlg.cs new file mode 100644 index 0000000000..4127a881df --- /dev/null +++ b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationCurveOptionsDlg.cs @@ -0,0 +1,53 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Windows.Forms; +using pwiz.Skyline.Model.DocSettings.AbsoluteQuantification; +using pwiz.Skyline.Util; + +namespace pwiz.Skyline.Controls.Graphs.Calibration +{ + public partial class CalibrationCurveOptionsDlg : FormEx + { + public CalibrationCurveOptionsDlg() + { + InitializeComponent(); + checkedListBoxSampleTypes.Items.AddRange(SampleType.ALL.ToArray()); + var options = Properties.Settings.Default.CalibrationCurveOptions; + textLineWidth.Text = options.LineWidth.ToString(CultureInfo.CurrentCulture); + GraphFontSize.PopulateCombo(textSizeComboBox, options.FontSize); + cbxLogXAxis.Checked = options.LogXAxis; + cbxLogYAxis.Checked = options.LogYAxis; + for (int i = 0; i < checkedListBoxSampleTypes.Items.Count; i++) + { + checkedListBoxSampleTypes.SetItemChecked(i, options.DisplaySampleTypes.Contains(checkedListBoxSampleTypes.Items[i])); + } + + cbxSingleBatch.Checked = options.SingleBatch; + cbxShowLegend.Checked = options.ShowLegend; + cbxShowFiguresOfMerit.Checked = options.ShowFiguresOfMerit; + cbxShowBootstrapCurves.Checked = options.ShowBootstrapCurves; + } + + public void OkDialog() + { + var options = Properties.Settings.Default.CalibrationCurveOptions; + options = options.ChangeLineWidth((float)textLineWidth.Value) + .ChangeFontSize(GraphFontSize.GetFontSize(textSizeComboBox).PointSize) + .ChangeLogXAxis(cbxLogXAxis.Checked) + .ChangeLogYAxis(cbxLogYAxis.Checked) + .ChangeDisplaySampleTypes(checkedListBoxSampleTypes.CheckedItems.OfType()) + .ChangeSingleBatch(cbxSingleBatch.Checked) + .ChangeShowLegend(cbxShowLegend.Checked) + .ChangeShowFiguresOfMerit(cbxShowFiguresOfMerit.Checked) + .ChangeShowBootstrapCurves(cbxShowBootstrapCurves.Checked); + Properties.Settings.Default.CalibrationCurveOptions = options; + DialogResult = DialogResult.OK; + } + + private void btnOk_Click(object sender, EventArgs e) + { + OkDialog(); + } + } +} diff --git a/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationCurveOptionsDlg.resx b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationCurveOptionsDlg.resx new file mode 100644 index 0000000000..2b482c6cd3 --- /dev/null +++ b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationCurveOptionsDlg.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 155, 17 + + \ No newline at end of file diff --git a/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationForm.Designer.cs b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationForm.Designer.cs index 777d6505e8..8731970f72 100644 --- a/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationForm.Designer.cs +++ b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationForm.Designer.cs @@ -28,125 +28,32 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CalibrationForm)); - this.zedGraphControl = new ZedGraph.ZedGraphControl(); - this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); - this.logXContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.logYAxisContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.showToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.showSampleTypesContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.singleBatchContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.showLegendContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.showSelectionContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.showFiguresOfMeritContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.contextMenuStrip1.SuspendLayout(); + this.calibrationGraphControl1 = new pwiz.Skyline.Controls.Graphs.Calibration.CalibrationGraphControl(); this.SuspendLayout(); // - // zedGraphControl + // calibrationGraphControl1 // - resources.ApplyResources(this.zedGraphControl, "zedGraphControl"); - this.zedGraphControl.EditButtons = System.Windows.Forms.MouseButtons.Left; - this.zedGraphControl.EditModifierKeys = System.Windows.Forms.Keys.None; - this.zedGraphControl.IsShowCopyMessage = false; - this.zedGraphControl.IsZoomOnMouseCenter = true; - this.zedGraphControl.Name = "zedGraphControl"; - this.zedGraphControl.ScrollGrace = 0D; - this.zedGraphControl.ScrollMaxX = 0D; - this.zedGraphControl.ScrollMaxY = 0D; - this.zedGraphControl.ScrollMaxY2 = 0D; - this.zedGraphControl.ScrollMinX = 0D; - this.zedGraphControl.ScrollMinY = 0D; - this.zedGraphControl.ScrollMinY2 = 0D; - this.zedGraphControl.ContextMenuBuilder += new ZedGraph.ZedGraphControl.ContextMenuBuilderEventHandler(this.zedGraphControl_ContextMenuBuilder); - this.zedGraphControl.MouseDownEvent += new ZedGraph.ZedGraphControl.ZedMouseEventHandler(this.zedGraphControl_MouseDownEvent); - this.zedGraphControl.MouseMoveEvent += new ZedGraph.ZedGraphControl.ZedMouseEventHandler(this.zedGraphControl_MouseMoveEvent); - // - // contextMenuStrip1 - // - this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.logXContextMenuItem, - this.logYAxisContextMenuItem, - this.showToolStripMenuItem, - this.showSampleTypesContextMenuItem, - this.singleBatchContextMenuItem, - this.showLegendContextMenuItem, - this.showSelectionContextMenuItem, - this.showFiguresOfMeritContextMenuItem}); - this.contextMenuStrip1.Name = "contextMenuStrip1"; - resources.ApplyResources(this.contextMenuStrip1, "contextMenuStrip1"); - // - // logXContextMenuItem - // - this.logXContextMenuItem.Name = "logXContextMenuItem"; - resources.ApplyResources(this.logXContextMenuItem, "logXContextMenuItem"); - this.logXContextMenuItem.Click += new System.EventHandler(this.logXAxisContextMenuItem_Click); - // - // logYAxisContextMenuItem - // - this.logYAxisContextMenuItem.Name = "logYAxisContextMenuItem"; - resources.ApplyResources(this.logYAxisContextMenuItem, "logYAxisContextMenuItem"); - this.logYAxisContextMenuItem.Click += new System.EventHandler(this.logYAxisContextMenuItem_Click); - // - // showToolStripMenuItem - // - this.showToolStripMenuItem.Name = "showToolStripMenuItem"; - resources.ApplyResources(this.showToolStripMenuItem, "showToolStripMenuItem"); - // - // showSampleTypesContextMenuItem - // - this.showSampleTypesContextMenuItem.Name = "showSampleTypesContextMenuItem"; - resources.ApplyResources(this.showSampleTypesContextMenuItem, "showSampleTypesContextMenuItem"); - // - // singleBatchContextMenuItem - // - this.singleBatchContextMenuItem.Name = "singleBatchContextMenuItem"; - resources.ApplyResources(this.singleBatchContextMenuItem, "singleBatchContextMenuItem"); - this.singleBatchContextMenuItem.Click += new System.EventHandler(this.singleBatchContextMenuItem_Click); - // - // showLegendContextMenuItem - // - this.showLegendContextMenuItem.Name = "showLegendContextMenuItem"; - resources.ApplyResources(this.showLegendContextMenuItem, "showLegendContextMenuItem"); - this.showLegendContextMenuItem.Click += new System.EventHandler(this.showLegendContextMenuItem_Click); - // - // showSelectionContextMenuItem - // - this.showSelectionContextMenuItem.Name = "showSelectionContextMenuItem"; - resources.ApplyResources(this.showSelectionContextMenuItem, "showSelectionContextMenuItem"); - this.showSelectionContextMenuItem.Click += new System.EventHandler(this.showSelectionContextMenuItem_Click); - // - // showFiguresOfMeritContextMenuItem - // - this.showFiguresOfMeritContextMenuItem.Name = "showFiguresOfMeritContextMenuItem"; - resources.ApplyResources(this.showFiguresOfMeritContextMenuItem, "showFiguresOfMeritContextMenuItem"); - this.showFiguresOfMeritContextMenuItem.Click += new System.EventHandler(this.showFiguresOfMeritContextMenuItem_Click); + resources.ApplyResources(this.calibrationGraphControl1, "calibrationGraphControl1"); + this.calibrationGraphControl1.ModeUIAwareFormHelper = null; + this.calibrationGraphControl1.Name = "calibrationGraphControl1"; + this.calibrationGraphControl1.Options = null; + this.calibrationGraphControl1.PointClicked += new System.Action(this.calibrationGraphControl1_PointClicked); // // CalibrationForm // resources.ApplyResources(this, "$this"); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.Controls.Add(this.zedGraphControl); + this.Controls.Add(this.calibrationGraphControl1); this.KeyPreview = true; this.Name = "CalibrationForm"; this.ShowInTaskbar = false; this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.CalibrationForm_KeyDown); - this.contextMenuStrip1.ResumeLayout(false); this.ResumeLayout(false); } #endregion - - private ZedGraph.ZedGraphControl zedGraphControl; - private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; - private System.Windows.Forms.ToolStripMenuItem logXContextMenuItem; - private System.Windows.Forms.ToolStripMenuItem showToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem showSampleTypesContextMenuItem; - private System.Windows.Forms.ToolStripMenuItem showLegendContextMenuItem; - private System.Windows.Forms.ToolStripMenuItem showSelectionContextMenuItem; - private System.Windows.Forms.ToolStripMenuItem showFiguresOfMeritContextMenuItem; - private System.Windows.Forms.ToolStripMenuItem logYAxisContextMenuItem; - private System.Windows.Forms.ToolStripMenuItem singleBatchContextMenuItem; + private CalibrationGraphControl calibrationGraphControl1; } } \ No newline at end of file diff --git a/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationForm.cs b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationForm.cs index 8d42210738..9f8e41d137 100644 --- a/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationForm.cs +++ b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationForm.cs @@ -17,60 +17,48 @@ * limitations under the License. */ using System; -using System.Collections.Generic; -using System.Drawing; +using System.ComponentModel; using System.Linq; using System.Windows.Forms; using pwiz.Skyline.Controls.SeqNode; using pwiz.Skyline.Model; -using pwiz.Skyline.Model.AuditLog; using pwiz.Skyline.Model.Databinding.Entities; using pwiz.Skyline.Model.DocSettings.AbsoluteQuantification; using pwiz.Skyline.Model.GroupComparison; -using pwiz.Skyline.Model.Results; using pwiz.Skyline.Properties; using pwiz.Skyline.Util; using pwiz.Skyline.Util.Extensions; using ZedGraph; -using SampleType = pwiz.Skyline.Model.DocSettings.AbsoluteQuantification.SampleType; namespace pwiz.Skyline.Controls.Graphs.Calibration { public partial class CalibrationForm : DockableFormEx, IUpdatable { private readonly SkylineWindow _skylineWindow; - private CurveList _scatterPlots; private string _originalFormTitle; public CalibrationForm(SkylineWindow skylineWindow) { InitializeComponent(); _skylineWindow = skylineWindow; - - zedGraphControl.MasterPane.Border.IsVisible = false; - zedGraphControl.GraphPane.Border.IsVisible = false; - zedGraphControl.GraphPane.Chart.Border.IsVisible = false; - zedGraphControl.GraphPane.XAxis.Title.Text = QuantificationStrings.Analyte_Concentration; - zedGraphControl.GraphPane.YAxis.Title.Text = QuantificationStrings.CalibrationCurveFitter_GetYAxisTitle_Peak_Area; - zedGraphControl.GraphPane.Legend.IsVisible = false; - zedGraphControl.GraphPane.Title.Text = null; - zedGraphControl.GraphPane.Title.FontSpec.Size = 12f; - zedGraphControl.GraphPane.IsFontsScaled = false; - zedGraphControl.GraphPane.XAxis.MajorTic.IsOpposite = false; - zedGraphControl.GraphPane.XAxis.MinorTic.IsOpposite = false; - zedGraphControl.GraphPane.YAxis.MajorTic.IsOpposite = false; - zedGraphControl.GraphPane.YAxis.MinorTic.IsOpposite = false; - zedGraphControl.IsZoomOnMouseCenter = true; + calibrationGraphControl1.SkylineWindow = skylineWindow; _originalFormTitle = Text; } - public static CalibrationCurveOptions Options { get { return Settings.Default.CalibrationCurveOptions; } } - protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); if (null != _skylineWindow) { _skylineWindow.DocumentUIChangedEvent += SkylineWindowOnDocumentUIChangedEvent; + Settings.Default.PropertyChanged += Settings_OnPropertyChanged; + UpdateUI(false); + } + } + + private void Settings_OnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Settings.CalibrationCurveOptions)) + { UpdateUI(false); } } @@ -82,6 +70,7 @@ private void SkylineWindowOnDocumentUIChangedEvent(object sender, DocumentChange protected override void OnHandleDestroyed(EventArgs e) { + Settings.Default.PropertyChanged -= Settings_OnPropertyChanged; if (null != _skylineWindow) { _skylineWindow.DocumentUIChangedEvent -= SkylineWindowOnDocumentUIChangedEvent; @@ -102,11 +91,7 @@ public void UpdateUI(bool selectionChanged) { return; } - zedGraphControl.GraphPane.GraphObjList.Clear(); - zedGraphControl.GraphPane.CurveList.Clear(); DisplayCalibrationCurve(); - zedGraphControl.AxisChange(); - zedGraphControl.Invalidate(); } catch (Exception e) { @@ -114,40 +99,36 @@ public void UpdateUI(bool selectionChanged) } } - public CalibrationCurve CalibrationCurve { get; private set; } - public CalibrationCurveMetrics CalibrationCurveMetrics { get; private set; } - public FiguresOfMerit FiguresOfMerit { get; private set; } - + public CalibrationCurve CalibrationCurve + { + get { return calibrationGraphControl1.CalibrationCurve; } + } + public CalibrationCurveMetrics CalibrationCurveMetrics + { + get { return calibrationGraphControl1.CalibrationCurveMetrics; } + } private void DisplayCalibrationCurve() { Text = TabText = _originalFormTitle; - CalibrationCurveOptions options = Settings.Default.CalibrationCurveOptions; - zedGraphControl.GraphPane.YAxis.Type = options.LogYAxis ? AxisType.Log : AxisType.Linear; - zedGraphControl.GraphPane.XAxis.Type = options.LogXAxis ? AxisType.Log : AxisType.Linear; - bool logPlot = options.LogXAxis || options.LogYAxis; - zedGraphControl.GraphPane.Legend.IsVisible = options.ShowLegend; - _scatterPlots = null; - CalibrationCurve = null; - FiguresOfMerit = FiguresOfMerit.EMPTY; - SrmDocument document = DocumentUiContainer.DocumentUI; + var document = _skylineWindow.DocumentUI; + if (!document.Settings.HasResults) { - zedGraphControl.GraphPane.Title.Text = - QuantificationStrings.CalibrationForm_DisplayCalibrationCurve_No_results_available; + DisplayError(QuantificationStrings.CalibrationForm_DisplayCalibrationCurve_No_results_available); return; } IdPeptideDocNode idPeptideDocNode = GetSelectedPeptide(); if (idPeptideDocNode == null) { - zedGraphControl.GraphPane.Title.Text = - ModeUIAwareStringFormat(QuantificationStrings - .CalibrationForm_DisplayCalibrationCurve_Select_a_peptide_to_see_its_calibration_curve); + DisplayError(ModeUIAwareStringFormat(QuantificationStrings + .CalibrationForm_DisplayCalibrationCurve_Select_a_peptide_to_see_its_calibration_curve)); return; } if (document.FindNodeIndex(idPeptideDocNode.PeptideGroup) < 0) { - zedGraphControl.GraphPane.Title.Text = ModeUIAwareStringFormat(QuantificationStrings.CalibrationForm_DisplayCalibrationCurve_The_selected_peptide_is_no_longer_part_of_the_Skyline_document_); + DisplayError(ModeUIAwareStringFormat(QuantificationStrings + .CalibrationForm_DisplayCalibrationCurve_The_selected_peptide_is_no_longer_part_of_the_Skyline_document_)); return; } CalibrationCurveFitter curveFitter = CalibrationCurveFitter.GetCalibrationCurveFitter(document, idPeptideDocNode); @@ -158,334 +139,15 @@ private void DisplayCalibrationCurve() } Text = TabText = GetFormTitle(curveFitter); - if (mainPeptideQuantifier.QuantificationSettings.RegressionFit == RegressionFit.NONE) - { - if (!(mainPeptideQuantifier.NormalizationMethod is NormalizationMethod.RatioToLabel)) - { - zedGraphControl.GraphPane.Title.Text = - ModeUIAwareStringFormat(QuantificationStrings.CalibrationForm_DisplayCalibrationCurve_Use_the_Quantification_tab_on_the_Peptide_Settings_dialog_to_control_the_conversion_of_peak_areas_to_concentrations_); - } - else - { - if (!idPeptideDocNode.PeptideDocNode.InternalStandardConcentration.HasValue) - { - zedGraphControl.GraphPane.Title.Text = - ModeUIAwareStringFormat(QuantificationStrings.CalibrationForm_DisplayCalibrationCurve_To_convert_peak_area_ratios_to_concentrations__specify_the_internal_standard_concentration_for__0__, idPeptideDocNode.PeptideDocNode.ModifiedSequenceDisplay); - } - else - { - zedGraphControl.GraphPane.Title.Text = null; - } - } - } - else - { - if (curveFitter.GetStandardConcentrations().Any()) - { - zedGraphControl.GraphPane.Title.Text = null; - } - else - { - zedGraphControl.GraphPane.Title.Text = QuantificationStrings.CalibrationForm_DisplayCalibrationCurve_To_fit_a_calibration_curve__set_the_Sample_Type_of_some_replicates_to_Standard__and_specify_their_concentration_; - } - } - - zedGraphControl.GraphPane.XAxis.Title.Text = curveFitter.GetXAxisTitle(); - zedGraphControl.GraphPane.YAxis.Title.Text = curveFitter.GetYAxisTitle(); - curveFitter.GetCalibrationCurveAndMetrics(out CalibrationCurve calibrationCurve, out CalibrationCurveMetrics calibrationCurveRow); - CalibrationCurve = calibrationCurve; - CalibrationCurveMetrics = calibrationCurveRow; - - FiguresOfMerit = curveFitter.GetFiguresOfMerit(CalibrationCurve); - double minX = double.MaxValue, maxX = double.MinValue; - double minY = double.MaxValue; - _scatterPlots = new CurveList(); - - IEnumerable sampleTypes = SampleType.ListSampleTypes() - .Where(Options.DisplaySampleType); - foreach (var sampleType in sampleTypes) - { - var samplePeptideQuantifier = curveFitter.GetPeptideQuantifier(sampleType); - PointPairList pointPairList = new PointPairList(); - PointPairList pointPairListExcluded = new PointPairList(); - foreach (var standardIdentifier in curveFitter.EnumerateCalibrationPoints()) - { - if (!Equals(sampleType, curveFitter.GetSampleType(standardIdentifier))) - { - continue; - } - - AnnotatedDouble normalizedArea = curveFitter.GetAnnotatedNormalizedPeakArea(standardIdentifier); - double? xCalculated = curveFitter.GetCalculatedXValue(CalibrationCurve, standardIdentifier); - double? x = curveFitter.GetSpecifiedXValue(standardIdentifier) - ?? xCalculated; - if (normalizedArea != null && x.HasValue) - { - PointPair point = new PointPair(x.Value, normalizedArea.Raw) {Tag = standardIdentifier }; - bool included = normalizedArea.Strict != null; - if (included && sampleType.AllowExclude) - { - if (null == standardIdentifier.LabelType && - samplePeptideQuantifier.PeptideDocNode.IsExcludeFromCalibration(standardIdentifier - .ReplicateIndex)) - { - included = false; - } - } - - if (included) - { - pointPairList.Add(point); - } - else - { - pointPairListExcluded.Add(point); - } - if (!IsNumber(x) || !IsNumber(normalizedArea.Raw)) - { - continue; - } - if (!logPlot || x.Value > 0) - { - minX = Math.Min(minX, x.Value); - } - if (!logPlot || normalizedArea.Raw > 0) - { - minY = Math.Min(minY, normalizedArea.Raw); - } - maxX = Math.Max(maxX, x.Value); - if (IsNumber(xCalculated)) - { - maxX = Math.Max(maxX, xCalculated.Value); - if (!logPlot || xCalculated.Value > 0) - { - minX = Math.Min(minX, xCalculated.Value); - } - } - } - } - - string curveLabel = sampleType.ToString(); - if (!ReferenceEquals(samplePeptideQuantifier, mainPeptideQuantifier)) - { - curveLabel = QualifyCurveNameWithSurrogate(curveLabel, samplePeptideQuantifier.PeptideDocNode); - } - - if (pointPairList.Any()) - { - var lineItem = zedGraphControl.GraphPane.AddCurve(curveLabel, pointPairList, - sampleType.Color, sampleType.SymbolType); - lineItem.Line.IsVisible = false; - lineItem.Symbol.Fill = new Fill(sampleType.Color); - _scatterPlots.Add(lineItem); - } - if (pointPairListExcluded.Any()) - { - var symbolType = sampleType.SymbolType; - if (symbolType == SymbolType.XCross) - { - // Excluded or invalid points get drawn without being filled. - // The XCross looks the same regardless of whether it is filled, so we switch to a circle - // when it has a problem - symbolType = SymbolType.Circle; - } - var lineItem = zedGraphControl.GraphPane.AddCurve(pointPairList.Any() ? null : curveLabel, pointPairListExcluded, - sampleType.Color, symbolType); - lineItem.Line.IsVisible = false; - _scatterPlots.Add(lineItem); - } - } - List labelLines = new List(); - RegressionFit regressionFit = document.Settings.PeptideSettings.Quantification.RegressionFit; - if (regressionFit != RegressionFit.NONE) - { - if (minX <= maxX) - { - int interpolatedLinePointCount = 100; - if (!logPlot && regressionFit != RegressionFit.LINEAR_IN_LOG_SPACE) - { - if (regressionFit == RegressionFit.LINEAR_THROUGH_ZERO) - { - minX = Math.Min(0, minX); - } - if (regressionFit != RegressionFit.QUADRATIC) - { - interpolatedLinePointCount = 2; - } - } - double[] xValues; - if (CalibrationCurve is CalibrationCurve.Bilinear bilinearCalibrationCurve) - { - xValues = new[] {minX, bilinearCalibrationCurve.TurningPoint, maxX}; - } - else - { - xValues = new[] {minX, maxX}; - } - Array.Sort(xValues); - LineItem interpolatedLine = CreateInterpolatedLine(CalibrationCurve, xValues, - interpolatedLinePointCount, logPlot); - if (null != interpolatedLine) - { - zedGraphControl.GraphPane.CurveList.Add(interpolatedLine); - } - } - labelLines.Add(CalibrationCurveMetrics.ToString()); - - if (CalibrationCurveMetrics.RSquared.HasValue) - { - labelLines.Add(CalibrationCurveMetrics.RSquaredDisplayText(CalibrationCurveMetrics.RSquared.Value)); - } - if (!Equals(curveFitter.QuantificationSettings.RegressionWeighting, RegressionWeighting.NONE)) - { - labelLines.Add(string.Format(@"{0}: {1}", - QuantificationStrings.Weighting, curveFitter.QuantificationSettings.RegressionWeighting)); - } - if (options.ShowFiguresOfMerit) - { - string strFiguresOfMerit = FiguresOfMerit.ToString(); - if (!string.IsNullOrEmpty(strFiguresOfMerit)) - { - labelLines.Add(strFiguresOfMerit); - } - } - } - - CalibrationPoint? selectionIdentifier = null; - if (options.ShowSelection) - { - if (curveFitter.IsotopologResponseCurve) - { - var labelType = (_skylineWindow.SequenceTree.SelectedNode as SrmTreeNode) - ?.GetNodeOfType()?.DocNode.LabelType; - if (labelType != null) - { - selectionIdentifier = - new CalibrationPoint(_skylineWindow.SelectedResultsIndex, - labelType); - } - } - else - { - selectionIdentifier = - new CalibrationPoint(_skylineWindow.SelectedResultsIndex, null); - } - } - if (selectionIdentifier.HasValue) { - AnnotatedDouble ySelected = curveFitter.GetAnnotatedNormalizedPeakArea(selectionIdentifier.Value); - if (IsNumber(ySelected?.Raw)) - { - double? xSelected = CalibrationCurve.GetX(ySelected.Raw); - var selectedLineColor = Color.FromArgb(128, GraphSummary.ColorSelected); - const float selectedLineWidth = 2; - double? xSpecified = curveFitter.GetSpecifiedXValue(selectionIdentifier.Value); - if (IsNumber(xSelected)) - { - ArrowObj arrow = new ArrowObj(xSelected.Value, ySelected.Raw, xSelected.Value, - ySelected.Raw) {Line = {Color = GraphSummary.ColorSelected}}; - zedGraphControl.GraphPane.GraphObjList.Insert(0, arrow); - var verticalLine = new LineObj(xSelected.Value, ySelected.Raw, xSelected.Value, - options.LogYAxis ? minY / 10 : 0) - { - Line = {Color = selectedLineColor, Width = selectedLineWidth}, - Location = {CoordinateFrame = CoordType.AxisXYScale}, - ZOrder = ZOrder.E_BehindCurves, - IsClippedToChartRect = true - }; - zedGraphControl.GraphPane.GraphObjList.Add(verticalLine); - if (IsNumber(xSpecified)) - { - var horizontalLine = new LineObj(xSpecified.Value, ySelected.Raw, xSelected.Value, - ySelected.Raw) - { - Line = {Color = selectedLineColor, Width = selectedLineWidth}, - Location = {CoordinateFrame = CoordType.AxisXYScale}, - ZOrder = ZOrder.E_BehindCurves, - IsClippedToChartRect = true - }; - zedGraphControl.GraphPane.GraphObjList.Add(horizontalLine); - } - } - else - { - // We were not able to map the observed intensity back to the calibration curve, but we still want to - // indicate where the currently selected point is. - if (IsNumber(xSpecified)) - { - // If the point has a specified concentration, then use that. - ArrowObj arrow = new ArrowObj(xSpecified.Value, ySelected.Raw, xSpecified.Value, - ySelected.Raw) {Line = {Color = GraphSummary.ColorSelected}}; - zedGraphControl.GraphPane.GraphObjList.Insert(0, arrow); - } - else - { - // Otherwise, draw a horizontal line at the appropriate y-value. - var horizontalLine = new LineObj(minX, ySelected.Raw, maxX, ySelected.Raw) - { - Line = {Color = selectedLineColor, Width = selectedLineWidth}, - Location = {CoordinateFrame = CoordType.AxisXYScale}, - IsClippedToChartRect = true, - }; - ZedGraphControl.GraphPane.GraphObjList.Add(horizontalLine); - } - } - } - - AnnotatedDouble calculatedConcentration; - if (curveFitter.IsotopologResponseCurve) - { - calculatedConcentration = - AnnotatedDouble.Of(curveFitter.GetCalculatedConcentration(CalibrationCurve, selectionIdentifier.Value)); - } - else - { - var quantificationResult = curveFitter.GetPeptideQuantificationResult(selectionIdentifier.Value.ReplicateIndex); - calculatedConcentration = quantificationResult?.CalculatedConcentration; - } - if (calculatedConcentration != null) - { - labelLines.Add(string.Format(@"{0} = {1}", - QuantificationStrings.Calculated_Concentration, - QuantificationResult.FormatCalculatedConcentration(calculatedConcentration, - curveFitter.QuantificationSettings.Units))); - } - else if (ySelected?.Message != null) - { - labelLines.Add(ySelected.Message); - } - } - if (Options.ShowFiguresOfMerit) - { - if (IsNumber(FiguresOfMerit.LimitOfDetection)) - { - var lodLine = new LineObj(Color.DarkMagenta, FiguresOfMerit.LimitOfDetection.Value, 0, - FiguresOfMerit.LimitOfDetection.Value, 1) - { - Location = { CoordinateFrame = CoordType.XScaleYChartFraction } - }; - zedGraphControl.GraphPane.GraphObjList.Add(lodLine); - } - if (IsNumber(FiguresOfMerit.LimitOfQuantification)) - { - var loqLine = new LineObj(Color.DarkCyan, FiguresOfMerit.LimitOfQuantification.Value, 0, - FiguresOfMerit.LimitOfQuantification.Value, 1) - { - Location = { CoordinateFrame = CoordType.XScaleYChartFraction } - }; - zedGraphControl.GraphPane.GraphObjList.Add(loqLine); - } - } - if (labelLines.Any()) + var settings = new CalibrationGraphControl.Settings(document, curveFitter) + .ChangeSelectedResultsIndex(_skylineWindow.SelectedResultsIndex); + if (curveFitter.IsotopologResponseCurve) { - TextObj text = new TextObj(TextUtil.LineSeparate(labelLines), .01, 0, - CoordType.ChartFraction, AlignH.Left, AlignV.Top) - { - IsClippedToChartRect = true, - ZOrder = ZOrder.E_BehindCurves, - FontSpec = GraphSummary.CreateFontSpec(Color.Black), - }; - zedGraphControl.GraphPane.GraphObjList.Add(text); + var labelType = (_skylineWindow.SequenceTree.SelectedNode as SrmTreeNode) + ?.GetNodeOfType()?.DocNode.LabelType; + settings = settings.ChangeSelectedLabelType(labelType); } + calibrationGraphControl1.Update(settings); } private string GetFormTitle(CalibrationCurveFitter curveFitter) @@ -533,277 +195,18 @@ protected override void OnClosed(EventArgs e) Dispose(); } - private bool zedGraphControl_MouseMoveEvent(ZedGraphControl sender, MouseEventArgs e) - { - var replicateIndex = ReplicateIndexFromPoint(e.Location); - if (replicateIndex.HasValue) - { - zedGraphControl.Cursor = Cursors.Hand; - return true; - } - return false; - } - - private bool zedGraphControl_MouseDownEvent(ZedGraphControl sender, MouseEventArgs e) - { - if (e.Button != MouseButtons.Left) - { - return false; - } - CalibrationPoint? replicateIndex = ReplicateIndexFromPoint(e.Location); - if (replicateIndex.HasValue) - { - _skylineWindow.SelectedResultsIndex = replicateIndex.Value.ReplicateIndex; - if (null != replicateIndex.Value.LabelType) - { - var selectedTransitionGroup = (_skylineWindow.SequenceTree.SelectedNode as SrmTreeNode) - ?.GetNodeOfType(); - if (selectedTransitionGroup == null || !Equals(selectedTransitionGroup.DocNode.LabelType, - replicateIndex.Value.LabelType)) - { - var selectedPeptide = (_skylineWindow.SequenceTree.SelectedNode as SrmTreeNode) - ?.GetNodeOfType(); - if (selectedPeptide != null) - { - var transitionGroupToSelect = selectedPeptide.Nodes.OfType() - .FirstOrDefault(node => Equals(replicateIndex.Value.LabelType, node.DocNode.LabelType)); - if (transitionGroupToSelect != null) - { - _skylineWindow.SequenceTree.SelectedPath = transitionGroupToSelect.Path; - } - } - } - } - return true; - } - return false; - } - - public CalibrationPoint? ReplicateIndexFromPoint(Point pt) - { - if (null == _scatterPlots) - { - return null; - } - PointF ptF = new PointF(pt.X, pt.Y); - CurveItem nearestCurve; - int iNeareast; - if (!zedGraphControl.GraphPane.FindNearestPoint(ptF, _scatterPlots, out nearestCurve, out iNeareast)) - { - return null; - } - PointPair nearPoint = nearestCurve.Points[iNeareast]; - PointF nearPointScreen = zedGraphControl.GraphPane.GeneralTransform(nearPoint.X, nearPoint.Y, CoordType.AxisXYScale); - if (Math.Abs(nearPointScreen.X - pt.X) > 5 || Math.Abs(nearPointScreen.Y - pt.Y) > 5) - { - return null; - } - return nearestCurve.Points[iNeareast].Tag as CalibrationPoint?; - } - - private bool IsEnableIsotopologResponseCurve() - { - return true == GetSelectedPeptide()?.PeptideDocNode.TransitionGroups - .Any(tg => tg.PrecursorConcentration.HasValue); - } - - private void zedGraphControl_ContextMenuBuilder(ZedGraphControl sender, ContextMenuStrip menuStrip, Point mousePt, ZedGraphControl.ContextMenuObjectState objState) - { - var calibrationCurveOptions = Settings.Default.CalibrationCurveOptions; - singleBatchContextMenuItem.Checked = calibrationCurveOptions.SingleBatch; - if (IsEnableIsotopologResponseCurve()) - { - singleBatchContextMenuItem.Visible = true; - } - else - { - singleBatchContextMenuItem.Visible = CalibrationCurveFitter.AnyBatchNames(_skylineWindow.Document.Settings); - } - var replicateIndexFromPoint = ReplicateIndexFromPoint(mousePt); - if (replicateIndexFromPoint.HasValue && null == replicateIndexFromPoint.Value.LabelType) - { - ToolStripMenuItem excludeStandardMenuItem - = MakeExcludeStandardMenuItem(replicateIndexFromPoint.Value.ReplicateIndex); - if (excludeStandardMenuItem != null) - { - menuStrip.Items.Clear(); - menuStrip.Items.Add(excludeStandardMenuItem); - return; - } - } - - showSampleTypesContextMenuItem.DropDownItems.Clear(); - foreach (var sampleType in SampleType.ListSampleTypes()) - { - showSampleTypesContextMenuItem.DropDownItems.Add(MakeShowSampleTypeMenuItem(sampleType)); - } - logXContextMenuItem.Checked = Options.LogXAxis; - logYAxisContextMenuItem.Checked = Options.LogYAxis; - showLegendContextMenuItem.Checked = Options.ShowLegend; - showSelectionContextMenuItem.Checked = Options.ShowSelection; - showFiguresOfMeritContextMenuItem.Checked = Options.ShowFiguresOfMerit; - ZedGraphHelper.BuildContextMenu(sender, menuStrip, true); - if (!menuStrip.Items.Contains(logXContextMenuItem)) - { - int index = 0; - menuStrip.Items.Insert(index++, logXContextMenuItem); - menuStrip.Items.Insert(index++, logYAxisContextMenuItem); - menuStrip.Items.Insert(index++, showSampleTypesContextMenuItem); - menuStrip.Items.Insert(index++, singleBatchContextMenuItem); - menuStrip.Items.Insert(index++, showLegendContextMenuItem); - menuStrip.Items.Insert(index++, showSelectionContextMenuItem); - menuStrip.Items.Insert(index++, showFiguresOfMeritContextMenuItem); - menuStrip.Items.Insert(index++, new ToolStripSeparator()); - } - } public ToolStripMenuItem MakeExcludeStandardMenuItem(int replicateIndex) { - var document = DocumentUiContainer.DocumentUI; - if (!document.Settings.HasResults) - { - return null; - } - ChromatogramSet chromatogramSet = null; - if (replicateIndex >= 0 && - replicateIndex < document.Settings.MeasuredResults.Chromatograms.Count) - { - chromatogramSet = document.Settings.MeasuredResults.Chromatograms[replicateIndex]; - } - if (chromatogramSet == null) - { - return null; - } - if (!chromatogramSet.SampleType.AllowExclude) - { - return null; - } - - IdPeptideDocNode idPeptideDocNode = GetSelectedPeptide(); - if (idPeptideDocNode == null) - { - return null; - } - - if (idPeptideDocNode.PeptideDocNode.SurrogateCalibrationCurve != null) - { - idPeptideDocNode = - document.Settings.GetSurrogateStandards(idPeptideDocNode.PeptideDocNode.SurrogateCalibrationCurve) - .FirstOrDefault() ?? idPeptideDocNode; - } - - var peptideDocNode = idPeptideDocNode.PeptideDocNode; - bool isExcluded = peptideDocNode.IsExcludeFromCalibration(replicateIndex); - var menuItemText = isExcluded ? QuantificationStrings.CalibrationForm_MakeExcludeStandardMenuItem_Include_Standard - : QuantificationStrings.CalibrationForm_MakeExcludeStandardMenuItem_Exclude_Standard; - var menuItem = new ToolStripMenuItem(menuItemText, null, (sender, args) => - { - _skylineWindow.ModifyDocument(menuItemText, - doc => SetExcludeStandard(doc, idPeptideDocNode.IdentityPath, replicateIndex, !isExcluded), docPair => - { - var msgType = isExcluded - ? MessageType.set_included_standard - : MessageType.set_excluded_standard; - return AuditLogEntry.CreateSingleMessageEntry(new MessageInfo(msgType, docPair.NewDocumentType, PeptideTreeNode.GetLabel(peptideDocNode, string.Empty), chromatogramSet.Name)); - }); - }); - return menuItem; + return calibrationGraphControl1.MakeExcludeStandardMenuItem(replicateIndex); } - private ToolStripMenuItem MakeShowSampleTypeMenuItem(SampleType sampleType) - { - ToolStripMenuItem menuItem = new ToolStripMenuItem(sampleType.ToString()) - { - Checked = Options.DisplaySampleTypes.Contains(sampleType.Name) - }; - menuItem.Click += (sender, args) => - { - if (menuItem.Checked) - { - Options.DisplaySampleTypes = Options.DisplaySampleTypes.Except(new[] {sampleType.Name}).ToArray(); - } - else - { - Options.DisplaySampleTypes = - Options.DisplaySampleTypes.Concat(new[] {sampleType.Name}).Distinct().ToArray(); - } - UpdateUI(false); - }; - return menuItem; - } - - protected override void OnFormClosed(FormClosedEventArgs e) { base.OnFormClosed(e); Dispose(); } - private void logXAxisContextMenuItem_Click(object sender, EventArgs e) - { - Options.LogXAxis = !Options.LogXAxis; - UpdateUI(false); - } - - private void logYAxisContextMenuItem_Click(object sender, EventArgs e) - { - Options.LogYAxis = !Options.LogYAxis; - UpdateUI(false); - } - - - private LineItem CreateInterpolatedLine(CalibrationCurve calibrationCurve, double[] xValues, int pointCount, bool logPlot) - { - PointPairList pointPairList = new PointPairList(); - for (int iRange = 0; iRange < xValues.Length - 1; iRange++) - { - double minX = xValues[iRange]; - double maxX = xValues[iRange + 1]; - for (int i = 0; i < pointCount; i++) - { - double x; - if (logPlot) - { - x = Math.Exp((Math.Log(minX) * (pointCount - 1 - i) + Math.Log(maxX) * i) / (pointCount - 1)); - } - else - { - x = (minX * (pointCount - 1 - i) + maxX * i) / (pointCount - 1); - } - double? y = calibrationCurve.GetY(x); - if (y.HasValue) - { - pointPairList.Add(x, y.Value); - } - } - } - if (!pointPairList.Any()) - { - return null; - } - return new LineItem(QuantificationStrings.Calibration_Curve, pointPairList, Color.Gray, SymbolType.None); - } - - private void showLegendContextMenuItem_Click(object sender, EventArgs e) - { - Options.ShowLegend = !Options.ShowLegend; - UpdateUI(false); - } - - private void showSelectionContextMenuItem_Click(object sender, EventArgs e) - { - Options.ShowSelection = !Options.ShowSelection; - UpdateUI(false); - } - - - private void showFiguresOfMeritContextMenuItem_Click(object sender, EventArgs e) - { - Options.ShowFiguresOfMerit = !Options.ShowFiguresOfMerit; - UpdateUI(false); - } - - private void CalibrationForm_KeyDown(object sender, KeyEventArgs e) { switch (e.KeyCode) @@ -829,34 +232,37 @@ public static bool IsNumber(double? value) } #region Test Methods - public ZedGraphControl ZedGraphControl { get { return zedGraphControl; } } + public ZedGraphControl ZedGraphControl { get { return calibrationGraphControl1.ZedGraphControl; } } #endregion - private SrmDocument SetExcludeStandard(SrmDocument document, IdentityPath peptideIdPath, int resultsIndex, bool exclude) + public void DisplayError(string message) { - if (!document.Settings.HasResults) - { - return document; - } - var peptideDocNode = (PeptideDocNode) document.FindNode(peptideIdPath); - if (peptideDocNode == null) - { - return document; - } - if (resultsIndex < 0 || resultsIndex >= document.Settings.MeasuredResults.Chromatograms.Count) - { - return document; - } - bool wasExcluded = peptideDocNode.IsExcludeFromCalibration(resultsIndex); - return (SrmDocument) document.ReplaceChild(peptideIdPath.Parent, - peptideDocNode.ChangeExcludeFromCalibration(resultsIndex, !wasExcluded)); + calibrationGraphControl1.DisplayError(message); } - private void singleBatchContextMenuItem_Click(object sender, EventArgs e) + private void calibrationGraphControl1_PointClicked(CalibrationPoint calibrationPoint) { - Settings.Default.CalibrationCurveOptions.SingleBatch = - !Settings.Default.CalibrationCurveOptions.SingleBatch; - UpdateUI(false); + _skylineWindow.SelectedResultsIndex = calibrationPoint.ReplicateIndex; + if (null != calibrationPoint.LabelType) + { + var selectedTransitionGroup = (_skylineWindow.SequenceTree.SelectedNode as SrmTreeNode) + ?.GetNodeOfType(); + if (selectedTransitionGroup == null || !Equals(selectedTransitionGroup.DocNode.LabelType, + calibrationPoint.LabelType)) + { + var selectedPeptide = (_skylineWindow.SequenceTree.SelectedNode as SrmTreeNode) + ?.GetNodeOfType(); + if (selectedPeptide != null) + { + var transitionGroupToSelect = selectedPeptide.Nodes.OfType() + .FirstOrDefault(node => Equals(calibrationPoint.LabelType, node.DocNode.LabelType)); + if (transitionGroupToSelect != null) + { + _skylineWindow.SequenceTree.SelectedPath = transitionGroupToSelect.Path; + } + } + } + } } public static string QualifyCurveNameWithSurrogate(string curveName, PeptideDocNode surrogateMolecule) diff --git a/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationForm.resx b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationForm.resx index 4ffb8b7eee..bca01bde64 100644 --- a/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationForm.resx +++ b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationForm.resx @@ -117,92 +117,35 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + - + Fill - + 0, 0 - + 759, 390 - - 0 - - - zedGraphControl - - - ZedGraph.ZedGraphControl, ZedGraph, Version=5.1.6992.18926, Culture=neutral, PublicKeyToken=8b2485f42e5e887d - - - $this - - + 1 - - 17, 17 - - - 198, 22 - - - Log X Axis - - - 198, 22 - - - Log Y Axis - - - 198, 22 - - - Show Calibration Curve - - - 198, 22 - - - Show Sample Types - - - 198, 22 - - - Single Batch - - - 198, 22 - - - Show Legend + + calibrationGraphControl1 - - 198, 22 + + pwiz.Skyline.Controls.Graphs.Calibration.CalibrationGraphControl, Skyline-daily, Version=22.2.1.448, Culture=neutral, PublicKeyToken=null - - Show Selection - - - 198, 22 - - - Show Figures of Merit - - - 199, 202 - - - contextMenuStrip1 + + $this - - System.Windows.Forms.ContextMenuStrip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 0 True @@ -222,58 +165,16 @@ Calibration Curve - - logXContextMenuItem - - - System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - logYAxisContextMenuItem - - - System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - showToolStripMenuItem - - - System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - showSampleTypesContextMenuItem - - - System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - singleBatchContextMenuItem - - - System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - showLegendContextMenuItem - - - System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - showSelectionContextMenuItem - - - System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - showFiguresOfMeritContextMenuItem + + ModeUIExtender - - System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + pwiz.Skyline.Util.Helpers+ModeUIExtender, Skyline-daily, Version=22.2.1.448, Culture=neutral, PublicKeyToken=null CalibrationForm - pwiz.Skyline.Util.DockableFormEx, Skyline, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + pwiz.Skyline.Util.DockableFormEx, Skyline-daily, Version=22.2.1.448, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationGraphControl.Designer.cs b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationGraphControl.Designer.cs new file mode 100644 index 0000000000..f130c15be0 --- /dev/null +++ b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationGraphControl.Designer.cs @@ -0,0 +1,169 @@ +namespace pwiz.Skyline.Controls.Graphs.Calibration +{ + partial class CalibrationGraphControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.zedGraphControl = new ZedGraph.ZedGraphControl(); + this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); + this.logXContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.logYAxisContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.showSampleTypesContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.singleBatchContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.showLegendContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.showSelectionContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.showFiguresOfMeritContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.showBootstrapCurvesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.moreDisplayOptionsContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.contextMenuStrip1.SuspendLayout(); + this.SuspendLayout(); + // + // zedGraphControl + // + this.zedGraphControl.Dock = System.Windows.Forms.DockStyle.Fill; + this.zedGraphControl.EditButtons = System.Windows.Forms.MouseButtons.Left; + this.zedGraphControl.EditModifierKeys = System.Windows.Forms.Keys.None; + this.zedGraphControl.IsShowCopyMessage = false; + this.zedGraphControl.IsZoomOnMouseCenter = true; + this.zedGraphControl.Location = new System.Drawing.Point(0, 0); + this.zedGraphControl.Name = "zedGraphControl"; + this.zedGraphControl.ScrollGrace = 0D; + this.zedGraphControl.ScrollMaxX = 0D; + this.zedGraphControl.ScrollMaxY = 0D; + this.zedGraphControl.ScrollMaxY2 = 0D; + this.zedGraphControl.ScrollMinX = 0D; + this.zedGraphControl.ScrollMinY = 0D; + this.zedGraphControl.ScrollMinY2 = 0D; + this.zedGraphControl.Size = new System.Drawing.Size(150, 150); + this.zedGraphControl.TabIndex = 1; + this.zedGraphControl.MouseDownEvent += new ZedGraph.ZedGraphControl.ZedMouseEventHandler(this.zedGraphControl_MouseDownEvent); + this.zedGraphControl.MouseMoveEvent += new ZedGraph.ZedGraphControl.ZedMouseEventHandler(this.zedGraphControl_MouseMoveEvent); + // + // contextMenuStrip1 + // + this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.logXContextMenuItem, + this.logYAxisContextMenuItem, + this.showSampleTypesContextMenuItem, + this.singleBatchContextMenuItem, + this.showLegendContextMenuItem, + this.showSelectionContextMenuItem, + this.showFiguresOfMeritContextMenuItem, + this.showBootstrapCurvesToolStripMenuItem, + this.moreDisplayOptionsContextMenuItem}); + this.contextMenuStrip1.Name = "contextMenuStrip1"; + this.contextMenuStrip1.Size = new System.Drawing.Size(198, 224); + // + // logXContextMenuItem + // + this.logXContextMenuItem.Name = "logXContextMenuItem"; + this.logXContextMenuItem.Size = new System.Drawing.Size(198, 22); + this.logXContextMenuItem.Text = "Log X Axis"; + this.logXContextMenuItem.Click += new System.EventHandler(this.logXAxisContextMenuItem_Click); + // + // logYAxisContextMenuItem + // + this.logYAxisContextMenuItem.Name = "logYAxisContextMenuItem"; + this.logYAxisContextMenuItem.Size = new System.Drawing.Size(198, 22); + this.logYAxisContextMenuItem.Text = "Log Y Axis"; + this.logYAxisContextMenuItem.Click += new System.EventHandler(this.logYAxisContextMenuItem_Click); + // + // showSampleTypesContextMenuItem + // + this.showSampleTypesContextMenuItem.Name = "showSampleTypesContextMenuItem"; + this.showSampleTypesContextMenuItem.Size = new System.Drawing.Size(198, 22); + this.showSampleTypesContextMenuItem.Text = "Show Sample Types"; + // + // singleBatchContextMenuItem + // + this.singleBatchContextMenuItem.Name = "singleBatchContextMenuItem"; + this.singleBatchContextMenuItem.Size = new System.Drawing.Size(198, 22); + this.singleBatchContextMenuItem.Text = "Single Batch"; + this.singleBatchContextMenuItem.Click += new System.EventHandler(this.singleBatchContextMenuItem_Click); + // + // showLegendContextMenuItem + // + this.showLegendContextMenuItem.Name = "showLegendContextMenuItem"; + this.showLegendContextMenuItem.Size = new System.Drawing.Size(198, 22); + this.showLegendContextMenuItem.Text = "Show Legend"; + this.showLegendContextMenuItem.Click += new System.EventHandler(this.showLegendContextMenuItem_Click); + // + // showSelectionContextMenuItem + // + this.showSelectionContextMenuItem.Name = "showSelectionContextMenuItem"; + this.showSelectionContextMenuItem.Size = new System.Drawing.Size(198, 22); + this.showSelectionContextMenuItem.Text = "Show Selection"; + this.showSelectionContextMenuItem.Click += new System.EventHandler(this.showSelectionContextMenuItem_Click); + // + // showFiguresOfMeritContextMenuItem + // + this.showFiguresOfMeritContextMenuItem.Name = "showFiguresOfMeritContextMenuItem"; + this.showFiguresOfMeritContextMenuItem.Size = new System.Drawing.Size(198, 22); + this.showFiguresOfMeritContextMenuItem.Text = "Show Figures of Merit"; + this.showFiguresOfMeritContextMenuItem.Click += new System.EventHandler(this.showFiguresOfMeritContextMenuItem_Click); + // + // showBootstrapCurvesToolStripMenuItem + // + this.showBootstrapCurvesToolStripMenuItem.Name = "showBootstrapCurvesToolStripMenuItem"; + this.showBootstrapCurvesToolStripMenuItem.Size = new System.Drawing.Size(198, 22); + this.showBootstrapCurvesToolStripMenuItem.Text = "Show Bootstrap Curves"; + this.showBootstrapCurvesToolStripMenuItem.Click += new System.EventHandler(this.showBootstrapCurvesToolStripMenuItem_Click); + // + // moreDisplayOptionsContextMenuItem + // + this.moreDisplayOptionsContextMenuItem.Name = "moreDisplayOptionsContextMenuItem"; + this.moreDisplayOptionsContextMenuItem.Size = new System.Drawing.Size(198, 22); + this.moreDisplayOptionsContextMenuItem.Text = "More Display Options..."; + this.moreDisplayOptionsContextMenuItem.Click += new System.EventHandler(this.moreDisplayOptionsContextMenuItem_Click); + // + // CalibrationGraphControl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.zedGraphControl); + this.Name = "CalibrationGraphControl"; + this.contextMenuStrip1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private ZedGraph.ZedGraphControl zedGraphControl; + private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; + private System.Windows.Forms.ToolStripMenuItem logXContextMenuItem; + private System.Windows.Forms.ToolStripMenuItem logYAxisContextMenuItem; + private System.Windows.Forms.ToolStripMenuItem showSampleTypesContextMenuItem; + private System.Windows.Forms.ToolStripMenuItem singleBatchContextMenuItem; + private System.Windows.Forms.ToolStripMenuItem showLegendContextMenuItem; + private System.Windows.Forms.ToolStripMenuItem showSelectionContextMenuItem; + private System.Windows.Forms.ToolStripMenuItem showFiguresOfMeritContextMenuItem; + private System.Windows.Forms.ToolStripMenuItem showBootstrapCurvesToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem moreDisplayOptionsContextMenuItem; + } +} diff --git a/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationGraphControl.cs b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationGraphControl.cs new file mode 100644 index 0000000000..d586d28c77 --- /dev/null +++ b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationGraphControl.cs @@ -0,0 +1,888 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Linq; +using System.Windows.Forms; +using pwiz.Common.Collections; +using pwiz.Common.SystemUtil; +using pwiz.Skyline.Controls.SeqNode; +using pwiz.Skyline.Model; +using pwiz.Skyline.Model.AuditLog; +using pwiz.Skyline.Model.Databinding.Entities; +using pwiz.Skyline.Model.DocSettings.AbsoluteQuantification; +using pwiz.Skyline.Model.GroupComparison; +using pwiz.Skyline.Model.Results; +using pwiz.Skyline.Util; +using pwiz.Skyline.Util.Extensions; +using ZedGraph; +using SampleType = pwiz.Skyline.Model.DocSettings.AbsoluteQuantification.SampleType; + +namespace pwiz.Skyline.Controls.Graphs.Calibration +{ + public partial class CalibrationGraphControl : UserControl + { + private CurveList _scatterPlots; + public CalibrationGraphControl() + { + InitializeComponent(); + zedGraphControl.MasterPane.Border.IsVisible = false; + zedGraphControl.GraphPane.Border.IsVisible = false; + zedGraphControl.GraphPane.Chart.Border.IsVisible = false; + zedGraphControl.GraphPane.XAxis.Title.Text = QuantificationStrings.Analyte_Concentration; + zedGraphControl.GraphPane.YAxis.Title.Text = QuantificationStrings.CalibrationCurveFitter_GetYAxisTitle_Peak_Area; + zedGraphControl.GraphPane.Legend.IsVisible = false; + zedGraphControl.GraphPane.Title.Text = null; + zedGraphControl.GraphPane.Title.FontSpec.Size = 12f; + zedGraphControl.GraphPane.IsFontsScaled = false; + zedGraphControl.GraphPane.XAxis.MajorTic.IsOpposite = false; + zedGraphControl.GraphPane.XAxis.MinorTic.IsOpposite = false; + zedGraphControl.GraphPane.YAxis.MajorTic.IsOpposite = false; + zedGraphControl.GraphPane.YAxis.MinorTic.IsOpposite = false; + zedGraphControl.IsZoomOnMouseCenter = true; + zedGraphControl.ContextMenuBuilder += zedGraphControl_ContextMenuBuilder; + } + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [EditorBrowsable(EditorBrowsableState.Never)] + public SkylineWindow SkylineWindow { get; set; } + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [EditorBrowsable(EditorBrowsableState.Never)] + public Helpers.ModeUIAwareFormHelper ModeUIAwareFormHelper + { + get; + set; + } + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [EditorBrowsable(EditorBrowsableState.Never)] + public CalibrationCurveOptions Options + { + get + { + return Properties.Settings.Default.CalibrationCurveOptions; + } + set + { + Properties.Settings.Default.CalibrationCurveOptions = value; + } + } + + protected override void OnHandleCreated(EventArgs e) + { + base.OnHandleCreated(e); + Properties.Settings.Default.PropertyChanged += Settings_OnPropertyChanged; + } + + private void Settings_OnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (DisplaySettings != null) + { + Update(DisplaySettings); + } + } + + protected override void OnHandleDestroyed(EventArgs e) + { + Properties.Settings.Default.PropertyChanged -= Settings_OnPropertyChanged; + base.OnHandleDestroyed(e); + } + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [EditorBrowsable(EditorBrowsableState.Never)] + public Settings DisplaySettings { get; private set; } + + public void Update(Settings displaySettings) + { + DoUpdate(displaySettings); + GraphHelper.FormatFontSize(zedGraphControl.GraphPane, Options.FontSize); + zedGraphControl.AxisChange(); + zedGraphControl.Invalidate(); + } + + public void Clear() + { + zedGraphControl.GraphPane.GraphObjList.Clear(); + zedGraphControl.GraphPane.CurveList.Clear(); + _scatterPlots = null; + CalibrationCurve = null; + FiguresOfMerit = FiguresOfMerit.EMPTY; + } + + private void DoUpdate(Settings displaySettings) + { + Clear(); + DisplaySettings = displaySettings; + var options = Options; + zedGraphControl.GraphPane.YAxis.Type = options.LogYAxis ? AxisType.Log : AxisType.Linear; + zedGraphControl.GraphPane.XAxis.Type = options.LogXAxis ? AxisType.Log : AxisType.Linear; + bool logPlot = options.LogXAxis || options.LogYAxis; + zedGraphControl.GraphPane.Legend.IsVisible = options.ShowLegend; + SrmDocument document = displaySettings.Document; + if (document == null) + { + zedGraphControl.GraphPane.Title.Text = displaySettings.GraphTitle; + return; + } + + if (!document.Settings.HasResults) + { + zedGraphControl.GraphPane.Title.Text = + TextUtil.LineSeparate(displaySettings.GraphTitle, + QuantificationStrings.CalibrationForm_DisplayCalibrationCurve_No_results_available); + return; + } + + CalibrationCurveFitter curveFitter = displaySettings.CalibrationCurveFitter; + var mainPeptideQuantifier = curveFitter.PeptideQuantifier; + if (mainPeptideQuantifier.QuantificationSettings.RegressionFit == RegressionFit.NONE) + { + var peptide = mainPeptideQuantifier.PeptideDocNode; + if (!(peptide.NormalizationMethod is NormalizationMethod.RatioToLabel)) + { + zedGraphControl.GraphPane.Title.Text = + TextUtil.LineSeparate(displaySettings.GraphTitle, + ModeUIAwareStringFormat(QuantificationStrings + .CalibrationForm_DisplayCalibrationCurve_Use_the_Quantification_tab_on_the_Peptide_Settings_dialog_to_control_the_conversion_of_peak_areas_to_concentrations_)); + } + else + { + if (!peptide.InternalStandardConcentration.HasValue) + { + zedGraphControl.GraphPane.Title.Text = + TextUtil.LineSeparate(displaySettings.GraphTitle, + ModeUIAwareStringFormat( + QuantificationStrings + .CalibrationForm_DisplayCalibrationCurve_To_convert_peak_area_ratios_to_concentrations__specify_the_internal_standard_concentration_for__0__, + peptide)); + } + else + { + zedGraphControl.GraphPane.Title.Text = displaySettings.GraphTitle; + } + } + } + else + { + if (curveFitter.GetStandardConcentrations().Any()) + { + zedGraphControl.GraphPane.Title.Text = displaySettings.GraphTitle; + } + else + { + zedGraphControl.GraphPane.Title.Text = TextUtil.LineSeparate(displaySettings.GraphTitle, + QuantificationStrings + .CalibrationForm_DisplayCalibrationCurve_To_fit_a_calibration_curve__set_the_Sample_Type_of_some_replicates_to_Standard__and_specify_their_concentration_); + } + } + + zedGraphControl.GraphPane.XAxis.Title.Text = curveFitter.GetXAxisTitle(); + zedGraphControl.GraphPane.YAxis.Title.Text = curveFitter.GetYAxisTitle(); + curveFitter.GetCalibrationCurveAndMetrics(out CalibrationCurve calibrationCurve, + out CalibrationCurveMetrics calibrationCurveRow); + CalibrationCurve = calibrationCurve; + CalibrationCurveMetrics = calibrationCurveRow; + + var bootstrapCurves = new List>(); + FiguresOfMerit = curveFitter.GetFiguresOfMerit(CalibrationCurve, bootstrapCurves); + double minX = double.MaxValue, maxX = double.MinValue; + double minY = double.MaxValue, maxY = double.MinValue; + _scatterPlots = new CurveList(); + + IEnumerable sampleTypes = SampleType.ListSampleTypes() + .Where(options.DisplaySampleType); + foreach (var sampleType in sampleTypes) + { + var samplePeptideQuantifier = curveFitter.GetPeptideQuantifier(sampleType); + PointPairList pointPairList = new PointPairList(); + PointPairList pointPairListExcluded = new PointPairList(); + foreach (var standardIdentifier in curveFitter.EnumerateCalibrationPoints()) + { + if (!Equals(sampleType, curveFitter.GetSampleType(standardIdentifier))) + { + continue; + } + + AnnotatedDouble normalizedArea = curveFitter.GetAnnotatedNormalizedPeakArea(standardIdentifier); + double? xCalculated = curveFitter.GetCalculatedXValue(CalibrationCurve, standardIdentifier); + double? x = curveFitter.GetSpecifiedXValue(standardIdentifier) + ?? xCalculated; + if (normalizedArea != null && x.HasValue) + { + PointPair point = new PointPair(x.Value, normalizedArea.Raw) { Tag = standardIdentifier }; + bool included = normalizedArea.Strict != null; + if (included && sampleType.AllowExclude) + { + if (null == standardIdentifier.LabelType && + samplePeptideQuantifier.PeptideDocNode.IsExcludeFromCalibration(standardIdentifier + .ReplicateIndex)) + { + included = false; + } + } + + if (included) + { + pointPairList.Add(point); + } + else + { + pointPairListExcluded.Add(point); + } + + if (!IsNumber(x) || !IsNumber(normalizedArea.Raw)) + { + continue; + } + + if (!logPlot || x.Value > 0) + { + minX = Math.Min(minX, x.Value); + } + + if (!logPlot || normalizedArea.Raw > 0) + { + minY = Math.Min(minY, normalizedArea.Raw); + } + + maxX = Math.Max(maxX, x.Value); + if (IsNumber(xCalculated)) + { + maxX = Math.Max(maxX, xCalculated.Value); + if (!logPlot || xCalculated.Value > 0) + { + minX = Math.Min(minX, xCalculated.Value); + } + } + + maxY = Math.Max(maxY, normalizedArea.Raw); + } + } + + if (pointPairList.Any()) + { + var lineItem = zedGraphControl.GraphPane.AddCurve(sampleType.ToString(), pointPairList, + sampleType.Color, sampleType.SymbolType); + lineItem.Line.IsVisible = false; + lineItem.Symbol.Fill = new Fill(sampleType.Color); + _scatterPlots.Add(lineItem); + } + + if (pointPairListExcluded.Any()) + { + var symbolType = sampleType.SymbolType; + if (symbolType == SymbolType.XCross) + { + // Excluded or invalid points get drawn without being filled. + // The XCross looks the same regardless of whether it is filled, so we switch to a circle + // when it has a problem + symbolType = SymbolType.Circle; + } + + string curveLabel = pointPairList.Any() ? null : sampleType.ToString(); + var lineItem = zedGraphControl.GraphPane.AddCurve(curveLabel, pointPairListExcluded, + sampleType.Color, symbolType); + lineItem.Line.IsVisible = false; + _scatterPlots.Add(lineItem); + } + } + + List labelLines = new List(); + RegressionFit regressionFit = document.Settings.PeptideSettings.Quantification.RegressionFit; + if (regressionFit != RegressionFit.NONE) + { + if (minX <= maxX) + { + int interpolatedLinePointCount = 100; + if (!logPlot && regressionFit != RegressionFit.LINEAR_IN_LOG_SPACE) + { + if (regressionFit == RegressionFit.LINEAR_THROUGH_ZERO) + { + minX = Math.Min(0, minX); + } + + if (regressionFit != RegressionFit.QUADRATIC) + { + interpolatedLinePointCount = 2; + } + } + + double[] xValues; + if (CalibrationCurve is CalibrationCurve.Bilinear bilinearCalibrationCurve) + { + xValues = new[] { minX, bilinearCalibrationCurve.TurningPoint, maxX }; + } + else + { + xValues = new[] { minX, maxX }; + } + + Array.Sort(xValues); + LineItem interpolatedLine = CreateInterpolatedLine(CalibrationCurve, xValues, + interpolatedLinePointCount, logPlot); + if (null != interpolatedLine) + { + zedGraphControl.GraphPane.CurveList.Add(interpolatedLine); + } + + maxY = Math.Max(maxY, GetMaxY(interpolatedLine.Points)); + } + + labelLines.Add(CalibrationCurveMetrics.ToString()); + + if (CalibrationCurveMetrics.RSquared.HasValue) + { + labelLines.Add(CalibrationCurveMetrics.RSquaredDisplayText(CalibrationCurveMetrics.RSquared.Value)); + } + + if (!Equals(curveFitter.QuantificationSettings.RegressionWeighting, RegressionWeighting.NONE)) + { + labelLines.Add(string.Format(@"{0}: {1}", + QuantificationStrings.Weighting, curveFitter.QuantificationSettings.RegressionWeighting)); + } + + if (options.ShowFiguresOfMerit) + { + string strFiguresOfMerit = FiguresOfMerit.ToString(); + if (!string.IsNullOrEmpty(strFiguresOfMerit)) + { + labelLines.Add(strFiguresOfMerit); + } + } + } + + CalibrationPoint? selectionIdentifier = null; + if (options.ShowSelection && displaySettings.SelectedResultsIndex.HasValue) + { + if (curveFitter.IsotopologResponseCurve) + { + var labelType = displaySettings.SelectedLabelType; + if (labelType != null) + { + selectionIdentifier = + new CalibrationPoint(displaySettings.SelectedResultsIndex.Value, + labelType); + } + } + else + { + selectionIdentifier = + new CalibrationPoint(displaySettings.SelectedResultsIndex.Value, null); + } + } + + if (selectionIdentifier.HasValue) + { + AnnotatedDouble ySelected = curveFitter.GetAnnotatedNormalizedPeakArea(selectionIdentifier.Value); + if (IsNumber(ySelected?.Raw)) + { + if (IsNumber(ySelected?.Raw)) + { + double? xSelected = CalibrationCurve.GetX(ySelected.Raw); + var selectedLineColor = Color.FromArgb(128, GraphSummary.ColorSelected); + const float selectedLineWidth = 2; + double? xSpecified = curveFitter.GetSpecifiedXValue(selectionIdentifier.Value); + if (IsNumber(xSelected)) + { + ArrowObj arrow = new ArrowObj(xSelected.Value, ySelected.Raw, xSelected.Value, + ySelected.Raw) + { Line = { Color = GraphSummary.ColorSelected } }; + zedGraphControl.GraphPane.GraphObjList.Insert(0, arrow); + var verticalLine = new LineObj(xSelected.Value, ySelected.Raw, xSelected.Value, + options.LogYAxis ? minY / 10 : 0) + { + Line = { Color = selectedLineColor, Width = selectedLineWidth }, + Location = { CoordinateFrame = CoordType.AxisXYScale }, + ZOrder = ZOrder.E_BehindCurves, + IsClippedToChartRect = true + }; + zedGraphControl.GraphPane.GraphObjList.Add(verticalLine); + if (IsNumber(xSpecified)) + { + var horizontalLine = new LineObj(xSpecified.Value, ySelected.Raw, xSelected.Value, + ySelected.Raw) + { + Line = { Color = selectedLineColor, Width = selectedLineWidth }, + Location = { CoordinateFrame = CoordType.AxisXYScale }, + ZOrder = ZOrder.E_BehindCurves, + IsClippedToChartRect = true + }; + zedGraphControl.GraphPane.GraphObjList.Add(horizontalLine); + } + } + else + { + // We were not able to map the observed intensity back to the calibration curve, but we still want to + // indicate where the currently selected point is. + if (IsNumber(xSpecified)) + { + // If the point has a specified concentration, then use that. + ArrowObj arrow = new ArrowObj(xSpecified.Value, ySelected.Raw, xSpecified.Value, + ySelected.Raw) + { Line = { Color = GraphSummary.ColorSelected } }; + zedGraphControl.GraphPane.GraphObjList.Insert(0, arrow); + } + else + { + // Otherwise, draw a horizontal line at the appropriate y-value. + var horizontalLine = new LineObj(minX, ySelected.Raw, maxX, ySelected.Raw) + { + Line = { Color = selectedLineColor, Width = selectedLineWidth }, + Location = { CoordinateFrame = CoordType.AxisXYScale }, + IsClippedToChartRect = true, + }; + zedGraphControl.GraphPane.GraphObjList.Add(horizontalLine); + } + } + } + + QuantificationResult quantificationResult = null; + AnnotatedDouble calculatedConcentration; + if (curveFitter.IsotopologResponseCurve) + { + calculatedConcentration = + AnnotatedDouble.Of(curveFitter.GetCalculatedConcentration(CalibrationCurve, + selectionIdentifier.Value)); + } + else + { + quantificationResult = + curveFitter.GetPeptideQuantificationResult(selectionIdentifier.Value.ReplicateIndex); + calculatedConcentration = quantificationResult?.CalculatedConcentration; + } + + if (calculatedConcentration != null) + { + labelLines.Add(string.Format(@"{0} = {1}", + QuantificationStrings.Calculated_Concentration, + QuantificationResult.FormatCalculatedConcentration(calculatedConcentration, + curveFitter.QuantificationSettings.Units))); + } + else if (ySelected?.Message != null) + { + labelLines.Add(ySelected.Message); + } + } + + List bootstrapCurveItems = new List(); + if (options.ShowBootstrapCurves) + { + var color = Color.FromArgb(40, Color.Teal); + foreach (var points in bootstrapCurves) + { + // Only assign a title to the first curve so that only one item appears in the Legend + string title = bootstrapCurveItems.Count == 0 + ? QuantificationStrings.CalibrationGraphControl_DoUpdate_Bootstrap_Curve + : null; + + var curve = new LineItem(title, new PointPairList(points), color, + SymbolType.None, options.LineWidth); + maxY = Math.Max(maxY, GetMaxY(curve.Points)); + bootstrapCurveItems.Add(curve); + } + } + + if (options.ShowFiguresOfMerit) + { + if (IsNumber(FiguresOfMerit.LimitOfDetection)) + { + var lod = FiguresOfMerit.LimitOfDetection.Value; + var points = new PointPairList(new[] { lod, lod }, new[] { minY, maxY }); + var lodLine = + new LineItem(QuantificationStrings.CalibrationGraphControl_DoUpdate_Limit_of_Detection, + points, Color.Black, SymbolType.None) + { + Line = { Style = DashStyle.Dot, Width = options.LineWidth } + }; + zedGraphControl.GraphPane.CurveList.Add(lodLine); + } + + if (IsNumber(FiguresOfMerit.LimitOfQuantification)) + { + var loq = FiguresOfMerit.LimitOfQuantification.Value; + var points = new PointPairList(new[] { loq, loq }, new[] { minY, maxY }); + var loqLine = + new LineItem( + QuantificationStrings.CalibrationGraphControl_DoUpdate_Lower_Limit_of_Quantification, + points, Color.Black, SymbolType.None) + { + Line = { Style = DashStyle.Dash, Width = options.LineWidth } + }; + zedGraphControl.GraphPane.CurveList.Add(loqLine); + } + } + + zedGraphControl.GraphPane.CurveList.AddRange(bootstrapCurveItems); + if (labelLines.Any()) + { + TextObj text = new TextObj(TextUtil.LineSeparate(labelLines), .01, 0, + CoordType.ChartFraction, AlignH.Left, AlignV.Top) + { + IsClippedToChartRect = true, + ZOrder = ZOrder.E_BehindCurves, + FontSpec = GraphSummary.CreateFontSpec(Color.Black), + }; + text.FontSpec.Size = options.FontSize; + zedGraphControl.GraphPane.GraphObjList.Add(text); + } + + } + } + + public CalibrationCurve CalibrationCurve { get; private set; } + public CalibrationCurveMetrics CalibrationCurveMetrics { get; private set; } + public FiguresOfMerit FiguresOfMerit { get; private set; } + + public class Settings : Immutable + { + public static readonly Settings EMPTY = new Settings(null, null); + public Settings(SrmDocument document, CalibrationCurveFitter calibrationCurveFitter) + { + Document = document; + CalibrationCurveFitter = calibrationCurveFitter; + } + + public SrmDocument Document { get; } + public CalibrationCurveFitter CalibrationCurveFitter { get; } + + public int? SelectedResultsIndex { get; private set; } + + public Settings ChangeSelectedResultsIndex(int? value) + { + return ChangeProp(ImClone(this), im => im.SelectedResultsIndex = value); + } + + public IsotopeLabelType SelectedLabelType { get; private set; } + + public Settings ChangeSelectedLabelType(IsotopeLabelType value) + { + return ChangeProp(ImClone(this), im => im.SelectedLabelType = value); + } + + public string GraphTitle { get; private set; } + + public Settings ChangeGraphTitle(string value) + { + return ChangeProp(ImClone(this), im => im.GraphTitle = value ?? string.Empty); + } + } + private LineItem CreateInterpolatedLine(CalibrationCurve calibrationCurve, double[] xValues, int pointCount, bool logPlot) + { + PointPairList pointPairList = new PointPairList(); + for (int iRange = 0; iRange < xValues.Length - 1; iRange++) + { + double minX = xValues[iRange]; + double maxX = xValues[iRange + 1]; + for (int i = 0; i < pointCount; i++) + { + double x; + if (logPlot) + { + x = Math.Exp((Math.Log(minX) * (pointCount - 1 - i) + Math.Log(maxX) * i) / (pointCount - 1)); + } + else + { + x = (minX * (pointCount - 1 - i) + maxX * i) / (pointCount - 1); + } + double y = calibrationCurve.GetY(x); + pointPairList.Add(x, y); + } + } + if (!pointPairList.Any()) + { + return null; + } + return new LineItem(QuantificationStrings.Calibration_Curve, pointPairList, Color.Gray, SymbolType.None, Options.LineWidth); + } + + + public string ModeUIAwareStringFormat(string format, params object[] args) + { + return ModeUIAwareFormHelper?.Format(format, args) ?? string.Format(format, args); + } + + public static bool IsNumber(double? value) + { + return CalibrationForm.IsNumber(value); + } + + public void DisplayError(string message) + { + Update(Settings.EMPTY.ChangeGraphTitle(message)); + } + public CalibrationPoint? ReplicateIndexFromPoint(Point pt) + { + if (null == _scatterPlots) + { + return null; + } + PointF ptF = new PointF(pt.X, pt.Y); + CurveItem nearestCurve; + int iNeareast; + if (!zedGraphControl.GraphPane.FindNearestPoint(ptF, _scatterPlots, out nearestCurve, out iNeareast)) + { + return null; + } + PointPair nearPoint = nearestCurve.Points[iNeareast]; + PointF nearPointScreen = zedGraphControl.GraphPane.GeneralTransform(nearPoint.X, nearPoint.Y, CoordType.AxisXYScale); + if (Math.Abs(nearPointScreen.X - pt.X) > 5 || Math.Abs(nearPointScreen.Y - pt.Y) > 5) + { + return null; + } + return nearestCurve.Points[iNeareast].Tag as CalibrationPoint?; + } + + public ZedGraphControl ZedGraphControl + { + get + { + return zedGraphControl; + } + } + + private bool zedGraphControl_MouseMoveEvent(ZedGraphControl sender, MouseEventArgs e) + { + var replicateIndex = ReplicateIndexFromPoint(e.Location); + if (replicateIndex.HasValue) + { + zedGraphControl.Cursor = Cursors.Hand; + return true; + } + return false; + } + + private bool zedGraphControl_MouseDownEvent(ZedGraphControl sender, MouseEventArgs e) + { + if (e.Button != MouseButtons.Left) + { + return false; + } + CalibrationPoint? replicateIndex = ReplicateIndexFromPoint(e.Location); + if (replicateIndex.HasValue) + { + PointClicked?.Invoke(replicateIndex.Value); + return true; + } + return false; + } + + public event Action PointClicked; + private void zedGraphControl_ContextMenuBuilder(ZedGraphControl sender, ContextMenuStrip menuStrip, Point mousePt, ZedGraphControl.ContextMenuObjectState objState) + { + if (DisplaySettings == null) + { + return; + } + var calibrationCurveOptions = Options; + singleBatchContextMenuItem.Checked = calibrationCurveOptions.SingleBatch; + if (IsEnableIsotopologResponseCurve()) + { + singleBatchContextMenuItem.Visible = true; + } + else + { + singleBatchContextMenuItem.Visible = CalibrationCurveFitter.AnyBatchNames(DisplaySettings.Document.Settings); + } + var replicateIndexFromPoint = ReplicateIndexFromPoint(mousePt); + if (replicateIndexFromPoint.HasValue && null == replicateIndexFromPoint.Value.LabelType) + { + ToolStripMenuItem excludeStandardMenuItem + = MakeExcludeStandardMenuItem(replicateIndexFromPoint.Value.ReplicateIndex); + if (excludeStandardMenuItem != null) + { + // If the user clicks on an external standard point, then show + // them a menu with only the "Exclude Standard" item. + menuStrip.Items.Clear(); + menuStrip.Items.Add(excludeStandardMenuItem); + return; + } + } + + showSampleTypesContextMenuItem.DropDownItems.Clear(); + foreach (var sampleType in SampleType.ListSampleTypes()) + { + showSampleTypesContextMenuItem.DropDownItems.Add(MakeShowSampleTypeMenuItem(sampleType)); + } + logXContextMenuItem.Checked = Options.LogXAxis; + logYAxisContextMenuItem.Checked = Options.LogYAxis; + showLegendContextMenuItem.Checked = Options.ShowLegend; + showSelectionContextMenuItem.Checked = Options.ShowSelection; + showFiguresOfMeritContextMenuItem.Checked = Options.ShowFiguresOfMerit; + showBootstrapCurvesToolStripMenuItem.Checked = Options.ShowBootstrapCurves; + ZedGraphHelper.BuildContextMenu(sender, menuStrip, true); + if (!menuStrip.Items.Contains(logXContextMenuItem)) + { + int index = 0; + menuStrip.Items.Insert(index++, logXContextMenuItem); + menuStrip.Items.Insert(index++, logYAxisContextMenuItem); + menuStrip.Items.Insert(index++, showSampleTypesContextMenuItem); + menuStrip.Items.Insert(index++, singleBatchContextMenuItem); + menuStrip.Items.Insert(index++, showLegendContextMenuItem); + menuStrip.Items.Insert(index++, showSelectionContextMenuItem); + menuStrip.Items.Insert(index++, showFiguresOfMeritContextMenuItem); + menuStrip.Items.Insert(index++, showBootstrapCurvesToolStripMenuItem); + menuStrip.Items.Insert(index++, moreDisplayOptionsContextMenuItem); + menuStrip.Items.Insert(index++, new ToolStripSeparator()); + } + } + private bool IsEnableIsotopologResponseCurve() + { + return true == GetSelectedPeptide()?.PeptideDocNode.TransitionGroups + .Any(tg => tg.PrecursorConcentration.HasValue); + } + + private IdPeptideDocNode GetSelectedPeptide() + { + SequenceTree sequenceTree = SkylineWindow.SequenceTree; + PeptideTreeNode peptideTreeNode = sequenceTree?.GetNodeOfType(); + PeptideGroupTreeNode peptideGroupTreeNode = sequenceTree?.GetNodeOfType(); + if (peptideGroupTreeNode != null && peptideTreeNode != null) + { + return new IdPeptideDocNode(peptideGroupTreeNode.DocNode.PeptideGroup, peptideTreeNode.DocNode); + } + return null; + } + + + public ToolStripMenuItem MakeExcludeStandardMenuItem(int replicateIndex) + { + var measuredResults = SkylineWindow?.DocumentUI.Settings.MeasuredResults; + if (measuredResults == null) + { + return null; + } + ChromatogramSet chromatogramSet = null; + if (replicateIndex >= 0 && + replicateIndex < measuredResults.Chromatograms.Count) + { + chromatogramSet = measuredResults.Chromatograms[replicateIndex]; + } + if (chromatogramSet == null) + { + return null; + } + if (!chromatogramSet.SampleType.AllowExclude) + { + return null; + } + + var idPeptideDocNode = GetSelectedPeptide(); + if (idPeptideDocNode == null) + { + return null; + } + + var peptideDocNode = idPeptideDocNode.PeptideDocNode; + bool isExcluded = peptideDocNode.IsExcludeFromCalibration(replicateIndex); + var menuItemText = isExcluded ? QuantificationStrings.CalibrationForm_MakeExcludeStandardMenuItem_Include_Standard + : QuantificationStrings.CalibrationForm_MakeExcludeStandardMenuItem_Exclude_Standard; + var peptideIdPath = idPeptideDocNode.IdentityPath; + var menuItem = new ToolStripMenuItem(menuItemText, null, (sender, args) => + { + SkylineWindow.ModifyDocument(menuItemText, + doc => SetExcludeStandard(doc, peptideIdPath, replicateIndex, !isExcluded), docPair => + { + var msgType = isExcluded + ? MessageType.set_included_standard + : MessageType.set_excluded_standard; + return AuditLogEntry.CreateSingleMessageEntry(new MessageInfo(msgType, docPair.NewDocumentType, PeptideTreeNode.GetLabel(peptideDocNode, string.Empty), chromatogramSet.Name)); + }); + }); + return menuItem; + } + + + private ToolStripMenuItem MakeShowSampleTypeMenuItem(SampleType sampleType) + { + ToolStripMenuItem menuItem = new ToolStripMenuItem(sampleType.ToString()) + { + Checked = Options.DisplaySampleTypes.Contains(sampleType) + }; + menuItem.Click += (sender, args) => + { + Options = Options.SetDisplaySampleType(sampleType, !menuItem.Checked); + }; + return menuItem; + } + private SrmDocument SetExcludeStandard(SrmDocument document, IdentityPath peptideIdPath, int resultsIndex, bool exclude) + { + if (!document.Settings.HasResults) + { + return document; + } + var peptideDocNode = (PeptideDocNode)document.FindNode(peptideIdPath); + if (peptideDocNode == null) + { + return document; + } + if (resultsIndex < 0 || resultsIndex >= document.Settings.MeasuredResults.Chromatograms.Count) + { + return document; + } + bool wasExcluded = peptideDocNode.IsExcludeFromCalibration(resultsIndex); + return (SrmDocument)document.ReplaceChild(peptideIdPath.Parent, + peptideDocNode.ChangeExcludeFromCalibration(resultsIndex, !wasExcluded)); + } + + private void logXAxisContextMenuItem_Click(object sender, EventArgs e) + { + Options = Options.ChangeLogXAxis(!Options.LogXAxis); + } + + private void logYAxisContextMenuItem_Click(object sender, EventArgs e) + { + Options = Options.ChangeLogYAxis(!Options.LogYAxis); + } + private void showLegendContextMenuItem_Click(object sender, EventArgs e) + { + Options = Options.ChangeShowLegend(!Options.ShowLegend); + } + + private void showSelectionContextMenuItem_Click(object sender, EventArgs e) + { + Options = Options.ChangeShowSelection(!Options.ShowSelection); + } + + + private void showFiguresOfMeritContextMenuItem_Click(object sender, EventArgs e) + { + Options = Options.ChangeShowFiguresOfMerit(!Options.ShowFiguresOfMerit); + } + + private void singleBatchContextMenuItem_Click(object sender, EventArgs e) + { + Options = Options.ChangeSingleBatch(!Options.SingleBatch); + } + + private void showBootstrapCurvesToolStripMenuItem_Click(object sender, EventArgs e) + { + Options = Options.ChangeShowBootstrapCurves(!Options.ShowBootstrapCurves); + } + + private void moreDisplayOptionsContextMenuItem_Click(object sender, EventArgs e) + { + ShowCalibrationCurveOptions(); + } + + public void ShowCalibrationCurveOptions() + { + using (var dlg = new CalibrationCurveOptionsDlg()) + { + dlg.ShowDialog(this); + } + } + + private double GetMaxY(IPointList pointList) + { + var max = double.MinValue; + for (int i = 0; i < pointList.Count; i++) + { + max = Math.Max(max, pointList[i].Y); + } + + return max; + } + } +} diff --git a/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationGraphControl.resx b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationGraphControl.resx new file mode 100644 index 0000000000..ad537526f3 --- /dev/null +++ b/pwiz_tools/Skyline/Controls/Graphs/Calibration/CalibrationGraphControl.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeDocumentTransitionsForm.Designer.cs b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeDocumentTransitionsForm.Designer.cs new file mode 100644 index 0000000000..b62c2f416b --- /dev/null +++ b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeDocumentTransitionsForm.Designer.cs @@ -0,0 +1,110 @@ +namespace pwiz.Skyline.EditUI.OptimizeTransitions +{ + partial class OptimizeDocumentTransitionsForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.panel1 = new System.Windows.Forms.Panel(); + this.optimizeTransitionsSettingsControl1 = new pwiz.Skyline.EditUI.OptimizeTransitions.OptimizeTransitionsSettingsControl(); + this.btnApply = new System.Windows.Forms.Button(); + this.btnPreview = new System.Windows.Forms.Button(); + this.panel1.SuspendLayout(); + this.SuspendLayout(); + // + // databoundGridControl + // + this.databoundGridControl.Location = new System.Drawing.Point(0, 65); + this.databoundGridControl.Size = new System.Drawing.Size(800, 385); + // + // panel1 + // + this.panel1.Controls.Add(this.optimizeTransitionsSettingsControl1); + this.panel1.Controls.Add(this.btnApply); + this.panel1.Controls.Add(this.btnPreview); + this.panel1.Dock = System.Windows.Forms.DockStyle.Top; + this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(800, 65); + this.panel1.TabIndex = 1; + // + // optimizeTransitionsSettingsControl1 + // + this.optimizeTransitionsSettingsControl1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.optimizeTransitionsSettingsControl1.Location = new System.Drawing.Point(0, 0); + this.optimizeTransitionsSettingsControl1.MinNumberOfTransitions = 4; + this.optimizeTransitionsSettingsControl1.Name = "optimizeTransitionsSettingsControl1"; + this.optimizeTransitionsSettingsControl1.PreserveNonQuantitative = false; + this.optimizeTransitionsSettingsControl1.Size = new System.Drawing.Size(700, 73); + this.optimizeTransitionsSettingsControl1.SyncWithGlobalSettings = true; + this.optimizeTransitionsSettingsControl1.TabIndex = 4; + // + // btnApply + // + this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnApply.Location = new System.Drawing.Point(713, 33); + this.btnApply.Name = "btnApply"; + this.btnApply.Size = new System.Drawing.Size(75, 23); + this.btnApply.TabIndex = 3; + this.btnApply.Text = "Apply"; + this.btnApply.UseVisualStyleBackColor = true; + this.btnApply.Click += new System.EventHandler(this.btnApply_Click); + // + // btnPreview + // + this.btnPreview.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnPreview.Location = new System.Drawing.Point(713, 4); + this.btnPreview.Name = "btnPreview"; + this.btnPreview.Size = new System.Drawing.Size(75, 23); + this.btnPreview.TabIndex = 2; + this.btnPreview.Text = "Preview"; + this.btnPreview.UseVisualStyleBackColor = true; + this.btnPreview.Click += new System.EventHandler(this.btnPreview_Click); + // + // OptimizeDocumentTransitionsForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Controls.Add(this.panel1); + this.Name = "OptimizeDocumentTransitionsForm"; + this.Text = "OptimizeDocumentTransitionsForm"; + this.Controls.SetChildIndex(this.panel1, 0); + this.Controls.SetChildIndex(this.databoundGridControl, 0); + this.panel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Button btnApply; + private System.Windows.Forms.Button btnPreview; + private OptimizeTransitions.OptimizeTransitionsSettingsControl optimizeTransitionsSettingsControl1; + } +} \ No newline at end of file diff --git a/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeDocumentTransitionsForm.cs b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeDocumentTransitionsForm.cs new file mode 100644 index 0000000000..f387b904d9 --- /dev/null +++ b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeDocumentTransitionsForm.cs @@ -0,0 +1,428 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading; +using pwiz.Common.Collections; +using pwiz.Common.DataBinding; +using pwiz.Common.DataBinding.Attributes; +using pwiz.Common.SystemUtil; +using pwiz.Skyline.Controls; +using pwiz.Skyline.Controls.Databinding; +using pwiz.Skyline.Model; +using pwiz.Skyline.Model.AuditLog; +using pwiz.Skyline.Model.Databinding; +using pwiz.Skyline.Model.Databinding.Entities; +using pwiz.Skyline.Model.DocSettings; +using pwiz.Skyline.Model.DocSettings.AbsoluteQuantification; +using pwiz.Skyline.Model.GroupComparison; +using pwiz.Skyline.Model.Hibernate; +using pwiz.Skyline.Properties; +using pwiz.Skyline.Util; + +namespace pwiz.Skyline.EditUI.OptimizeTransitions +{ + public partial class OptimizeDocumentTransitionsForm : DataboundGridForm + { + private SkylineDataSchema _dataSchema; + private List _rowList = new List(); + private BindingList _bindingList; + private SrmDocument _originalDocument; + private SrmDocument _optimizedDocument; + + public OptimizeDocumentTransitionsForm(SkylineWindow skylineWindow) + { + InitializeComponent(); + SkylineWindow = skylineWindow; + _dataSchema = new SkylineWindowDataSchema(skylineWindow, SkylineDataSchema.GetLocalizedSchemaLocalizer()); + BindingListSource.QueryLock = _dataSchema.QueryLock; + _bindingList = new BindingList(_rowList); + UpdateViewContext(); + Text = TabText = OptimizeTransitionsResources.OptimizeDocumentTransitionsForm_OptimizeDocumentTransitionsForm_Optimize_Document_Transitions; + Icon = Resources.Skyline; + } + + public SkylineWindow SkylineWindow { get; } + + private IList MakeRowSourceInfos() + { + var rootColumn = ColumnDescriptor.RootColumn(_dataSchema, typeof(Row)); + return ImmutableList.Singleton(new RowSourceInfo(BindingListRowSource.Create(_bindingList), + new ViewInfo(rootColumn, GetDefaultViewSpec()))); + } + + private ViewSpec GetDefaultViewSpec() + { + var propertyPaths = new List + { + PropertyPath.Root.Property(nameof(Row.Molecule)), + }; + foreach (string fom in new[] {nameof(Row.OriginalFiguresOfMerit), nameof(Row.OptimizedFiguresOfMerit)}) + { + var ppFom = PropertyPath.Root.Property(fom); + if (OptimizeType == OptimizeType.LOD) + { + propertyPaths.Add(ppFom.Property(nameof(FiguresOfMerit.LimitOfDetection))); + } + else + { + propertyPaths.Add(ppFom.Property(nameof(FiguresOfMerit.LimitOfQuantification))); + } + } + + if (OptimizeType == OptimizeType.LOD) + { + propertyPaths.Add(PropertyPath.Root.Property(nameof(Row.LodImprovement))); + } + else + { + propertyPaths.Add(PropertyPath.Root.Property(nameof(Row.LloqImprovement))); + } + + return new ViewSpec().SetRowType(typeof(Row)).SetColumns(propertyPaths.Select(pp => new ColumnSpec(pp))); + } + + public class Row : SkylineObject + { + private SrmDocument _originalDocument; + private SrmDocument _optimizedDocument; + private Lazy _originalFiguresOfMerit; + private Lazy _optimizedFiguresOfMerit; + private BilinearTransitionOptimizer _bilinearTransitionOptimizer; + public Row(Model.Databinding.Entities.Peptide molecule, SrmDocument originalDocument, SrmDocument optimizedDocument, BilinearTransitionOptimizer bilinearTransitionOptimizer) + { + Molecule = molecule; + _originalDocument = originalDocument; + _optimizedDocument = optimizedDocument; + _bilinearTransitionOptimizer = bilinearTransitionOptimizer; + _originalFiguresOfMerit = new Lazy(GetOriginalFiguresOfMerit); + _optimizedFiguresOfMerit = new Lazy(GetOptimizedFiguresOfMerit); + if (optimizedDocument != null) + { + var peptideQuantifier = GetPeptideQuantifier(null, optimizedDocument, molecule.IdentityPath, bilinearTransitionOptimizer.OptimizeTransitionSettings); + int countQuantitative = 0; + int countNonQuantitative = 0; + foreach (var tg in peptideQuantifier.PeptideDocNode.TransitionGroups) + { + if (!peptideQuantifier.SkipTransitionGroup(tg)) + { + countQuantitative += tg.Transitions.Count(t => t.ExplicitQuantitative); + countNonQuantitative += tg.Transitions.Count(t => !t.ExplicitQuantitative); + } + } + + CountQuantitative = countQuantitative; + CountNonQuantitative = countNonQuantitative; + } + } + + protected override SkylineDataSchema GetDataSchema() + { + return Molecule.DataSchema; + } + + [InvariantDisplayName("Peptide", InUiMode = UiModes.PROTEOMIC)] + public Model.Databinding.Entities.Peptide Molecule { get; private set; } + + public int? CountQuantitative + { + get; internal set; + } + + public int? CountNonQuantitative + { + get; internal set; + } + + [ChildDisplayName("Original{0}")] + public FiguresOfMerit OriginalFiguresOfMerit + { + get { return _originalFiguresOfMerit.Value;} + } + + [ChildDisplayName("Optimized{0}")] + public FiguresOfMerit OptimizedFiguresOfMerit + { + get { return _optimizedFiguresOfMerit.Value; } + } + + private FiguresOfMerit GetOriginalFiguresOfMerit() + { + return GetFiguresOfMerit(_originalDocument, _bilinearTransitionOptimizer.OptimizeTransitionSettings); + } + + private FiguresOfMerit GetOptimizedFiguresOfMerit() + { + return GetFiguresOfMerit(_optimizedDocument, _bilinearTransitionOptimizer.OptimizeTransitionSettings); + } + + private FiguresOfMerit GetFiguresOfMerit(SrmDocument document, OptimizeTransitionSettings optimizeTransitionSettings) + { + if (document == null) + { + return null; + } + + var peptideQuantifier = GetPeptideQuantifier(null, document, Molecule.IdentityPath, optimizeTransitionSettings); + if (peptideQuantifier == null) + { + return null; + } + + var calibrationCurveFitter = + _bilinearTransitionOptimizer.OptimizeTransitionSettings.GetCalibrationCurveFitter(peptideQuantifier, + document.Settings); + return MakeFiguresOfMerit(_bilinearTransitionOptimizer.ComputeQuantLimits(calibrationCurveFitter), + document.Settings); + } + + [Format(Formats.CalibrationCurve)] + public double? LloqImprovement + { + get + { + return _originalFiguresOfMerit.Value?.LimitOfQuantification - + _optimizedFiguresOfMerit.Value?.LimitOfQuantification; + } + } + + [Format(Formats.CalibrationCurve)] + public double? LodImprovement + { + get + { + return _optimizedFiguresOfMerit.Value?.LimitOfDetection - + _optimizedFiguresOfMerit.Value?.LimitOfDetection; + } + } + } + + public SrmDocument OptimizeTransitions(ILongWaitBroker longWaitBroker, SrmDocument document, BilinearTransitionOptimizer bilinearTransitionOptimizer) + { + longWaitBroker.ProgressValue = 0; + var newMoleculeArrays = new List(); + var moleculeListMoleculesIndexes = new List>(); + for (int iMoleculeList = 0; iMoleculeList < document.Children.Count; iMoleculeList++) + { + var moleculeList = (PeptideGroupDocNode) document.Children[iMoleculeList]; + newMoleculeArrays.Add(new PeptideDocNode[moleculeList.Children.Count]); + for (int iMolecule = 0; iMolecule < moleculeList.Children.Count; iMolecule++) + { + var molecule = (PeptideDocNode) moleculeList.Children[iMolecule]; + if (molecule.IsDecoy || null != molecule.GlobalStandardType) + { + newMoleculeArrays[iMoleculeList][iMolecule] = molecule; + } + else + { + moleculeListMoleculesIndexes.Add(Tuple.Create(iMoleculeList, iMolecule)); + } + } + } + + if (moleculeListMoleculesIndexes.Count == 0) + { + return document; + } + + int processedMoleculeCount = 0; + var normalizationData = NormalizationData.GetNormalizationData(document, false, null); + + ParallelEx.ForEach(moleculeListMoleculesIndexes, moleculeListMoleculeIndex => + { + var moleculeList = (PeptideGroupDocNode) document.Children[moleculeListMoleculeIndex.Item1]; + var molecule = (PeptideDocNode) moleculeList.Children[moleculeListMoleculeIndex.Item2]; + longWaitBroker.CancellationToken.ThrowIfCancellationRequested(); + var peptideQuantifier = GetPeptideQuantifier(normalizationData, document, + new IdentityPath(moleculeList.PeptideGroup, molecule.Peptide), bilinearTransitionOptimizer.OptimizeTransitionSettings); + if (!bilinearTransitionOptimizer.OptimizeTransitionSettings.PreserveNonQuantitative) + { + peptideQuantifier = peptideQuantifier.MakeAllTransitionsQuantitative(); + } + + var calibrationCurveFitter = + bilinearTransitionOptimizer.OptimizeTransitionSettings.GetCalibrationCurveFitter(peptideQuantifier, + document.Settings); + var optimizedMolecule = + bilinearTransitionOptimizer.OptimizeTransitions(calibrationCurveFitter, null); + newMoleculeArrays[moleculeListMoleculeIndex.Item1][moleculeListMoleculeIndex.Item2] = optimizedMolecule; + Interlocked.Increment(ref processedMoleculeCount); + longWaitBroker.ProgressValue = processedMoleculeCount * 100 / moleculeListMoleculesIndexes.Count; + }); + var newMoleculeLists = new List(); + for (int iMoleculeList = 0; iMoleculeList < document.Children.Count; iMoleculeList++) + { + var moleculeList = (PeptideGroupDocNode) document.Children[iMoleculeList]; + moleculeList = (PeptideGroupDocNode) moleculeList.ChangeChildren(newMoleculeArrays[iMoleculeList]); + newMoleculeLists.Add(moleculeList); + } + return (SrmDocument) document.ChangeChildren(newMoleculeLists.ToArray()); + } + + private void btnPreview_Click(object sender, EventArgs e) + { + Preview(); + } + + public void Preview() + { + var originalDocument = SkylineWindow.Document; + var optimizedDocument = GetOptimizedDocument(originalDocument); + if (optimizedDocument != null) + { + _originalDocument = originalDocument; + _optimizedDocument = optimizedDocument; + var newRows = MakeRows().ToList(); + _rowList.Clear(); + _rowList.AddRange(newRows); + _bindingList.ResetBindings(); + } + } + + private BilinearTransitionOptimizer GetBilinearCurveFitter(CancellationToken cancellationToken) + { + var settings = optimizeTransitionsSettingsControl1.CurrentSettings; + if (settings == null) + { + return null; + } + return new BilinearTransitionOptimizer + { + CancellationToken = cancellationToken, + OptimizeTransitionSettings = settings + }; + } + + public SrmDocument GetOptimizedDocument(SrmDocument document) + { + SrmDocument newDocument = null; + using (var longWaitDlg = new LongWaitDlg()) + { + var bilinearCurveFitter = GetBilinearCurveFitter(longWaitDlg.CancellationToken); + if (bilinearCurveFitter == null) + { + return null; + } + + longWaitDlg.PerformWork(this, 1000, broker => + { + newDocument = OptimizeTransitions(broker, document, bilinearCurveFitter); + }); + } + + return newDocument; + } + + public IEnumerable MakeRows() + { + UpdateViewContext(); + var currentDocument = SkylineWindow.Document; + var bilinearCurveFitter = GetBilinearCurveFitter(CancellationToken.None); + foreach (var moleculeList in currentDocument.MoleculeGroups) + { + foreach (var molecule in moleculeList.Molecules) + { + if (molecule.IsDecoy || null != molecule.GlobalStandardType) + { + continue; + } + + var peptideIdentityPath = new IdentityPath(moleculeList.PeptideGroup, molecule.Peptide); + var row = new Row(new Model.Databinding.Entities.Peptide(_dataSchema, peptideIdentityPath), _originalDocument, _optimizedDocument, bilinearCurveFitter); + yield return row; + } + } + } + + private static FiguresOfMerit MakeFiguresOfMerit(QuantLimit quantLimit, SrmSettings settings) + { + if (quantLimit == null) + { + return null; + } + + return FiguresOfMerit.EMPTY.ChangeLimitOfDetection(quantLimit.Lod) + .ChangeLimitOfQuantification(quantLimit.Loq) + .ChangeUnits(settings.PeptideSettings.Quantification.Units); + } + + private void btnApply_Click(object sender, EventArgs e) + { + Apply(); + } + + public void Apply() + { + lock (SkylineWindow.GetDocumentChangeLock()) + { + SkylineWindow.ModifyDocument(QuantificationStrings.OptimizeDocumentTransitionsForm_Apply_Optimize_transitions, doc => + { + if (null != _optimizedDocument && ReferenceEquals(doc, _originalDocument)) + { + return _optimizedDocument; + } + + var optimizedDoc = GetOptimizedDocument(doc); + if (optimizedDoc != null) + { + _originalDocument = doc; + _optimizedDocument = optimizedDoc; + return optimizedDoc; + } + + return doc; + }, + docPair => AuditLogEntry.DiffDocNodes(MessageType.changed_quantitative, docPair, QuantificationStrings.OptimizeDocumentTransitionsForm_Apply_Optimize_transitions)); + } + } + + public static PeptideQuantifier GetPeptideQuantifier(NormalizationData normalizationData, SrmDocument document, IdentityPath peptideIdentityPath, OptimizeTransitionSettings optimizeTransitionSettings) + { + var moleculeList = (PeptideGroupDocNode) + document.FindNode(peptideIdentityPath.GetIdentity((int) SrmDocument.Level.MoleculeGroups)); + var molecule = (PeptideDocNode) moleculeList?.FindNode(peptideIdentityPath.GetIdentity((int) SrmDocument.Level.Molecules)); + if (molecule == null) + { + return null; + } + + var quantificationSettings = optimizeTransitionSettings.GetQuantificationSettings(document.Settings); + if (normalizationData == null) + { + return PeptideQuantifier.GetPeptideQuantifier(document, moleculeList, molecule) + .WithQuantificationSettings(quantificationSettings); + } + + return new PeptideQuantifier(new Lazy(()=>normalizationData), moleculeList, molecule, + quantificationSettings); + } + + public OptimizeType OptimizeType + { + get + { + return optimizeTransitionsSettingsControl1.OptimizeType; + } + set + { + optimizeTransitionsSettingsControl1.OptimizeType = value; + } + } + + private void UpdateViewContext() + { + var viewContext = new SkylineViewContext(_dataSchema, MakeRowSourceInfos()); + if (BindingListSource.ViewInfo == null || + Equals(BindingListSource.ViewInfo.ViewGroup.Id, ViewGroup.BUILT_IN.Id)) + { + BindingListSource.SetViewContext(viewContext, viewContext.GetViewInfo( + ViewGroup.BUILT_IN, + viewContext.GetViewSpecList(ViewGroup.BUILT_IN.Id).ViewSpecs.FirstOrDefault())); + } + else + { + BindingListSource.SetViewContext(viewContext); + } + } + } +} diff --git a/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeDocumentTransitionsForm.resx b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeDocumentTransitionsForm.resx new file mode 100644 index 0000000000..a7b925b4e4 --- /dev/null +++ b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeDocumentTransitionsForm.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsForm.Designer.cs b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsForm.Designer.cs new file mode 100644 index 0000000000..1c21c4abaa --- /dev/null +++ b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsForm.Designer.cs @@ -0,0 +1,174 @@ +namespace pwiz.Skyline.EditUI.OptimizeTransitions +{ + partial class OptimizeTransitionsForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.panel1 = new System.Windows.Forms.Panel(); + this.btnApply = new System.Windows.Forms.Button(); + this.lblOriginal = new System.Windows.Forms.LinkLabel(); + this.lblOptimized = new System.Windows.Forms.LinkLabel(); + this.btnOptimizeDocumentTransitions = new System.Windows.Forms.Button(); + this.optimizeTransitionsSettingsControl1 = new pwiz.Skyline.EditUI.OptimizeTransitions.OptimizeTransitionsSettingsControl(); + this.calibrationGraphControl1 = new pwiz.Skyline.Controls.Graphs.Calibration.CalibrationGraphControl(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.panel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + this.SuspendLayout(); + // + // databoundGridControl + // + this.databoundGridControl.Dock = System.Windows.Forms.DockStyle.Top; + this.databoundGridControl.Location = new System.Drawing.Point(0, 94); + this.databoundGridControl.Size = new System.Drawing.Size(800, 53); + // + // panel1 + // + this.panel1.Controls.Add(this.btnApply); + this.panel1.Controls.Add(this.lblOriginal); + this.panel1.Controls.Add(this.lblOptimized); + this.panel1.Controls.Add(this.btnOptimizeDocumentTransitions); + this.panel1.Controls.Add(this.optimizeTransitionsSettingsControl1); + this.panel1.Dock = System.Windows.Forms.DockStyle.Top; + this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(800, 94); + this.panel1.TabIndex = 1; + // + // btnApply + // + this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnApply.Location = new System.Drawing.Point(635, 11); + this.btnApply.Name = "btnApply"; + this.btnApply.Size = new System.Drawing.Size(153, 23); + this.btnApply.TabIndex = 3; + this.btnApply.Text = "Apply Optimization"; + this.btnApply.UseVisualStyleBackColor = true; + this.btnApply.Click += new System.EventHandler(this.btnApply_Click); + // + // lblOriginal + // + this.lblOriginal.AutoSize = true; + this.lblOriginal.Location = new System.Drawing.Point(3, 66); + this.lblOriginal.Name = "lblOriginal"; + this.lblOriginal.Size = new System.Drawing.Size(62, 13); + this.lblOriginal.TabIndex = 0; + this.lblOriginal.TabStop = true; + this.lblOriginal.Text = "Original {0}:"; + this.lblOriginal.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.lblOriginal_LinkClicked); + // + // lblOptimized + // + this.lblOptimized.AutoSize = true; + this.lblOptimized.Location = new System.Drawing.Point(202, 66); + this.lblOptimized.Name = "lblOptimized"; + this.lblOptimized.Size = new System.Drawing.Size(73, 13); + this.lblOptimized.TabIndex = 2; + this.lblOptimized.TabStop = true; + this.lblOptimized.Text = "Optimized {0}:"; + this.lblOptimized.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.lblOptimized_LinkClicked); + // + // btnOptimizeDocumentTransitions + // + this.btnOptimizeDocumentTransitions.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnOptimizeDocumentTransitions.Location = new System.Drawing.Point(635, 40); + this.btnOptimizeDocumentTransitions.Name = "btnOptimizeDocumentTransitions"; + this.btnOptimizeDocumentTransitions.Size = new System.Drawing.Size(153, 23); + this.btnOptimizeDocumentTransitions.TabIndex = 1; + this.btnOptimizeDocumentTransitions.Text = "Optimize Entire Document..."; + this.btnOptimizeDocumentTransitions.UseVisualStyleBackColor = true; + this.btnOptimizeDocumentTransitions.Click += new System.EventHandler(this.btnOptimizeDocumentTransitions_Click); + // + // optimizeTransitionsSettingsControl1 + // + this.optimizeTransitionsSettingsControl1.Location = new System.Drawing.Point(0, 0); + this.optimizeTransitionsSettingsControl1.MinNumberOfTransitions = 4; + this.optimizeTransitionsSettingsControl1.Name = "optimizeTransitionsSettingsControl1"; + this.optimizeTransitionsSettingsControl1.PreserveNonQuantitative = false; + this.optimizeTransitionsSettingsControl1.Size = new System.Drawing.Size(629, 63); + this.optimizeTransitionsSettingsControl1.SyncWithGlobalSettings = true; + this.optimizeTransitionsSettingsControl1.TabIndex = 0; + this.optimizeTransitionsSettingsControl1.SettingsChange += new System.EventHandler(this.optimizeTransitionsSettingsControl1_SettingsChanged); + // + // calibrationGraphControl1 + // + this.calibrationGraphControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.calibrationGraphControl1.Location = new System.Drawing.Point(0, 0); + this.calibrationGraphControl1.Name = "calibrationGraphControl1"; + this.calibrationGraphControl1.Size = new System.Drawing.Size(800, 173); + this.calibrationGraphControl1.TabIndex = 2; + // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.Location = new System.Drawing.Point(0, 147); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.calibrationGraphControl1); + this.splitContainer1.Size = new System.Drawing.Size(800, 303); + this.splitContainer1.SplitterDistance = 126; + this.splitContainer1.TabIndex = 3; + // + // OptimizeTransitionsForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Controls.Add(this.splitContainer1); + this.Controls.Add(this.panel1); + this.Name = "OptimizeTransitionsForm"; + this.TabText = "Optimize Transitions"; + this.Text = "Optimize Transitions"; + this.Controls.SetChildIndex(this.panel1, 0); + this.Controls.SetChildIndex(this.databoundGridControl, 0); + this.Controls.SetChildIndex(this.splitContainer1, 0); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Panel panel1; + private OptimizeTransitions.OptimizeTransitionsSettingsControl optimizeTransitionsSettingsControl1; + private Controls.Graphs.Calibration.CalibrationGraphControl calibrationGraphControl1; + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.Button btnOptimizeDocumentTransitions; + private System.Windows.Forms.LinkLabel lblOriginal; + private System.Windows.Forms.LinkLabel lblOptimized; + private System.Windows.Forms.Button btnApply; + } +} \ No newline at end of file diff --git a/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsForm.cs b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsForm.cs new file mode 100644 index 0000000000..60b9a4e6dd --- /dev/null +++ b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsForm.cs @@ -0,0 +1,672 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading; +using System.Windows.Forms; +using pwiz.Common.Collections; +using pwiz.Common.DataBinding; +using pwiz.Common.DataBinding.Attributes; +using pwiz.Common.SystemUtil; +using pwiz.Skyline.Controls; +using pwiz.Skyline.Controls.Databinding; +using pwiz.Skyline.Controls.Graphs.Calibration; +using pwiz.Skyline.Controls.SeqNode; +using pwiz.Skyline.Model; +using pwiz.Skyline.Model.AuditLog; +using pwiz.Skyline.Model.Databinding; +using pwiz.Skyline.Model.DocSettings.AbsoluteQuantification; +using pwiz.Skyline.Model.GroupComparison; +using pwiz.Skyline.Properties; +using pwiz.Skyline.Util.Extensions; +using Transition = pwiz.Skyline.Model.Transition; + +namespace pwiz.Skyline.EditUI.OptimizeTransitions +{ + public partial class OptimizeTransitionsForm : DataboundGridForm + { + private Selection _selection; + private CancellationTokenSource _cancellationTokenSource; + private List _rowList = new List(); + private BindingList _bindingList; + private SkylineDataSchema _dataSchema; + private SequenceTree _sequenceTree; + private OptimizeTransitionDetails _details; + private Selection _detailsSelection; + private string _originalTitle; + private bool _updateTransitionPending; + public OptimizeTransitionsForm(SkylineWindow skylineWindow) + { + InitializeComponent(); + databoundGridControl.Parent.Controls.Remove(databoundGridControl); + databoundGridControl.Dock = DockStyle.Fill; + splitContainer1.Panel1.Controls.Add(databoundGridControl); + SkylineWindow = skylineWindow; + calibrationGraphControl1.SkylineWindow = skylineWindow; + _bindingList = new BindingList(_rowList); + _dataSchema = new SkylineWindowDataSchema(skylineWindow, SkylineDataSchema.GetLocalizedSchemaLocalizer()); + BindingListSource.QueryLock = _dataSchema.QueryLock; + _bindingList = new BindingList(_rowList); + var rootColumn = ColumnDescriptor.RootColumn(_dataSchema, typeof(Row)); + var rowSourceInfo = new RowSourceInfo(BindingListRowSource.Create(_bindingList), + SkylineViewContext.GetDefaultViewInfo(rootColumn)); + BindingListSource.SetViewContext(new SkylineViewContext(_dataSchema, ImmutableList.Singleton(rowSourceInfo))); + DataGridView.CurrentCellChanged += DataGridView_OnSelectionChanged; + DataGridView.SelectionChanged += DataGridView_OnSelectionChanged; + _originalTitle = Text; + Icon = Resources.Skyline; + } + + private void DataGridView_OnSelectionChanged(object sender, EventArgs e) + { + if (!_updateTransitionPending) + { + _updateTransitionPending = true; + BeginInvoke(new Action(DisplayQuantLimitForSelection)); + } + } + + private void DisplayQuantLimitForSelection() + { + _updateTransitionPending = false; + var selectedRowIndexes = DataGridView.SelectedRows.Cast() + .Select(row => row.Index).ToHashSet(); + var transitionIdentityPaths = new HashSet(); + foreach (var rowIndex in selectedRowIndexes) + { + var identityPath = GetRowTransitionIdentityPath(rowIndex); + if (identityPath != null) + { + transitionIdentityPaths.Add(identityPath); + } + } + + foreach (DataGridViewCell cell in DataGridView.SelectedCells) + { + if (selectedRowIndexes.Contains(cell.RowIndex)) + { + continue; + } + transitionIdentityPaths.UnionWith(GetCellTransitionIdentityPaths(cell)); + } + + if (transitionIdentityPaths.Count == 0) + { + transitionIdentityPaths.UnionWith(GetCellTransitionIdentityPaths(DataGridView.CurrentCell)); + } + + if (transitionIdentityPaths.Count == 0) + { + return; + } + + DisplayTransitionQuantLimit(transitionIdentityPaths); + + } + + private IEnumerable GetCellTransitionIdentityPaths(DataGridViewCell cell) + { + IEnumerable identityPaths = Array.Empty(); + if (cell == null) + { + return identityPaths; + } + var rowIndex = cell.RowIndex; + if (rowIndex < 0 || rowIndex >= BindingListSource.Count) + { + return identityPaths; + } + var row = (BindingListSource[rowIndex] as RowItem)?.Value as Row; + if (row == null) + { + return identityPaths; + } + + var transitionIdentityPath = row.Transition.IdentityPath; + identityPaths = new[] { transitionIdentityPath }; + + var columnIndex = cell.ColumnIndex; + if (columnIndex < 0 || columnIndex >= DataGridView.ColumnCount) + { + return identityPaths; + } + var dataPropertyName = DataGridView.Columns[columnIndex]?.DataPropertyName; + if (dataPropertyName == null) + { + return identityPaths; + } + var propertyPath = + (BindingListSource.ItemProperties.FindByName(dataPropertyName) as ColumnPropertyDescriptor) + ?.PropertyPath; + if (propertyPath == null) + { + return identityPaths; + } + + + IList quantLimitList = null; + if (propertyPath.StartsWith(PropertyPath.Root.Property(nameof(Row.SingleQuantLimit)))) + { + quantLimitList = _details.SingleQuantLimits; + } + else if (propertyPath.StartsWith(PropertyPath.Root.Property(nameof(Row.AcceptedQuantLimit)))) + { + quantLimitList = _details.AcceptedQuantLimits; + } + else if (propertyPath.StartsWith(PropertyPath.Root.Property(nameof(Row.RejectedQuantLimit)))) + { + quantLimitList = _details.RejectedQuantLimits; + } + + if (quantLimitList != null) + { + var transitionQuantLimit = quantLimitList.FirstOrDefault(tql => + Equals(transitionIdentityPath, tql.TransitionIdentityPaths.Last())); + if (transitionQuantLimit != null) + { + return transitionQuantLimit.TransitionIdentityPaths; + } + } + return identityPaths; + } + + private IdentityPath GetRowTransitionIdentityPath(int rowIndex) + { + if (rowIndex < 0 || rowIndex >= BindingListSource.Count) + { + return null; + } + var row = (BindingListSource[rowIndex] as RowItem)?.Value as Row; + if (row == null) + { + return null; + } + + return row.Transition.IdentityPath; + } + + public SkylineWindow SkylineWindow { get; } + + protected override void OnHandleCreated(EventArgs e) + { + base.OnHandleCreated(e); + SkylineWindow.DocumentUIChangedEvent += SkylineWindow_OnDocumentUIChangedEvent; + OnDocumentChanged(); + } + + private void SkylineWindow_OnDocumentUIChangedEvent(object sender, DocumentChangedEventArgs e) + { + OnDocumentChanged(); + } + + private void OnDocumentChanged() + { + SetSequenceTree(SkylineWindow.SequenceTree); + UpdateSelection(); + } + + public void UpdateSelection() + { + SetSelection(GetCurrentSelection()); + } + + protected override void OnHandleDestroyed(EventArgs e) + { + SkylineWindow.DocumentUIChangedEvent -= SkylineWindow_OnDocumentUIChangedEvent; + SetSequenceTree(null); + base.OnHandleDestroyed(e); + } + + private Selection GetCurrentSelection() + { + var identityPath = SkylineWindow.SelectedPath; + if (identityPath.Length <= (int)SrmDocument.Level.Molecules) + { + return null; + } + + return new Selection(optimizeTransitionsSettingsControl1.CurrentSettings, SkylineWindow.DocumentUI, + identityPath.GetPathTo((int)SrmDocument.Level.Molecules)); + } + + private void SetSequenceTree(SequenceTree sequenceTree) + { + if (ReferenceEquals(sequenceTree, _sequenceTree)) + { + return; + } + + if (_sequenceTree != null) + { + _sequenceTree.AfterSelect -= SequenceTree_OnAfterSelect; + } + + _sequenceTree = sequenceTree; + if (_sequenceTree != null) + { + _sequenceTree.AfterSelect += SequenceTree_OnAfterSelect; + } + } + + private void SequenceTree_OnAfterSelect(object sender, TreeViewEventArgs e) + { + UpdateSelection(); + } + + private void SetSelection(Selection newSelection) + { + if (Equals(newSelection, _selection)) + { + return; + } + + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource = null; + _selection = newSelection; + if (!Equals(newSelection?.MoleculeIdentityPath, _selection?.MoleculeIdentityPath)) + { + _rowList.Clear(); + } + + if (newSelection != null) + { + _cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = _cancellationTokenSource.Token; + ActionUtil.RunAsync(() => + { + var details = OptimizeTransitions(cancellationToken, newSelection); + CommonActionUtil.SafeBeginInvoke(this, () => + { + if (!cancellationToken.IsCancellationRequested) + { + SetDetails(newSelection, details); + } + }); + }); + } + } + + private void SetDetails(Selection selection, OptimizeTransitionDetails details) + { + if (!Equals(selection, _selection)) + { + return; + } + + if (selection.Settings.OptimizeType == OptimizeType.LOD) + { + lblOriginal.Text = "Original LOD"; + lblOptimized.Text = "Optimized LOD"; + } + else + { + lblOriginal.Text = "Original LLOQ"; + lblOptimized.Text = "Optimized LLOQ"; + } + + + Text = TabText = selection.PeptideDocNode == null + ? _originalTitle + : TextUtil.ColonSeparate(_originalTitle, selection.PeptideDocNode.ModifiedSequenceDisplay); + _rowList.Clear(); + _details = details; + _detailsSelection = selection; + lblOriginal.Text = FormatQuantLimitString(false, details?.Original?.QuantLimit); + var optimizedQuantLimit = details?.Optimized?.QuantLimit; + lblOptimized.Text = FormatQuantLimitString(true, optimizedQuantLimit); + btnApply.Enabled = optimizedQuantLimit != null; + if (details != null) + { + foreach (var singleQuantLimit in details.SingleQuantLimits) + { + var transition = new Model.Databinding.Entities.Transition(_dataSchema, + singleQuantLimit.TransitionIdentityPaths.Single()); + var row = Row.CreateRow(transition, details); + _rowList.Add(row); + } + } + _bindingList.ResetBindings(); + DisplayQuantLimitForSelection(); + } + + private string FormatQuantLimitString(bool optimized, QuantLimit quantLimit) + { + string limitName; + double? value; + if (_selection.Settings.OptimizeType == OptimizeType.LOD) + { + limitName = optimized + ? "Optimzied LOD" + : "Original LOD"; + value = quantLimit?.Lod; + } + else + { + limitName = optimized + ? "Optimized LLOQ" + : "Original LLOQ"; + value = quantLimit?.Loq; + } + + string valueText; + if (value.HasValue) + { + valueText = FiguresOfMerit.FormatValue(value.Value, + !string.IsNullOrEmpty(_selection.Document.Settings.PeptideSettings.Quantification.Units)); + } + else + { + valueText = "Unknown"; + } + + return TextUtil.ColonSeparate(limitName, valueText); + } + + private OptimizeTransitionDetails OptimizeTransitions(CancellationToken cancellationToken, Selection selection) + { + var bilinearCurveFitter = new BilinearTransitionOptimizer() + { + CancellationToken = cancellationToken, + OptimizeTransitionSettings = selection.Settings + }; + var peptideQuantifier = + OptimizeDocumentTransitionsForm.GetPeptideQuantifier(null, selection.Document, + selection.MoleculeIdentityPath, selection.Settings); + if (peptideQuantifier == null) + { + return null; + } + + if (!selection.Settings.PreserveNonQuantitative) + { + peptideQuantifier = peptideQuantifier.MakeAllTransitionsQuantitative(); + } + + var calibrationCurveFitter = + selection.Settings.GetCalibrationCurveFitter(peptideQuantifier, selection.Document.Settings); + var details = new OptimizeTransitionDetails(); + bilinearCurveFitter.OptimizeTransitions(calibrationCurveFitter, details); + return details; + } + + public class Row + { + public Model.Databinding.Entities.Transition Transition { get; private set; } + [ChildDisplayName("Single{0}")] public QuantLimit SingleQuantLimit { get; private set; } + [ChildDisplayName("Accepted{0}")] public QuantLimit AcceptedQuantLimit { get; private set; } + [ChildDisplayName("Rejected{0}")] public QuantLimit RejectedQuantLimit { get; private set; } + + public static Row CreateRow(Model.Databinding.Entities.Transition transition, + OptimizeTransitionDetails details) + { + return new Row + { + Transition = transition, + SingleQuantLimit = details.SingleQuantLimits.FirstOrDefault(tql => + Equals(transition.IdentityPath, tql.TransitionIdentityPaths.Single()))?.QuantLimit, + AcceptedQuantLimit = details.AcceptedQuantLimits + .FirstOrDefault(tql => Equals(transition.IdentityPath, tql.TransitionIdentityPaths.Last())) + ?.QuantLimit, + RejectedQuantLimit = details.RejectedQuantLimits + .FirstOrDefault(tql => Equals(transition.IdentityPath, tql.TransitionIdentityPaths.Last())) + ?.QuantLimit + }; + } + } + + private class Selection : Immutable + { + public Selection(OptimizeTransitionSettings settings, SrmDocument document, IdentityPath identityPath) + { + Settings = settings; + Document = document; + MoleculeIdentityPath = identityPath; + } + public IdentityPath MoleculeIdentityPath { get; private set; } + public SrmDocument Document { get; private set; } + public OptimizeTransitionSettings Settings { get; private set; } + + public PeptideGroupDocNode PeptideGroupDocNode + { + get + { + return Document.FindPeptideGroup((PeptideGroup) MoleculeIdentityPath.GetIdentity(0)); + } + } + + public PeptideDocNode PeptideDocNode + { + get + { + return (PeptideDocNode)PeptideGroupDocNode?.FindNode(MoleculeIdentityPath.GetIdentity(1)); + } + } + + protected bool Equals(Selection other) + { + return MoleculeIdentityPath.Equals(other.MoleculeIdentityPath) + && Document.Equals(other.Document) && + Settings.Equals(other.Settings); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Selection)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = MoleculeIdentityPath.GetHashCode(); + hashCode = (hashCode * 397) ^ Document.GetHashCode(); + hashCode = (hashCode * 397) ^ Settings.GetHashCode(); + return hashCode; + } + } + } + + private void optimizeTransitionsSettingsControl1_SettingsChanged(object sender, EventArgs e) + { + UpdateSelection(); + } + + public void DisplayTransitionQuantLimit(HashSet transitionIdentityPaths) + { + var document = _selection.Document; + var quantificationSettings = _selection.Settings.GetQuantificationSettings(_selection.Document.Settings); + quantificationSettings = quantificationSettings.ChangeLodCalculation(LodCalculation.TURNING_POINT_STDERR); + var peptideQuantifier = PeptideQuantifier.GetPeptideQuantifier(_selection.Document, + _selection.PeptideGroupDocNode, _selection.PeptideDocNode) + .WithQuantificationSettings(quantificationSettings) + .WithQuantifiableTransitions(transitionIdentityPaths); + var calibrationCurveFitter = + _selection.Settings.GetCalibrationCurveFitter(peptideQuantifier, _selection.Document.Settings); + var settings = new CalibrationGraphControl.Settings(document, calibrationCurveFitter) + .ChangeGraphTitle(GetCalibrationCurveTitle(document, transitionIdentityPaths)); + calibrationGraphControl1.Update(settings); + } + + public string GetCalibrationCurveTitle(SrmDocument document, ICollection transitionIdentityPaths) + { + if (transitionIdentityPaths.Count == 0) + { + return string.Empty; + } + + if (transitionIdentityPaths.Count == 1) + { + return string.Format("Calibration curve using only {0} transition", GetTransitionLabel(document, transitionIdentityPaths.Single())); + } + + return string.Format("Calibration curve using {0} transitions", transitionIdentityPaths.Count); + } + + public string GetTransitionLabel(SrmDocument document, IdentityPath transitionIdentityPath) + { + var peptideDocNode = (PeptideDocNode) document.FindNode(transitionIdentityPath.GetPathTo((int)SrmDocument.Level.Molecules)); + var transitionGroupDocNode = + (TransitionGroupDocNode)peptideDocNode?.FindNode( + transitionIdentityPath.GetIdentity((int)SrmDocument.Level.TransitionGroups)); + var transitionDocNode = + (TransitionDocNode)transitionGroupDocNode?.FindNode( + transitionIdentityPath.GetIdentity((int)SrmDocument.Level.Transitions)); + if (transitionDocNode == null) + { + return transitionIdentityPath.ToString(); + } + + var transition = transitionDocNode.Transition; + string transitionText; + if (transition.IsCustom() || transition.IsPrecursor()) + { + transitionText = transition.ToString(); + } + else + { + transitionText = string.Concat(transition.IonType.ToString().ToLowerInvariant(), + transition.Ordinal, + Transition.GetChargeIndicator(transition.Adduct)); + } + + if (peptideDocNode.Children.Count == 1) + { + return transitionText; + } + + string precursorText = TransitionGroupTreeNode.GetLabel(transitionGroupDocNode.TransitionGroup, + transitionGroupDocNode.PrecursorMz, string.Empty); + return TextUtil.ColonSeparate(precursorText, transitionText); + } + + private void btnOptimizeDocumentTransitions_Click(object sender, EventArgs e) + { + ShowOptimizeDocumentTransitionsForm(); + } + + public void ShowOptimizeDocumentTransitionsForm() + { + SkylineWindow.ShowOptimizeDocumentTransitionsForm(); + } + + private void lblOriginal_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + SelectOriginalTransitionRows(); + } + + public void SelectOriginalTransitionRows() + { + SelectTransitionRows(_details?.Original?.TransitionIdentityPaths); + } + + private void lblOptimized_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + SelectOptimizedTransitionRows(); + } + + public void SelectOptimizedTransitionRows() + { + SelectTransitionRows(_details?.Optimized?.TransitionIdentityPaths); + } + + public void SelectTransitionRows(IEnumerable transitionIdentityPaths) + { + if (transitionIdentityPaths == null) + { + return; + } + var set = transitionIdentityPaths.ToHashSet(); + DataGridView.ClearSelection(); + foreach (DataGridViewRow row in DataGridView.Rows) + { + var identityPath = GetRowTransitionIdentityPath(row.Index); + if (identityPath != null) + { + row.Selected = set.Contains(identityPath); + } + } + } + + + + private void btnApply_Click(object sender, EventArgs e) + { + var optimizedTransitions = _details?.Optimized?.TransitionIdentityPaths; + if (optimizedTransitions == null) + { + return; + } + + SetQuantifiableTransitions(_selection.PeptideGroupDocNode.PeptideGroup, _selection.PeptideDocNode.Peptide, + optimizedTransitions); + } + + public void SetQuantifiableTransitions(PeptideGroup peptideGroup, Peptide peptide, IEnumerable identityPaths) + { + if (identityPaths == null) + { + return; + } + + string message = string.Empty; + var set = identityPaths.ToHashSet(); + SkylineWindow.ModifyDocument("Optimize Transitions", doc => + { + var peptideIdentityPath = new IdentityPath(peptideGroup, peptide); + var peptideDocNode = (PeptideDocNode)doc.FindNode(peptideIdentityPath); + if (peptideDocNode == null) + { + return doc; + } + + var newTransitionGroups = new List(); + foreach (var transitionGroupDocNode in peptideDocNode.TransitionGroups) + { + var newTransitions = new List(); + foreach (var transition in transitionGroupDocNode.Transitions) + { + var identityPath = new IdentityPath(peptideGroup, peptide, + transitionGroupDocNode.TransitionGroup, transition.Transition); + newTransitions.Add(transition.ChangeQuantitative(set.Contains(identityPath))); + } + + if (newTransitions.SequenceEqual(transitionGroupDocNode.Transitions)) + { + newTransitionGroups.Add(transitionGroupDocNode); + } + else + { + newTransitionGroups.Add( + (TransitionGroupDocNode)transitionGroupDocNode.ChangeChildren(newTransitions)); + } + } + + if (peptideDocNode.TransitionGroups.SequenceEqual(newTransitionGroups)) + { + return doc; + } + + message = peptideDocNode.ModifiedSequenceDisplay; + return (SrmDocument)doc.ReplaceChild(new IdentityPath(peptideGroup), + peptideDocNode.ChangeChildren(newTransitionGroups)); + }, docPair => AuditLogEntry.DiffDocNodes(MessageType.changed_quantitative, docPair, message)); + } + + public CalibrationGraphControl CalibrationGraphControl + { + get { return calibrationGraphControl1; } + } + + public override bool IsComplete + { + get + { + return base.IsComplete && !_updateTransitionPending && Equals(_selection, _detailsSelection); + } + } + } +} + diff --git a/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsForm.resx b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsForm.resx new file mode 100644 index 0000000000..a7b925b4e4 --- /dev/null +++ b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsForm.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsResources.Designer.cs b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsResources.Designer.cs new file mode 100644 index 0000000000..8d5b9a1b4d --- /dev/null +++ b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsResources.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace pwiz.Skyline.EditUI.OptimizeTransitions { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class OptimizeTransitionsResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal OptimizeTransitionsResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("pwiz.Skyline.EditUI.OptimizeTransitions.OptimizeTransitionsResources", typeof(OptimizeTransitionsResources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Optimize Document Transitions. + /// + internal static string OptimizeDocumentTransitionsForm_OptimizeDocumentTransitionsForm_Optimize_Document_Transitions { + get { + return ResourceManager.GetString("OptimizeDocumentTransitionsForm_OptimizeDocumentTransitionsForm_Optimize_Document" + + "_Transitions", resourceCulture); + } + } + } +} diff --git a/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsResources.resx b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsResources.resx new file mode 100644 index 0000000000..e41107804d --- /dev/null +++ b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsResources.resx @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Optimize Document Transitions + + \ No newline at end of file diff --git a/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsSettingsControl.Designer.cs b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsSettingsControl.Designer.cs new file mode 100644 index 0000000000..562171c39b --- /dev/null +++ b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsSettingsControl.Designer.cs @@ -0,0 +1,140 @@ +namespace pwiz.Skyline.EditUI.OptimizeTransitions +{ + partial class OptimizeTransitionsSettingsControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.groupBoxOptimize = new System.Windows.Forms.GroupBox(); + this.radioLOQ = new System.Windows.Forms.RadioButton(); + this.radioLOD = new System.Windows.Forms.RadioButton(); + this.cbxPreserveNonQuantitative = new System.Windows.Forms.CheckBox(); + this.tbxMinTransitions = new System.Windows.Forms.NumericUpDown(); + this.lblMinTransitions = new System.Windows.Forms.Label(); + this.groupBoxOptimize.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.tbxMinTransitions)).BeginInit(); + this.SuspendLayout(); + // + // groupBoxOptimize + // + this.groupBoxOptimize.Controls.Add(this.radioLOQ); + this.groupBoxOptimize.Controls.Add(this.radioLOD); + this.groupBoxOptimize.Location = new System.Drawing.Point(223, 0); + this.groupBoxOptimize.Name = "groupBoxOptimize"; + this.groupBoxOptimize.Size = new System.Drawing.Size(211, 59); + this.groupBoxOptimize.TabIndex = 12; + this.groupBoxOptimize.TabStop = false; + this.groupBoxOptimize.Text = "Optimize"; + // + // radioLOQ + // + this.radioLOQ.AutoSize = true; + this.radioLOQ.Checked = true; + this.radioLOQ.Location = new System.Drawing.Point(6, 39); + this.radioLOQ.Name = "radioLOQ"; + this.radioLOQ.Size = new System.Drawing.Size(124, 17); + this.radioLOQ.TabIndex = 1; + this.radioLOQ.TabStop = true; + this.radioLOQ.Text = "Limit of quantification"; + this.radioLOQ.UseVisualStyleBackColor = true; + this.radioLOQ.CheckedChanged += new System.EventHandler(this.SettingsValueChange); + // + // radioLOD + // + this.radioLOD.AutoSize = true; + this.radioLOD.Location = new System.Drawing.Point(6, 16); + this.radioLOD.Name = "radioLOD"; + this.radioLOD.Size = new System.Drawing.Size(105, 17); + this.radioLOD.TabIndex = 0; + this.radioLOD.Text = "Limit of detection"; + this.radioLOD.UseVisualStyleBackColor = true; + this.radioLOD.CheckedChanged += new System.EventHandler(this.SettingsValueChange); + // + // cbxPreserveNonQuantitative + // + this.cbxPreserveNonQuantitative.AutoSize = true; + this.cbxPreserveNonQuantitative.Location = new System.Drawing.Point(6, 42); + this.cbxPreserveNonQuantitative.Name = "cbxPreserveNonQuantitative"; + this.cbxPreserveNonQuantitative.Size = new System.Drawing.Size(197, 17); + this.cbxPreserveNonQuantitative.TabIndex = 11; + this.cbxPreserveNonQuantitative.Text = "Preserve non-quantitative transitions"; + this.cbxPreserveNonQuantitative.UseVisualStyleBackColor = true; + this.cbxPreserveNonQuantitative.CheckedChanged += new System.EventHandler(this.SettingsValueChange); + // + // tbxMinTransitions + // + this.tbxMinTransitions.Location = new System.Drawing.Point(6, 16); + this.tbxMinTransitions.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.tbxMinTransitions.Name = "tbxMinTransitions"; + this.tbxMinTransitions.Size = new System.Drawing.Size(120, 20); + this.tbxMinTransitions.TabIndex = 10; + this.tbxMinTransitions.Value = new decimal(new int[] { + 4, + 0, + 0, + 0}); + this.tbxMinTransitions.ValueChanged += new System.EventHandler(this.SettingsValueChange); + // + // lblMinTransitions + // + this.lblMinTransitions.AutoSize = true; + this.lblMinTransitions.Location = new System.Drawing.Point(3, 0); + this.lblMinTransitions.Name = "lblMinTransitions"; + this.lblMinTransitions.Size = new System.Drawing.Size(148, 13); + this.lblMinTransitions.TabIndex = 9; + this.lblMinTransitions.Text = "Minimum number of transitions"; + // + // OptimizeTransitionsSettingsControl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.groupBoxOptimize); + this.Controls.Add(this.cbxPreserveNonQuantitative); + this.Controls.Add(this.tbxMinTransitions); + this.Controls.Add(this.lblMinTransitions); + this.Name = "OptimizeTransitionsSettingsControl"; + this.Size = new System.Drawing.Size(453, 65); + this.groupBoxOptimize.ResumeLayout(false); + this.groupBoxOptimize.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.tbxMinTransitions)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + private System.Windows.Forms.GroupBox groupBoxOptimize; + private System.Windows.Forms.RadioButton radioLOQ; + private System.Windows.Forms.RadioButton radioLOD; + private System.Windows.Forms.CheckBox cbxPreserveNonQuantitative; + private System.Windows.Forms.NumericUpDown tbxMinTransitions; + private System.Windows.Forms.Label lblMinTransitions; + } +} diff --git a/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsSettingsControl.cs b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsSettingsControl.cs new file mode 100644 index 0000000000..2abb1f2531 --- /dev/null +++ b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsSettingsControl.cs @@ -0,0 +1,158 @@ +using System; +using System.Windows.Forms; +using pwiz.Skyline.Model.DocSettings.AbsoluteQuantification; + +namespace pwiz.Skyline.EditUI.OptimizeTransitions +{ + public partial class OptimizeTransitionsSettingsControl : UserControl + { + private bool _inSettingsChange; + private OptimizeTransitionSettings _currentSettings = OptimizeTransitionSettings.DEFAULT; + private bool _syncWithGlobalSettings = true; + public OptimizeTransitionsSettingsControl() + { + InitializeComponent(); + FireSettingsChange(false); + } + + public OptimizeTransitionSettings CurrentSettings + { + get + { + return _currentSettings; + } + } + + public bool SyncWithGlobalSettings + { + get + { + return _syncWithGlobalSettings; + } + set + { + _syncWithGlobalSettings = value; + } + } + + protected override void OnHandleCreated(EventArgs e) + { + base.OnHandleCreated(e); + OptimizeTransitionSettings.GlobalSettingsChange += OptimizeTransitionSettings_OnGlobalSettingsChange; + if (SyncWithGlobalSettings) + { + OptimizeTransitionSettings_OnGlobalSettingsChange(); + } + } + + private void OptimizeTransitionSettings_OnGlobalSettingsChange() + { + if (!_syncWithGlobalSettings || _inSettingsChange) + { + return; + } + + if (!Equals(_currentSettings, OptimizeTransitionSettings.GlobalSettings)) + { + _currentSettings = OptimizeTransitionSettings.GlobalSettings; + FireSettingsChange(false); + } + } + + protected override void OnHandleDestroyed(EventArgs e) + { + OptimizeTransitionSettings.GlobalSettingsChange -= OptimizeTransitionSettings_OnGlobalSettingsChange; + base.OnHandleDestroyed(e); + } + + public int MinNumberOfTransitions + { + get + { + return (int)tbxMinTransitions.Value; + } + set + { + tbxMinTransitions.Value = value; + } + } + + public OptimizeType OptimizeType + { + get + { + return radioLOD.Checked ? OptimizeType.LOD : OptimizeType.LOQ; + } + set + { + if (value == OptimizeType.LOD) + { + radioLOD.Checked = true; + } + else + { + radioLOQ.Checked = true; + } + } + } + + public bool PreserveNonQuantitative + { + get + { + return cbxPreserveNonQuantitative.Checked; + } + set + { + cbxPreserveNonQuantitative.Checked = value; + } + } + + public event EventHandler SettingsChange; + + private void SettingsValueChange(object sender, EventArgs e) + { + FireSettingsChange(true); + } + + private void FireSettingsChange(bool fromUi) + { + var settings = _currentSettings; + if (fromUi) + { + settings = settings + .ChangeMinimumNumberOfTransitions((int)tbxMinTransitions.Value) + .ChangeOptimizeType(radioLOD.Checked ? OptimizeType.LOD : OptimizeType.LOQ) + .ChangePreserveNonQuantitative(cbxPreserveNonQuantitative.Checked); + } + + _currentSettings = settings; + if (_inSettingsChange) + { + return; + } + + try + { + _inSettingsChange = true; + if (!fromUi) + { + tbxMinTransitions.Value = settings.MinimumNumberOfTransitions; + radioLOD.Checked = settings.OptimizeType == OptimizeType.LOD; + radioLOQ.Checked = settings.OptimizeType == OptimizeType.LOQ; + cbxPreserveNonQuantitative.Checked = settings.PreserveNonQuantitative; + } + + if (SyncWithGlobalSettings) + { + OptimizeTransitionSettings.GlobalSettings = settings; + } + SettingsChange?.Invoke(this, EventArgs.Empty); + } + finally + { + _inSettingsChange = false; + } + } + } +} diff --git a/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsSettingsControl.resx b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsSettingsControl.resx new file mode 100644 index 0000000000..1af7de150c --- /dev/null +++ b/pwiz_tools/Skyline/EditUI/OptimizeTransitions/OptimizeTransitionsSettingsControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/pwiz_tools/Skyline/Menus/RefineMenu.Designer.cs b/pwiz_tools/Skyline/Menus/RefineMenu.Designer.cs index 3277e2f550..0485793f70 100644 --- a/pwiz_tools/Skyline/Menus/RefineMenu.Designer.cs +++ b/pwiz_tools/Skyline/Menus/RefineMenu.Designer.cs @@ -51,6 +51,7 @@ private void InitializeComponent() this.removeEmptyPeptidesMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.removeDuplicatePeptidesMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.removeRepeatedPeptidesMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.optimizeTransitionsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator35 = new System.Windows.Forms.ToolStripSeparator(); this.permuteIsotopeModificationsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.refineAdvancedMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -83,6 +84,7 @@ private void InitializeComponent() this.removeEmptyPeptidesMenuItem, this.removeDuplicatePeptidesMenuItem, this.removeRepeatedPeptidesMenuItem, + this.optimizeTransitionsMenuItem, this.toolStripSeparator35, this.permuteIsotopeModificationsMenuItem, this.refineAdvancedMenuItem}); @@ -218,6 +220,12 @@ private void InitializeComponent() resources.ApplyResources(this.removeRepeatedPeptidesMenuItem, "removeRepeatedPeptidesMenuItem"); this.removeRepeatedPeptidesMenuItem.Click += new System.EventHandler(this.removeRepeatedPeptidesMenuItem_Click); // + // optimizeTransitionsMenuItem + // + this.optimizeTransitionsMenuItem.Name = "optimizeTransitionsMenuItem"; + resources.ApplyResources(this.optimizeTransitionsMenuItem, "optimizeTransitionsMenuItem"); + this.optimizeTransitionsMenuItem.Click += new System.EventHandler(this.optimizeTransitionsMenuItem_Click); + // // toolStripSeparator35 // this.toolStripSeparator35.Name = "toolStripSeparator35"; @@ -276,5 +284,6 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripSeparator toolStripSeparator35; private System.Windows.Forms.ToolStripMenuItem permuteIsotopeModificationsMenuItem; private System.Windows.Forms.ToolStripMenuItem refineAdvancedMenuItem; + private System.Windows.Forms.ToolStripMenuItem optimizeTransitionsMenuItem; } } diff --git a/pwiz_tools/Skyline/Menus/RefineMenu.cs b/pwiz_tools/Skyline/Menus/RefineMenu.cs index 9c63df54f1..25d98b14c9 100644 --- a/pwiz_tools/Skyline/Menus/RefineMenu.cs +++ b/pwiz_tools/Skyline/Menus/RefineMenu.cs @@ -391,5 +391,10 @@ public void ShowRefineDlg() } } } + + private void optimizeTransitionsMenuItem_Click(object sender, EventArgs e) + { + SkylineWindow.ShowOptimizeTransitionsForm(); + } } } diff --git a/pwiz_tools/Skyline/Menus/RefineMenu.resx b/pwiz_tools/Skyline/Menus/RefineMenu.resx index 60669d9195..e09a8326de 100644 --- a/pwiz_tools/Skyline/Menus/RefineMenu.resx +++ b/pwiz_tools/Skyline/Menus/RefineMenu.resx @@ -124,13 +124,43 @@ 155, 17 + + 0, 0 + + + 150, 24 + + + + 0 + + + menuStrip1 + + + menuStrip1 + + + System.Windows.Forms.MenuStrip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + 52, 20 + + + &Refine + 246, 22 &Reintegrate... - True @@ -182,36 +212,36 @@ Re&name Proteins... + + 246, 22 + + + &Sort Proteins + - 180, 22 + 173, 22 By &Name - 180, 22 + 173, 22 By &Accession - 180, 22 + 173, 22 By &Preferred Name - 180, 22 + 173, 22 By &Gene - - 246, 22 - - - &Sort Proteins - 243, 6 @@ -247,6 +277,12 @@ will be removed. All repeated peptides will be removed to leave only the first occurrence of any peptide. + + 246, 22 + + + Optimize Transitions... + 243, 6 @@ -262,36 +298,6 @@ first occurrence of any peptide. &Advanced... - - 52, 20 - - - &Refine - - - 0, 0 - - - 150, 24 - - - 0 - - - menuStrip1 - - - menuStrip1 - - - System.Windows.Forms.MenuStrip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - $this - - - 0 - True @@ -302,7 +308,7 @@ first occurrence of any peptide. modeUIHandler - pwiz.Skyline.Util.Helpers+ModeUIExtender, Skyline, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + pwiz.Skyline.Util.Helpers+ModeUIExtender, Skyline-daily, Version=22.2.1.442, Culture=neutral, PublicKeyToken=null refineToolStripMenuItem @@ -430,6 +436,12 @@ first occurrence of any peptide. System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + optimizeTransitionsMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + toolStripSeparator35 @@ -452,6 +464,6 @@ first occurrence of any peptide. RefineMenu - pwiz.Skyline.Menus.SkylineControl, Skyline, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + pwiz.Skyline.Menus.SkylineControl, Skyline-daily, Version=22.2.1.442, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/pwiz_tools/Skyline/Model/AuditLog/EnumNames.Designer.cs b/pwiz_tools/Skyline/Model/AuditLog/EnumNames.Designer.cs index 89c0dc20da..474dffdd94 100644 --- a/pwiz_tools/Skyline/Model/AuditLog/EnumNames.Designer.cs +++ b/pwiz_tools/Skyline/Model/AuditLog/EnumNames.Designer.cs @@ -195,6 +195,15 @@ public static string BilinearFit_bilinear { } } + /// + /// Looks up a localized string similar to Bilinear. + /// + public static string BilinearRegressionFit_bilinear { + get { + return ResourceManager.GetString("BilinearRegressionFit_bilinear", resourceCulture); + } + } + /// /// Looks up a localized string similar to Chebyshev. /// @@ -807,6 +816,15 @@ public static string LodCalculation_turning_point { } } + /// + /// Looks up a localized string similar to Bilinear turning point standard error. + /// + public static string LodCalculation_turning_point_stderr { + get { + return ResourceManager.GetString("LodCalculation_turning_point_stderr", resourceCulture); + } + } + /// /// Looks up a localized string similar to Always. /// diff --git a/pwiz_tools/Skyline/Model/AuditLog/EnumNames.resx b/pwiz_tools/Skyline/Model/AuditLog/EnumNames.resx index 82b8f01cea..70a93a5fcb 100644 --- a/pwiz_tools/Skyline/Model/AuditLog/EnumNames.resx +++ b/pwiz_tools/Skyline/Model/AuditLog/EnumNames.resx @@ -720,6 +720,12 @@ Ratio to Light + + Bilinear turning point standard error + + + Bilinear + Assigned to the gene with the most peptides diff --git a/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnCaptions.Designer.cs b/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnCaptions.Designer.cs index 68063be287..d2055821c1 100644 --- a/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnCaptions.Designer.cs +++ b/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnCaptions.Designer.cs @@ -591,6 +591,24 @@ public static string ConfidenceLevel { } } + /// + /// Looks up a localized string similar to Count Non Quantitative. + /// + public static string CountNonQuantitative { + get { + return ResourceManager.GetString("CountNonQuantitative", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Count Quantitative. + /// + public static string CountQuantitative { + get { + return ResourceManager.GetString("CountQuantitative", resourceCulture); + } + } + /// /// Looks up a localized string similar to Count Truncated. /// @@ -1590,6 +1608,24 @@ public static string LinearFit { } } + /// + /// Looks up a localized string similar to LLOQ Improvement. + /// + public static string LloqImprovement { + get { + return ResourceManager.GetString("LloqImprovement", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lod Improvement. + /// + public static string LodImprovement { + get { + return ResourceManager.GetString("LodImprovement", resourceCulture); + } + } + /// /// Looks up a localized string similar to Log 2 Fold Change. /// @@ -2355,6 +2391,33 @@ public static string OptDeclusteringPotential { } } + /// + /// Looks up a localized string similar to Optimized Figures Of Merit. + /// + public static string OptimizedFiguresOfMerit { + get { + return ResourceManager.GetString("OptimizedFiguresOfMerit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Optimized Limit of Detection. + /// + public static string OptimizedLimitOfDetection { + get { + return ResourceManager.GetString("OptimizedLimitOfDetection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Optimized Limit of Quantification. + /// + public static string OptimizedLimitOfQuantification { + get { + return ResourceManager.GetString("OptimizedLimitOfQuantification", resourceCulture); + } + } + /// /// Looks up a localized string similar to Opt Step. /// @@ -2364,6 +2427,33 @@ public static string OptStep { } } + /// + /// Looks up a localized string similar to Original Figures Of Merit. + /// + public static string OriginalFiguresOfMerit { + get { + return ResourceManager.GetString("OriginalFiguresOfMerit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Original Limit of Detection. + /// + public static string OriginalLimitOfDetection { + get { + return ResourceManager.GetString("OriginalLimitOfDetection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Original Limit of Quantification. + /// + public static string OriginalLimitOfQuantification { + get { + return ResourceManager.GetString("OriginalLimitOfQuantification", resourceCulture); + } + } + /// /// Looks up a localized string similar to Peak Group End Time. /// diff --git a/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnCaptions.resx b/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnCaptions.resx index 09b17ffa8d..fe53ca8ed2 100644 --- a/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnCaptions.resx +++ b/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnCaptions.resx @@ -294,6 +294,12 @@ Confidence Level + + Count Non Quantitative + + + Count Quantitative + Count Truncated @@ -627,6 +633,12 @@ Linear Fit + + LLOQ Improvement + + + Lod Improvement + Log 2 Fold Change @@ -882,9 +894,27 @@ Opt Declustering Potential + + Optimized Figures Of Merit + + + Optimized Limit of Detection + + + Optimized Limit of Quantification + Opt Step + + Original Figures Of Merit + + + Original Limit of Detection + + + Original Limit of Quantification + Peak Group End Time diff --git a/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnToolTips.Designer.cs b/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnToolTips.Designer.cs index 9859a2501e..e507881937 100644 --- a/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnToolTips.Designer.cs +++ b/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnToolTips.Designer.cs @@ -605,6 +605,24 @@ public static string ConfidenceLevel { } } + /// + /// Looks up a localized string similar to Number of transitions that were marked as non-quantitative after the optimization of the figures of merit. + /// + public static string CountNonQuantitative { + get { + return ResourceManager.GetString("CountNonQuantitative", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Number of transitions that remained after the optimization of the figures of merit. + /// + public static string CountQuantitative { + get { + return ResourceManager.GetString("CountQuantitative", resourceCulture); + } + } + /// /// Looks up a localized string similar to The number of transitions for a precursor that integrate a peak with a ///boundary at either end of the acquisition time range, where intensity at the end is @@ -1643,6 +1661,24 @@ public static string LinearFit { } } + /// + /// Looks up a localized string similar to Amount that the lower limit of quantification was improved by the marking some transitions as non-quantitative. + /// + public static string LloqImprovement { + get { + return ResourceManager.GetString("LloqImprovement", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Amount that the limit of detection was improved by marking some transitions as non-quantitative. + /// + public static string LodImprovement { + get { + return ResourceManager.GetString("LodImprovement", resourceCulture); + } + } + /// /// Looks up a localized string similar to The logarithm base 2 of the fold change value.. /// @@ -2431,6 +2467,33 @@ public static string OptDeclusteringPotential { } } + /// + /// Looks up a localized string similar to Figures of merit after marking some transitions as non-quantitative. + /// + public static string OptimizedFiguresOfMerit { + get { + return ResourceManager.GetString("OptimizedFiguresOfMerit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Limit of detection after marking some transitions as non-quantitative. + /// + public static string OptimizedLimitOfDetection { + get { + return ResourceManager.GetString("OptimizedLimitOfDetection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lower limit of quantification after marking some transitions as non-quantitative. + /// + public static string OptimizedLimitOfQuantification { + get { + return ResourceManager.GetString("OptimizedLimitOfQuantification", resourceCulture); + } + } + /// /// Looks up a localized string similar to Optimization step value indicating distance from the default value for the ///parameter being optimized, 0 for the default parameter value, or if no optimization is @@ -2442,6 +2505,33 @@ public static string OptStep { } } + /// + /// Looks up a localized string similar to Figures of merit before marking any transitions as non-quantitative. + /// + public static string OriginalFiguresOfMerit { + get { + return ResourceManager.GetString("OriginalFiguresOfMerit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Limit of detection before marking any transitions as non-quantitative. + /// + public static string OriginalLimitOfDetection { + get { + return ResourceManager.GetString("OriginalLimitOfDetection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lower limit of quantification before marking any transitions as non-quantitative. + /// + public static string OriginalLimitOfQuantification { + get { + return ResourceManager.GetString("OriginalLimitOfQuantification", resourceCulture); + } + } + /// /// Looks up a localized string similar to The maximum end time of the individual peaks in the peak group.. /// diff --git a/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnToolTips.resx b/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnToolTips.resx index 00e8667366..dea58e1d7f 100644 --- a/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnToolTips.resx +++ b/pwiz_tools/Skyline/Model/Databinding/Entities/ColumnToolTips.resx @@ -289,6 +289,12 @@ specific default collision energy equation within Skyline Confidence level which was used to determine the confidence interval around the fold change. + + Number of transitions that were marked as non-quantitative after the optimization of the figures of merit + + + Number of transitions that remained after the optimization of the figures of merit + The number of transitions for a precursor that integrate a peak with a boundary at either end of the acquisition time range, where intensity at the end is @@ -662,6 +668,12 @@ The options for determining this value can be specified on the Quantification ta The result of doing the linear regression to determine the fold change. + + Amount that the lower limit of quantification was improved by the marking some transitions as non-quantitative + + + Amount that the limit of detection was improved by marking some transitions as non-quantitative + The logarithm base 2 of the fold change value. @@ -943,11 +955,29 @@ collision energy optimization is being performed The declustering potential value corresponding to the OptStep if declustering potential optimization is being performed + + Figures of merit after marking some transitions as non-quantitative + + + Limit of detection after marking some transitions as non-quantitative + + + Lower limit of quantification after marking some transitions as non-quantitative + Optimization step value indicating distance from the default value for the parameter being optimized, 0 for the default parameter value, or if no optimization is being performed in the replicate. + + Figures of merit before marking any transitions as non-quantitative + + + Limit of detection before marking any transitions as non-quantitative + + + Lower limit of quantification before marking any transitions as non-quantitative + The maximum end time of the individual peaks in the peak group. diff --git a/pwiz_tools/Skyline/Model/Databinding/Entities/Peptide.cs b/pwiz_tools/Skyline/Model/Databinding/Entities/Peptide.cs index 5af34957e3..39e97fb346 100644 --- a/pwiz_tools/Skyline/Model/Databinding/Entities/Peptide.cs +++ b/pwiz_tools/Skyline/Model/Databinding/Entities/Peptide.cs @@ -442,7 +442,7 @@ public LinkValue CalibrationCurve if (DocNode.HasPrecursorConcentrations && Settings.Default.CalibrationCurveOptions.SingleBatch) { - Settings.Default.CalibrationCurveOptions.SingleBatch = false; + Settings.Default.CalibrationCurveOptions = Settings.Default.CalibrationCurveOptions.ChangeSingleBatch(false); calibrationForm.UpdateUI(false); } } diff --git a/pwiz_tools/Skyline/Model/Databinding/Entities/PeptideResult.cs b/pwiz_tools/Skyline/Model/Databinding/Entities/PeptideResult.cs index 829d3bc4ad..7aec6f4922 100644 --- a/pwiz_tools/Skyline/Model/Databinding/Entities/PeptideResult.cs +++ b/pwiz_tools/Skyline/Model/Databinding/Entities/PeptideResult.cs @@ -237,7 +237,8 @@ public LinkValue ReplicateCalibrationCurve if (!string.IsNullOrEmpty(ResultFile.Replicate.BatchName) || Peptide.DocNode.HasPrecursorConcentrations) { - Settings.Default.CalibrationCurveOptions.SingleBatch = true; + Settings.Default.CalibrationCurveOptions = + Settings.Default.CalibrationCurveOptions.ChangeSingleBatch(true); calibrationForm.UpdateUI(false); } } diff --git a/pwiz_tools/Skyline/Model/Databinding/Entities/PrecursorResult.cs b/pwiz_tools/Skyline/Model/Databinding/Entities/PrecursorResult.cs index 8277cf7004..a47aa469fa 100644 --- a/pwiz_tools/Skyline/Model/Databinding/Entities/PrecursorResult.cs +++ b/pwiz_tools/Skyline/Model/Databinding/Entities/PrecursorResult.cs @@ -29,6 +29,7 @@ using pwiz.Skyline.Model.Hibernate; using pwiz.Skyline.Model.Results; using pwiz.Skyline.Model.Results.Scoring; +using pwiz.Skyline.Properties; using pwiz.Skyline.Util.Extensions; namespace pwiz.Skyline.Model.Databinding.Entities @@ -197,7 +198,8 @@ public LinkValue PrecursorQuantification skylineWindow.ShowCalibrationForm(); skylineWindow.SelectedResultsIndex = GetResultFile().Replicate.ReplicateIndex; skylineWindow.SelectedPath = Precursor.IdentityPath; - Properties.Settings.Default.CalibrationCurveOptions.SingleBatch = true; + Settings.Default.CalibrationCurveOptions = + Settings.Default.CalibrationCurveOptions.ChangeSingleBatch(true); } }); } diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/BilinearRegressionFit.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/BilinearRegressionFit.cs new file mode 100644 index 0000000000..466413ed2c --- /dev/null +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/BilinearRegressionFit.cs @@ -0,0 +1,56 @@ +/* + * Original author: Nicholas Shulman , + * MacCoss Lab, Department of Genome Sciences, UW + * + * Copyright 2023 University of Washington - Seattle, WA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System.Collections.Generic; +using System.Linq; + +namespace pwiz.Skyline.Model.DocSettings.AbsoluteQuantification +{ + public class BilinearRegressionFit : RegressionFit + { + public BilinearRegressionFit() : base(@"bilinear", () => QuantificationStrings.RegressionFit_BILINEAR_Bilinear) + { + + } + + protected override CalibrationCurve FitPoints(IList points) + { + var concentrations = points.Select(pt => pt.X).Distinct().OrderBy(x=>x).ToList(); + ScoredBilinearCurve bestCurve = null; + var linearCurve = LINEAR.Fit(points) as CalibrationCurve.Linear; + if (linearCurve != null) + { + bestCurve = ScoredBilinearCurve.FromCalibrationCurve(linearCurve, points); + } + foreach (var xOffset in concentrations) + { + var candidateCurveFit = ScoredBilinearCurve.WithOffset(xOffset, points); + if (candidateCurveFit == null) + { + continue; + } + if (bestCurve == null || candidateCurveFit.Error < bestCurve.Error) + { + bestCurve = candidateCurveFit; + } + } + + return bestCurve?.CalibrationCurve; + } + } +} \ No newline at end of file diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/BilinearTransitionOptimizer.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/BilinearTransitionOptimizer.cs new file mode 100644 index 0000000000..570062dedd --- /dev/null +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/BilinearTransitionOptimizer.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using pwiz.Common.Collections; +using ZedGraph; + +namespace pwiz.Skyline.Model.DocSettings.AbsoluteQuantification +{ + public class BilinearTransitionOptimizer + { + public BilinearTransitionOptimizer() + { + OptimizeTransitionSettings = OptimizeTransitionSettings.DEFAULT; + BootstrapFiguresOfMeritCalculator = new BootstrapFiguresOfMeritCalculator(.2); + } + + public OptimizeTransitionSettings OptimizeTransitionSettings { get; set; } + public CancellationToken CancellationToken { get; set; } + public BootstrapFiguresOfMeritCalculator BootstrapFiguresOfMeritCalculator { get; set; } + + public double ComputeBootstrappedLoq(IList points, List> bootstrapCurves) + { + return BootstrapFiguresOfMeritCalculator.ComputeBootstrappedLoq(points, bootstrapCurves); + } + + public static double ComputeLod(IList points) + { + return BootstrapFiguresOfMeritCalculator.ComputeLod(points); + } + + public QuantLimit ComputeQuantLimits(IList areas, List> bootstrapCurves = null) + { + var lod = ComputeLod(areas); + var loq = ComputeBootstrappedLoq(areas, bootstrapCurves); + if (loq < lod) + { + loq = lod; + } + return new QuantLimit(lod, loq); + } + + public QuantLimit ComputeQuantLimits(CalibrationCurveFitter calibrationCurveFitter) + { + var weightedPoints = calibrationCurveFitter.GetStandardPoints().ToList(); + List combinedWeightedPoints; + if (OptimizeTransitionSettings.CombinePointsWithSameConcentration) + { + combinedWeightedPoints = new List(); + foreach (var pointGroup in weightedPoints.GroupBy(pt => pt.X)) + { + combinedWeightedPoints.Add(new WeightedPoint(pointGroup.Key, pointGroup.Average(pt => pt.Y), pointGroup.First().Weight)); + } + } + else + { + combinedWeightedPoints = weightedPoints; + } + return ComputeQuantLimits(combinedWeightedPoints); + } + + public PeptideDocNode OptimizeTransitions(CalibrationCurveFitter calibrationCurveFitter, OptimizeTransitionDetails details) + { + int? msLevel = calibrationCurveFitter.QuantificationSettings.MsLevel; + + var standardConcentrations = calibrationCurveFitter.GetStandardConcentrations(); + if (standardConcentrations.Count == 0) + { + return null; + } + + var optimizeType = OptimizeTransitionSettings.OptimizeType; + OptimizeType otherOptimizeType; + if (optimizeType == OptimizeType.LOD) + { + otherOptimizeType = OptimizeType.LOQ; + } + else + { + otherOptimizeType = OptimizeType.LOD; + } + + var maxConcentration = standardConcentrations.Values.Max(); + var singleQuantLimits = new List(); + var peptideDocNode = calibrationCurveFitter.PeptideQuantifier.PeptideDocNode; + foreach (var transitionGroupDocNode in peptideDocNode.TransitionGroups) + { + foreach (var transitionDocNode in transitionGroupDocNode.Transitions) + { + if (OptimizeTransitionSettings.PreserveNonQuantitative && !transitionDocNode.ExplicitQuantitative) + { + continue; + } + + if (msLevel == 1 && !transitionDocNode.IsMs1 || msLevel == 2 && transitionDocNode.IsMs1) + { + continue; + } + var identityPath = new IdentityPath( + calibrationCurveFitter.PeptideQuantifier.PeptideGroup, + calibrationCurveFitter.PeptideQuantifier.PeptideDocNode.Peptide, + transitionGroupDocNode.TransitionGroup, transitionDocNode.Transition); + var quantLimit = ComputeTransitionQuantLimit(calibrationCurveFitter, identityPath); + if (quantLimit != null) + { + singleQuantLimits.Add(quantLimit); + } + } + } + + if (singleQuantLimits.Count == 0) + { + return peptideDocNode; + } + + if (details != null) + { + details.Original = ComputeTransitionsQuantLimit(calibrationCurveFitter, + singleQuantLimits.SelectMany(q => q.TransitionIdentityPaths)); + } + var lowestLimits = new Dictionary() + { + {OptimizeType.LOD, singleQuantLimits.Min(q=>q.QuantLimit.Lod)}, + {OptimizeType.LOQ, singleQuantLimits.Min(q=>q.QuantLimit.Loq)} + }; + if (lowestLimits[optimizeType] == maxConcentration && lowestLimits[otherOptimizeType] < maxConcentration) + { + singleQuantLimits = singleQuantLimits.OrderBy(q => q.QuantLimit.GetQuantLimit(otherOptimizeType)).ToList(); + } + else + { + singleQuantLimits = singleQuantLimits.OrderBy(q => q.QuantLimit.GetQuantLimit(optimizeType)).ToList(); + } + details?.SingleQuantLimits.AddRange(singleQuantLimits); + IList acceptedTransitionIdentityPaths = new List(); + foreach (var quantLimit in singleQuantLimits.Take(OptimizeTransitionSettings.MinimumNumberOfTransitions)) + { + if (acceptedTransitionIdentityPaths.Count > 0 && quantLimit.QuantLimit.GetQuantLimit(optimizeType) >= maxConcentration) + { + break; + } + acceptedTransitionIdentityPaths.Add(quantLimit.TransitionIdentityPaths.Single()); + } + + var optimizedQuantLimit = ComputeTransitionsQuantLimit(calibrationCurveFitter, acceptedTransitionIdentityPaths); + details?.AcceptedQuantLimits.Add(optimizedQuantLimit); + int startIndex = Math.Min(acceptedTransitionIdentityPaths.Count, OptimizeTransitionSettings.MinimumNumberOfTransitions); + var rejectedItems = new List(); + foreach (var quantLimitAndIndex in singleQuantLimits.Skip(startIndex)) + { + var prospectiveQuantLimit = ComputeTransitionsQuantLimit(calibrationCurveFitter, + acceptedTransitionIdentityPaths.Append(quantLimitAndIndex.TransitionIdentityPaths.Single())); + // accept this transition if it helped the result + if (prospectiveQuantLimit.GetQuantLimit(optimizeType) < optimizedQuantLimit.GetQuantLimit(optimizeType)) + { + optimizedQuantLimit = prospectiveQuantLimit; + acceptedTransitionIdentityPaths.Add(quantLimitAndIndex.TransitionIdentityPaths.Single()); + details?.AcceptedQuantLimits.Add(prospectiveQuantLimit); + } + else + { + // save the limits in case we don't have enough limits at the end of this + rejectedItems.Add(prospectiveQuantLimit); + lowestLimits[OptimizeType.LOD] = Math.Min(lowestLimits[OptimizeType.LOD], + prospectiveQuantLimit.GetQuantLimit(OptimizeType.LOD)); + lowestLimits[OptimizeType.LOQ] = Math.Min(lowestLimits[OptimizeType.LOQ], + prospectiveQuantLimit.GetQuantLimit(OptimizeType.LOQ)); + details?.RejectedQuantLimits.Add(prospectiveQuantLimit); + } + } + // if we still don't have enough transitions, for the case where there were transitions at the maximum limit + if (acceptedTransitionIdentityPaths.Count < OptimizeTransitionSettings.MinimumNumberOfTransitions && rejectedItems.Any()) + { + if (lowestLimits[optimizeType] == maxConcentration && + lowestLimits[otherOptimizeType] < maxConcentration) + { + rejectedItems = rejectedItems.OrderBy(item => item.GetQuantLimit(otherOptimizeType)).ToList(); + } + else + { + rejectedItems = rejectedItems.OrderBy(item => item.GetQuantLimit(optimizeType)).ToList(); + } + + int numTransitionsNeeded = OptimizeTransitionSettings.MinimumNumberOfTransitions - acceptedTransitionIdentityPaths.Count; + foreach (var item in rejectedItems.Take(numTransitionsNeeded)) + { + acceptedTransitionIdentityPaths.Add(item.TransitionIdentityPaths.Last()); + } + + optimizedQuantLimit = ComputeTransitionsQuantLimit(calibrationCurveFitter, acceptedTransitionIdentityPaths); + details?.AcceptedQuantLimits.Add(optimizedQuantLimit); + } + + return calibrationCurveFitter.PeptideQuantifier.WithQuantifiableTransitions(acceptedTransitionIdentityPaths).PeptideDocNode; + } + + private TransitionsQuantLimit ComputeTransitionQuantLimit(CalibrationCurveFitter calibrationCurveFitter, + IdentityPath transitionIdentityPath) + { + return ComputeTransitionsQuantLimit(calibrationCurveFitter, + ImmutableList.Singleton(transitionIdentityPath)); + } + private TransitionsQuantLimit ComputeTransitionsQuantLimit(CalibrationCurveFitter calibrationCurveFitter, + IEnumerable identityPaths) + { + var identityPathList = ImmutableList.ValueOf(identityPaths); + var transitionCalibrationCurveFitter = + calibrationCurveFitter.MakeCalibrationCurveFitterWithTransitions(identityPathList); + var quantLimit = ComputeQuantLimits(transitionCalibrationCurveFitter); + return new TransitionsQuantLimit(quantLimit, identityPathList); + } + } +} diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/BootstrapFiguresOfMeritCalculator.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/BootstrapFiguresOfMeritCalculator.cs new file mode 100644 index 0000000000..f46d60e374 --- /dev/null +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/BootstrapFiguresOfMeritCalculator.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MathNet.Numerics.Statistics; +using pwiz.Common.Collections; +using pwiz.Common.SystemUtil; +using ZedGraph; + +namespace pwiz.Skyline.Model.DocSettings.AbsoluteQuantification +{ + public class BootstrapFiguresOfMeritCalculator : Immutable, IFiguresOfMeritCalculator + { + private const double SAME_LOQ_REL_THRESHOLD = .001; + public BootstrapFiguresOfMeritCalculator(double cvThreshold) + { + CvThreshold = cvThreshold; + RandomSeed = 0; + GridSize = 100; + MaxBootstrapIterations = 100; + MinBootstrapIterations = 10; + MinSameLoqCountForAccept = 25; + } + + public double CvThreshold { get; } + public int RandomSeed { get; } + public int GridSize { get; } + public int MaxBootstrapIterations { get; set; } + + public BootstrapFiguresOfMeritCalculator ChangeMaxBootstrapIterations(int value) + { + return ChangeProp(ImClone(this), im => im.MaxBootstrapIterations = value); + } + public int MinBootstrapIterations { get; private set; } + + public BootstrapFiguresOfMeritCalculator ChangeMinBootstrapIterations(int value) + { + return ChangeProp(ImClone(this), im => im.MinBootstrapIterations = value); + } + public int MinSameLoqCountForAccept { get; } + + public FiguresOfMerit GetFiguresOfMerit(CalibrationCurve calibrationCurve, IList standards, IList blanks, + List> bootstrapCurves) + { + var limitOfDetection = ComputeLod(standards); + var limitOfQuantification = ComputeBootstrappedLoq(standards, bootstrapCurves); + limitOfQuantification = Math.Max(limitOfDetection, limitOfQuantification); + var figuresOfMerit = FiguresOfMerit.EMPTY; + if (limitOfDetection < double.MaxValue) + { + figuresOfMerit = figuresOfMerit.ChangeLimitOfDetection(limitOfDetection); + } + + if (limitOfQuantification < double.MaxValue) + { + figuresOfMerit = figuresOfMerit.ChangeLimitOfQuantification(limitOfQuantification); + } + return figuresOfMerit; + } + + public static double ComputeLod(IList points) + { + if (points.Count == 0) + { + return double.MaxValue; + } + ScoredBilinearCurve fit = ScoredBilinearCurve.FromPoints(points); + if (fit == null || double.IsNaN(fit.StdDevBaseline)) + { + return double.MaxValue; + } + var largestConc = points.Max(pt => pt.X); + var lodArea = fit.BaselineHeight + fit.StdDevBaseline; + var smallestNonzeroConc = points.Where(pt => pt.X > 0).Select(pt => pt.X).Append(largestConc).Min(); + double lodConc; + if (fit.Slope == 0) + { + lodConc = largestConc; + } + else + { + lodConc = (lodArea - fit.Intercept) / fit.Slope; + } + + lodConc = Math.Max(smallestNonzeroConc, Math.Min(lodConc, largestConc)); + return lodConc; + } + + public double ComputeBootstrappedLoq(IList points, List> bootstrapCurves) + { + var random = new Random(RandomSeed); + var lod = ComputeLod(points); + if (points.Count == 0 || lod >= double.MaxValue) + { + return double.MaxValue; + } + var maxConcentration = points.Max(pt => pt.X); + var concentrationValues = Enumerable.Range(0, GridSize) + .Select(i => lod + (maxConcentration - lod) * i / (GridSize - 1)).ToList(); + var areaGrid = Enumerable.Range(0, GridSize).Select(i => new RunningStatistics()).ToList(); + int numItersWithSameLoq = 0; + double lastLoq = maxConcentration; + for (int i = 0; i < MaxBootstrapIterations; i++) + { + var p = ComputeBootstrapParams(random, points); + List areaValues = null; + if (bootstrapCurves != null) + { + areaValues = new List(); + } + for (int iConcentration = 0; iConcentration < concentrationValues.Count; iConcentration++) + { + var area = p.CalibrationCurve.GetY(concentrationValues[iConcentration]); + areaGrid[iConcentration].Push(area); + areaValues?.Add(area); + } + + bootstrapCurves?.Add(ImmutableList.ValueOf(concentrationValues.Zip(areaValues, (x, y) => new PointPair(x, y)))); + + if (i > MinBootstrapIterations) + { + var loqCheck = maxConcentration; + for (int iConcentration = concentrationValues.Count - 1; iConcentration >= 0; iConcentration--) + { + if (GetCv(areaGrid[iConcentration]) > CvThreshold) + { + break; + } + else + { + loqCheck = concentrationValues[iConcentration]; + } + } + + if (loqCheck >= 0 && Math.Abs(loqCheck - lastLoq) / loqCheck < SAME_LOQ_REL_THRESHOLD) + { + numItersWithSameLoq++; + } + else + { + numItersWithSameLoq = 0; + } + + lastLoq = loqCheck; + if (numItersWithSameLoq > MinSameLoqCountForAccept) + { + break; + } + } + } + + double loq = double.MaxValue; + for (int iConcentration = concentrationValues.Count - 1; iConcentration >= 0; iConcentration--) + { + var cv = GetCv(areaGrid[iConcentration]); + if (cv > CvThreshold) + { + break; + } + + loq = concentrationValues[iConcentration]; + } + + return loq; + } + private double GetCv(RunningStatistics runningStatistics) + { + if (runningStatistics.Mean <= 0) + { + return CvThreshold * 2; + } + + return runningStatistics.StandardDeviation / runningStatistics.Mean; + } + public ScoredBilinearCurve ComputeBootstrapParams(Random random, IList points) + { + var randomPoints = Enumerable.Range(0, points.Count) + .Select(i => points[random.Next(points.Count)]).ToList(); + return ScoredBilinearCurve.FromPoints(randomPoints); + } + + } +} diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/CalibrationCurve.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/CalibrationCurve.cs index ddbb7af0fd..b4b20e56c9 100644 --- a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/CalibrationCurve.cs +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/CalibrationCurve.cs @@ -30,7 +30,11 @@ public abstract class CalibrationCurve public static readonly CalibrationCurve NO_EXTERNAL_STANDARDS = new Simple(1); - public abstract double? GetY(double? x); + public double? GetY(double? x) + { + return x == null ? (double?) null : GetY(x.Value); + } + public abstract double GetY(double x); /// /// Returns the inverse of GetY @@ -71,10 +75,6 @@ public CalibrationCurveMetrics GetMetrics(IList points) foreach (var point in points) { double? yFitted = GetY(point.X); - if (!yFitted.HasValue) - { - continue; - } yValues.Add(point.Y); residuals.Add(point.Y - yFitted.Value); } @@ -99,7 +99,7 @@ public Linear(double slope, double? intercept) public double Slope { get; } public double? Intercept { get; } - public override double? GetY(double? x) + public override double GetY(double x) { return x * Slope + Intercept.GetValueOrDefault(); } @@ -157,7 +157,7 @@ public Quadratic(double intercept, double slope, double quadraticCoefficient) return (-b + sqrtDiscriminant) / 2 / a; } - public override double? GetY(double? x) + public override double GetY(double x) { return x * x * QuadraticCoefficient + x * Slope + Intercept; } @@ -192,17 +192,10 @@ public double Intercept get { return _linearCalibrationCurve.Intercept.GetValueOrDefault(); } } - public override double? GetY(double? x) + public override double GetY(double x) { - if (x.HasValue) - { - var y = _linearCalibrationCurve.GetY(Math.Log(x.Value)); - if (y.HasValue) - { - return Math.Exp(y.Value); - } - } - return null; + var y = _linearCalibrationCurve.GetY(Math.Log(x)); + return Math.Exp(y); } public override double? GetX(double? y) @@ -267,7 +260,7 @@ public double Intercept public double TurningPoint { get; } - public override double? GetY(double? x) + public override double GetY(double x) { if (x < TurningPoint) { @@ -303,6 +296,11 @@ public override string ToString() { return _linearCalibrationCurve + string.Format(@"; x > {0}", TurningPoint); } + + public Linear GetLinearCalibrationCurve() + { + return _linearCalibrationCurve; + } } public class Simple : CalibrationCurve @@ -313,7 +311,7 @@ public Simple(double slope) } public double Slope { get; } - public override double? GetY(double? x) + public override double GetY(double x) { return x * Slope; } @@ -342,9 +340,9 @@ public Error(string message) } public string ErrorMessage { get; } - public override double? GetY(double? x) + public override double GetY(double x) { - return null; + return 0; } public override double? GetX(double? y) diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/CalibrationCurveFitter.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/CalibrationCurveFitter.cs index ee1f1d747a..5130cc89d2 100644 --- a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/CalibrationCurveFitter.cs +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/CalibrationCurveFitter.cs @@ -20,13 +20,13 @@ using System; using System.Collections.Generic; using System.Linq; -using MathNet.Numerics.Statistics; using pwiz.Common.Collections; using pwiz.Skyline.Model.Databinding.Entities; using pwiz.Skyline.Model.GroupComparison; using pwiz.Skyline.Model.Results; using pwiz.Skyline.Util; using pwiz.Skyline.Util.Extensions; +using ZedGraph; namespace pwiz.Skyline.Model.DocSettings.AbsoluteQuantification { @@ -37,12 +37,13 @@ public class CalibrationCurveFitter = new Dictionary>(); private HashSet _transitionsToQuantifyOn; - private CalibrationCurveFitter(PeptideQuantifier peptideQuantifier, PeptideQuantifier standardPeptideQuantifier, SrmSettings srmSettings) + internal CalibrationCurveFitter(PeptideQuantifier peptideQuantifier, PeptideQuantifier standardPeptideQuantifier, SrmSettings srmSettings) { PeptideQuantifier = peptideQuantifier; StandardPeptideQuantifier = standardPeptideQuantifier; SrmSettings = srmSettings; IsotopologResponseCurve = peptideQuantifier.PeptideDocNode.HasPrecursorConcentrations; + FiguresOfMeritCalculator = QuantificationSettings.GetFiguresOfMeritCalculator(); } public static CalibrationCurveFitter GetCalibrationCurveFitter(Lazy getNormalizationDataFunc, SrmSettings settings, @@ -106,6 +107,8 @@ public QuantificationSettings QuantificationSettings public bool IsotopologResponseCurve { get; set; } public int? SingleBatchReplicateIndex { get; set; } + public bool CombinePointsWithSameConcentration { get; set; } + public IFiguresOfMeritCalculator FiguresOfMeritCalculator { get; set; } public IDictionary GetTransitionQuantities(CalibrationPoint calibrationPoint) { @@ -198,6 +201,11 @@ public IEnumerable EnumerateCalibrationPoints() EnumerateReplicates().Select(replicateIndex => new CalibrationPoint(replicateIndex, labelType))); } + public IEnumerable GetStandardPoints() + { + return GetValidStandardReplicates().Select(GetWeightedPoint).OfType(); + } + public IEnumerable EnumerateLabelTypes() { if (!IsotopologResponseCurve) @@ -212,7 +220,7 @@ public IEnumerable EnumerateReplicates() { if (!SrmSettings.HasResults) { - return new int[0]; + return Array.Empty(); } if (SingleBatchReplicateIndex.HasValue) { @@ -379,7 +387,7 @@ public void GetCalibrationCurveAndMetrics(out CalibrationCurve calibrationCurve, calibrationCurveMetrics = calibrationCurve.GetMetrics(points); } - private CalibrationCurve GetCalibrationCurveAndPoints(List points) + public CalibrationCurve GetCalibrationCurveAndPoints(List points) { if (RegressionFit.NONE.Equals(QuantificationSettings.RegressionFit)) { @@ -390,20 +398,25 @@ private CalibrationCurve GetCalibrationCurveAndPoints(List points return new CalibrationCurve.Simple(1); } + + var allPoints = new List(); foreach (var replicateIndex in GetValidStandardReplicates()) { - double? intensity = GetYValue(replicateIndex); - if (!intensity.HasValue) + WeightedPoint? weightedPoint = GetWeightedPoint(replicateIndex); + if (weightedPoint.HasValue) { - continue; + allPoints.Add(weightedPoint.Value); } + } - double x = GetSpecifiedXValue(replicateIndex).Value; - double weight = QuantificationSettings.RegressionWeighting.GetWeighting(x, intensity.Value); - WeightedPoint weightedPoint = new WeightedPoint(x, intensity.Value, weight); - points.Add(weightedPoint); + if (CombinePointsWithSameConcentration) + { + allPoints = allPoints.GroupBy(pt => pt.X).Select(group => + new WeightedPoint(group.Key, group.Average(pt => pt.Y), group.First().Weight)).ToList(); } + points.AddRange(allPoints); + if (points.Count == 0) { return new CalibrationCurve.Error(QuantificationStrings @@ -412,21 +425,39 @@ private CalibrationCurve GetCalibrationCurveAndPoints(List points return GetCalibrationCurveFromPoints(points); } - public FiguresOfMerit GetFiguresOfMerit(CalibrationCurve calibrationCurve) + public WeightedPoint? GetWeightedPoint(CalibrationPoint calibrationPoint) { - var figuresOfMerit = FiguresOfMerit.EMPTY; - if (calibrationCurve != null) + double? intensity = GetYValue(calibrationPoint); + if (!intensity.HasValue) { - figuresOfMerit = figuresOfMerit.ChangeLimitOfDetection( - QuantificationSettings.LodCalculation.CalculateLod(calibrationCurve, this)); + return null; } - figuresOfMerit = figuresOfMerit.ChangeLimitOfQuantification(GetLimitOfQuantification(calibrationCurve)); - if (!FiguresOfMerit.EMPTY.Equals(figuresOfMerit)) + double x = GetSpecifiedXValue(calibrationPoint).Value; + double weight = QuantificationSettings.RegressionWeighting.GetWeighting(x, intensity.Value); + WeightedPoint weightedPoint = new WeightedPoint(x, intensity.Value, weight); + return weightedPoint; + } + + public FiguresOfMerit GetFiguresOfMerit(CalibrationCurve calibrationCurve, List> bootstrapCurves = null) + { + var measuredResults = SrmSettings.MeasuredResults; + var standardPoints = ImmutableList.ValueOf(GetStandardPoints()); + var blanks = new List(); + foreach (int replicateIndex in EnumerateReplicates()) { - figuresOfMerit = figuresOfMerit.ChangeUnits(QuantificationSettings.Units); + var chromatogramSet = measuredResults.Chromatograms[replicateIndex]; + if (Equals(SampleType.BLANK, chromatogramSet.SampleType)) + { + var peakArea = GetNormalizedPeakArea(new CalibrationPoint(replicateIndex, null)); + if (peakArea.HasValue) + { + blanks.Add(peakArea.Value); + } + } } - return figuresOfMerit; + return FiguresOfMeritCalculator.GetFiguresOfMerit(calibrationCurve, standardPoints, blanks, + bootstrapCurves); } public double? GetTargetIonRatio(TransitionGroupDocNode transitionGroupDocNode) @@ -462,70 +493,6 @@ public FiguresOfMerit GetFiguresOfMerit(CalibrationCurve calibrationCurve) return null; } - public double? GetLimitOfQuantification(CalibrationCurve calibrationCurve) - { - if (!QuantificationSettings.MaxLoqBias.HasValue && !QuantificationSettings.MaxLoqCv.HasValue) - { - return null; - } - - double? bestLoq = null; - var concentrationReplicateLookup = GetStandardConcentrations().ToLookup(entry=>entry.Value, entry=>entry.Key); - foreach (var concentrationReplicate in concentrationReplicateLookup.OrderByDescending(grouping=>grouping.Key)) - { - var peakAreas = new List(); - foreach (var standardIdentifier in concentrationReplicate) - { - double? peakArea = GetNormalizedPeakArea(standardIdentifier); - if (peakArea.HasValue) - { - peakAreas.Add(peakArea.Value); - } - } - if (QuantificationSettings.MaxLoqCv.HasValue) - { - if (peakAreas.Count > 1) - { - double cv = peakAreas.StandardDeviation() / peakAreas.Mean(); - if (double.IsNaN(cv) || double.IsInfinity(cv)) - { - break; - } - if (cv * 100 > QuantificationSettings.MaxLoqCv) - { - break; - } - } - } - if (QuantificationSettings.MaxLoqBias.HasValue) - { - if (calibrationCurve == null) - { - continue; - } - double meanPeakArea = peakAreas.Mean(); - double? backCalculatedConcentration = - GetConcentrationFromXValue(calibrationCurve.GetXValueForLimitOfDetection(meanPeakArea)); - if (!backCalculatedConcentration.HasValue) - { - break; - } - double bias = (concentrationReplicate.Key - backCalculatedConcentration.Value) / - concentrationReplicate.Key; - if (double.IsNaN(bias) || double.IsInfinity(bias)) - { - break; - } - if (Math.Abs(bias * 100) > QuantificationSettings.MaxLoqBias.Value) - { - break; - } - } - bestLoq = concentrationReplicate.Key; - } - return bestLoq; - } - private CalibrationCurve GetCalibrationCurveFromPoints(IList points) { return QuantificationSettings.RegressionFit.Fit(points); @@ -832,6 +799,15 @@ public static bool AnyBatchNames(SrmSettings srmSettings) srmSettings.MeasuredResults.Chromatograms.Any(c => !string.IsNullOrEmpty(c.BatchName)); } + public CalibrationCurveFitter MakeCalibrationCurveFitterWithTransitions(IEnumerable transitionIdentityPaths) + { + return new CalibrationCurveFitter(PeptideQuantifier.WithQuantifiableTransitions(transitionIdentityPaths),null, + SrmSettings) + { + CombinePointsWithSameConcentration = CombinePointsWithSameConcentration + }; + } + /// /// Given a flat list of objects, /// create a dictionary using the elements from as the keys. diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/CalibrationCurveOptions.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/CalibrationCurveOptions.cs index 8bffd19309..0117a8ca49 100644 --- a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/CalibrationCurveOptions.cs +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/CalibrationCurveOptions.cs @@ -16,8 +16,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +using System; +using System.Collections.Generic; using System.Linq; +using System.Xml; +using System.Xml.Schema; using System.Xml.Serialization; +using pwiz.Common.Collections; +using pwiz.Common.SystemUtil; +using pwiz.Skyline.Controls.Graphs; +using pwiz.Skyline.Util; namespace pwiz.Skyline.Model.DocSettings.AbsoluteQuantification { @@ -25,39 +34,215 @@ namespace pwiz.Skyline.Model.DocSettings.AbsoluteQuantification /// Options for the display of the calibration curve window, which get persisted in Settings. /// [XmlRoot("calibration_curve")] - public class CalibrationCurveOptions + public class CalibrationCurveOptions : Immutable, IXmlSerializable { - public CalibrationCurveOptions() + public static readonly CalibrationCurveOptions DEFAULT = new CalibrationCurveOptions() + { + DisplaySampleTypes = ImmutableList.ValueOf( + new[] { SampleType.STANDARD, SampleType.BLANK, SampleType.QC, SampleType.UNKNOWN}), + ShowLegend = true, + ShowSelection = true, + ShowFiguresOfMerit = true, + FontSize = GraphFontSize.NORMAL.PointSize, + LineWidth = 2 + }; + public bool LogXAxis { get; private set; } + + public CalibrationCurveOptions ChangeLogXAxis(bool value) + { + return ChangeProp(ImClone(this), im => im.LogXAxis = value); + } + + public bool LogYAxis { get; private set; } + + public CalibrationCurveOptions ChangeLogYAxis(bool value) + { + return ChangeProp(ImClone(this), im => im.LogYAxis = value); + } + + public ImmutableList DisplaySampleTypes { get; private set; } + + public CalibrationCurveOptions ChangeDisplaySampleTypes(IEnumerable value) { - DisplaySampleTypes = new[] { SampleType.STANDARD.Name, SampleType.BLANK.Name, SampleType.QC.Name, SampleType.UNKNOWN.Name }; - ShowLegend = true; - ShowSelection = true; - ShowFiguresOfMerit = true; + return ChangeProp(ImClone(this), im => im.DisplaySampleTypes = ImmutableList.ValueOf(value)); } + public bool SingleBatch { get; private set; } - public bool LogXAxis { get; set; } - public bool LogYAxis { get; set; } - public string[] DisplaySampleTypes { get; set; } - public bool SingleBatch { get; set; } + public CalibrationCurveOptions ChangeSingleBatch(bool value) + { + return ChangeProp(ImClone(this), im => im.SingleBatch = value); + } public bool DisplaySampleType(SampleType sampleType) { - return DisplaySampleTypes.Contains(sampleType.Name); + return DisplaySampleTypes.Contains(sampleType); } - public bool ShowLegend { get; set; } - public bool ShowSelection { get; set; } - public bool ShowFiguresOfMerit { get; set; } + public bool ShowLegend { get; private set; } - public void SetDisplaySampleType(SampleType sampleType, bool display) + public CalibrationCurveOptions ChangeShowLegend(bool value) + { + return ChangeProp(ImClone(this), im => im.ShowLegend = value); + } + public bool ShowSelection { get; private set; } + + public CalibrationCurveOptions ChangeShowSelection(bool value) + { + return ChangeProp(ImClone(this), im => im.ShowSelection = value); + } + public bool ShowFiguresOfMerit { get; private set; } + + public CalibrationCurveOptions ChangeShowFiguresOfMerit(bool value) + { + return ChangeProp(ImClone(this), im => im.ShowFiguresOfMerit = value); + } + + public bool ShowBootstrapCurves { get; private set; } + + public CalibrationCurveOptions ChangeShowBootstrapCurves(bool value) + { + return ChangeProp(ImClone(this), im => im.ShowBootstrapCurves = value); + } + + public float LineWidth { get; private set; } + + public CalibrationCurveOptions ChangeLineWidth(float value) + { + return ChangeProp(ImClone(this), im => im.LineWidth = value); + } + public float FontSize { get; private set; } + + public CalibrationCurveOptions ChangeFontSize(float value) + { + return ChangeProp(ImClone(this), im => im.FontSize = value); + } + + public CalibrationCurveOptions SetDisplaySampleType(SampleType sampleType, bool display) { if (display) { - DisplaySampleTypes = DisplaySampleTypes.Concat(new[] {sampleType.Name}).Distinct().ToArray(); + return ChangeDisplaySampleTypes(DisplaySampleTypes.Concat(new[] { sampleType }).Distinct() + .ToArray()); } else { - DisplaySampleTypes = DisplaySampleTypes.Except(new[] {sampleType.Name}).ToArray(); + return ChangeDisplaySampleTypes(DisplaySampleTypes.Except(new[] {sampleType}).ToArray()); + } + } + + #region serialization + + private enum EL + { + display_sample_type, + } + + private enum ATTR + { + log_x_axis, + log_y_axis, + single_batch, + show_legend, + show_selection, + show_figures_of_merit, + show_bootstrap_curves, + line_width, + font_size + } + + private CalibrationCurveOptions() + { + } + + public XmlSchema GetSchema() + { + return null; + } + + public void ReadXml(XmlReader reader) + { + if (DisplaySampleTypes != null) + { + throw new InvalidOperationException(); + } + + LogXAxis = reader.GetBoolAttribute(ATTR.log_x_axis); + LogYAxis = reader.GetBoolAttribute(ATTR.log_y_axis); + SingleBatch = reader.GetBoolAttribute(ATTR.single_batch); + ShowLegend = reader.GetBoolAttribute(ATTR.show_legend); + ShowSelection = reader.GetBoolAttribute(ATTR.show_selection); + ShowFiguresOfMerit = reader.GetBoolAttribute(ATTR.show_legend); + ShowBootstrapCurves = reader.GetBoolAttribute(ATTR.show_bootstrap_curves); + LineWidth = reader.GetFloatAttribute(ATTR.line_width, 1); + FontSize = reader.GetFloatAttribute(ATTR.font_size, GraphFontSize.NORMAL.PointSize); + bool isEmpty = reader.IsEmptyElement; + reader.Read(); + var displaySampleTypes = new List(); + if (!isEmpty) + { + while (reader.IsStartElement(EL.display_sample_type)) + { + var sampleType = SampleType.FromName(reader.ReadElementContentAsString()); + if (sampleType != null) + { + displaySampleTypes.Add(sampleType); + } + } + } + DisplaySampleTypes = ImmutableList.ValueOf(displaySampleTypes.Distinct()); + } + + public void WriteXml(XmlWriter writer) + { + writer.WriteAttribute(ATTR.log_x_axis, LogXAxis); + writer.WriteAttribute(ATTR.log_y_axis, LogYAxis); + writer.WriteAttribute(ATTR.single_batch, SingleBatch); + writer.WriteAttribute(ATTR.show_legend, ShowLegend); + writer.WriteAttribute(ATTR.show_selection, ShowSelection); + writer.WriteAttribute(ATTR.show_figures_of_merit, ShowFiguresOfMerit); + writer.WriteAttribute(ATTR.show_bootstrap_curves, ShowBootstrapCurves); + writer.WriteAttribute(ATTR.font_size, FontSize); + writer.WriteAttribute(ATTR.line_width, LineWidth); + foreach (var displaySampleType in DisplaySampleTypes) + { + writer.WriteElementString(EL.display_sample_type, displaySampleType.Name); + } + } + + #endregion + + protected bool Equals(CalibrationCurveOptions other) + { + return LogXAxis == other.LogXAxis && LogYAxis == other.LogYAxis && + DisplaySampleTypes.Equals(other.DisplaySampleTypes) && SingleBatch == other.SingleBatch && + ShowLegend == other.ShowLegend && ShowSelection == other.ShowSelection && + ShowFiguresOfMerit == other.ShowFiguresOfMerit && ShowBootstrapCurves == other.ShowBootstrapCurves && + Equals(FontSize, other.FontSize) && Equals(LineWidth, other.LineWidth); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CalibrationCurveOptions)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = LogXAxis.GetHashCode(); + hashCode = (hashCode * 397) ^ LogYAxis.GetHashCode(); + hashCode = (hashCode * 397) ^ DisplaySampleTypes.GetHashCode(); + hashCode = (hashCode * 397) ^ SingleBatch.GetHashCode(); + hashCode = (hashCode * 397) ^ ShowLegend.GetHashCode(); + hashCode = (hashCode * 397) ^ ShowSelection.GetHashCode(); + hashCode = (hashCode * 397) ^ ShowFiguresOfMerit.GetHashCode(); + hashCode = (hashCode * 397) ^ ShowBootstrapCurves.GetHashCode(); + hashCode = (hashCode * 397) ^ LineWidth.GetHashCode(); + hashCode = (hashCode * 397) ^ FontSize.GetHashCode(); + return hashCode; } } } diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/FiguresOfMerit.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/FiguresOfMerit.cs index f156d3c30a..9fd4699cb8 100644 --- a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/FiguresOfMerit.cs +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/FiguresOfMerit.cs @@ -16,6 +16,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -55,14 +57,14 @@ public FiguresOfMerit ChangeUnits(string units) public override string ToString() { var parts = new List(); - string format = string.IsNullOrEmpty(Units) ? Formats.CalibrationCurve : Formats.Concentration; + bool unitsSpecified = !string.IsNullOrEmpty(Units); if (LimitOfDetection.HasValue) { - parts.Add(QuantificationStrings.FiguresOfMerit_ToString_LOD__ + LimitOfDetection.Value.ToString(format)); + parts.Add(QuantificationStrings.FiguresOfMerit_ToString_LOD__ + FormatValue(LimitOfDetection.Value, unitsSpecified)); } if (LimitOfQuantification.HasValue) { - parts.Add(QuantificationStrings.FiguresOfMerit_ToString_LOQ__ + LimitOfQuantification.Value.ToString(format)); + parts.Add(QuantificationStrings.FiguresOfMerit_ToString_LOQ__ + FormatValue(LimitOfQuantification.Value, unitsSpecified)); } if (!parts.Any()) { @@ -74,5 +76,16 @@ public override string ToString() } return TextUtil.SpaceSeparate(parts); } + + public static string FormatValue(double value, bool unitsSpecified) + { + // If the units have been specified, and the value is not too large, then use Formats.Concentration + if (unitsSpecified && Math.Abs(value) < 1e8) + { + return value.ToString(Formats.Concentration); + } + // Otherwise, use scientific format + return value.ToString(Formats.CalibrationCurve); + } } } diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/IFiguresOfMeritCalculator.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/IFiguresOfMeritCalculator.cs new file mode 100644 index 0000000000..baae23fe04 --- /dev/null +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/IFiguresOfMeritCalculator.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MathNet.Numerics.Statistics; +using pwiz.Common.Collections; +using ZedGraph; + +namespace pwiz.Skyline.Model.DocSettings.AbsoluteQuantification +{ + public interface IFiguresOfMeritCalculator + { + public FiguresOfMerit GetFiguresOfMerit(CalibrationCurve calibrationCurve, IList standards, + IList blanks, List> bootstrapCurves); + } + + public class SimpleFiguresOfMeritCalculator : IFiguresOfMeritCalculator + { + public SimpleFiguresOfMeritCalculator(LodCalculation lodCalculation, double? maxLoqCv, double? maxLoqBias) + { + LodCalculation = lodCalculation; + MaxLoqCv = maxLoqCv; + MaxLoqBias = maxLoqBias; + } + + public LodCalculation LodCalculation { get; } + public double? MaxLoqCv { get; } + public double? MaxLoqBias { get; } + + public FiguresOfMerit GetFiguresOfMerit(CalibrationCurve calibrationCurve, IList standards, IList blanks, + List> bootstrapCurves) + { + FiguresOfMerit figuresOfMerit = FiguresOfMerit.EMPTY; + if (LodCalculation != null) + { + figuresOfMerit = + figuresOfMerit.ChangeLimitOfDetection( + LodCalculation.CalculateLod(new LodCalculation.LodCalculationArgs(calibrationCurve, standards, blanks))); + } + + figuresOfMerit = figuresOfMerit.ChangeLimitOfQuantification(CalculateLoq(calibrationCurve, standards)); + return figuresOfMerit; + } + + public double? CalculateLoq(CalibrationCurve calibrationCurve, IList standards) + { + if (!MaxLoqCv.HasValue && !MaxLoqBias.HasValue) + { + return null; + } + double? bestLoq = null; + foreach (var concentrationGroup in standards.GroupBy(pt => pt.X).OrderByDescending(grouping => grouping.Key)) + { + var peakAreas = concentrationGroup.Select(pt => pt.Y).ToList(); + if (MaxLoqCv.HasValue) + { + if (peakAreas.Count > 1) + { + double cv = peakAreas.StandardDeviation() / peakAreas.Mean(); + if (double.IsNaN(cv) || double.IsInfinity(cv)) + { + break; + } + if (cv > MaxLoqCv) + { + break; + } + } + } + if (MaxLoqBias.HasValue) + { + if (calibrationCurve == null) + { + continue; + } + double meanPeakArea = peakAreas.Mean(); + double? backCalculatedConcentration = calibrationCurve.GetXValueForLimitOfDetection(meanPeakArea); + if (!backCalculatedConcentration.HasValue) + { + break; + } + double bias = (concentrationGroup.Key - backCalculatedConcentration.Value) / + concentrationGroup.Key; + if (double.IsNaN(bias) || double.IsInfinity(bias)) + { + break; + } + if (Math.Abs(bias) > MaxLoqBias.Value) + { + break; + } + } + bestLoq = concentrationGroup.Key; + } + return bestLoq; + } + } +} diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/LodCalculation.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/LodCalculation.cs index 685fe9ed2d..729313400d 100644 --- a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/LodCalculation.cs +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/LodCalculation.cs @@ -28,24 +28,27 @@ namespace pwiz.Skyline.Model.DocSettings.AbsoluteQuantification public class LodCalculation : LabeledValues { public static readonly LodCalculation NONE = new LodCalculation(@"none", - () => QuantificationStrings.LodCalculation_NONE_None, (curve, fitter) => null); + () => QuantificationStrings.LodCalculation_NONE_None, args => null); public static readonly LodCalculation TURNING_POINT = new LodCalculation(@"turning_point", () => QuantificationStrings.LodCalculation_TURNING_POINT_Bilinear_turning_point, CalculateLodFromTurningPoint); public static readonly LodCalculation BLANK_PLUS_2SD = new LodCalculation(@"blank_plus_2_sd", () => QuantificationStrings.LodCalculation_BLANK_PLUS_2SD_Blank_plus_2___SD, - (curve, fitter)=>BlankPlusSdMultiple(curve, fitter, 2.0)); + args=>BlankPlusSdMultiple(args, 2.0)); public static readonly LodCalculation BLANK_PLUS_3SD = new LodCalculation(@"blank_plus_3_sd", () => QuantificationStrings.LodCalculation_BLANK_PLUS_3SD_Blank_plus_3___SD, - (curve, fitter)=>BlankPlusSdMultiple(curve, fitter, 3.0)); + args=>BlankPlusSdMultiple(args, 3.0)); + + public static readonly LodCalculation TURNING_POINT_STDERR = new LodCalculation(@"turning_point_stderr", + () => QuantificationStrings.LodCalculation_TURNING_POINT_STDERR_Bilinear_turning_point_standard_error, CalculateLodFromTurningPointWithStdErr); public static readonly ImmutableList ALL = ImmutableList.ValueOf(new[] { - NONE, BLANK_PLUS_2SD, BLANK_PLUS_3SD, TURNING_POINT + NONE, BLANK_PLUS_2SD, BLANK_PLUS_3SD, TURNING_POINT, TURNING_POINT_STDERR }); - private readonly Func _calculateLodFunc; + private readonly Func _calculateLodFunc; - private LodCalculation(string name, Func getLabelFunc, Func calculateLodFunc) : + private LodCalculation(string name, Func getLabelFunc, Func calculateLodFunc) : base(name, getLabelFunc) { _calculateLodFunc = calculateLodFunc; @@ -66,39 +69,34 @@ public static LodCalculation Parse(string name) } - public double? CalculateLod(CalibrationCurve calibrationCurve, CalibrationCurveFitter calibrationCurveFitter) + public double? CalculateLod(LodCalculationArgs args) { - return _calculateLodFunc(calibrationCurve, calibrationCurveFitter); + return _calculateLodFunc(args); } - public static double? CalculateLodFromTurningPoint(CalibrationCurve calibrationCurve, - CalibrationCurveFitter fitter) + public static double? CalculateLodFromTurningPoint(LodCalculationArgs args) { - return (calibrationCurve as CalibrationCurve.Bilinear)?.TurningPoint; - } - - public static double? BlankPlusSdMultiple(CalibrationCurve calibrationCurve, CalibrationCurveFitter fitter, double sdMultiple) - { - List blankPeakAreas = new List(); - var measuredResults = fitter.SrmSettings.MeasuredResults; - if (measuredResults == null) + if (args.CalibrationCurve is CalibrationCurve.Bilinear bilinear) { - return null; + return bilinear.TurningPoint; } - for (int iReplicate = 0; iReplicate < measuredResults.Chromatograms.Count; iReplicate++) + + if (args.CalibrationCurve is CalibrationCurve.Linear) { - var chromatogramSet = measuredResults.Chromatograms[iReplicate]; - if (!SampleType.BLANK.Equals(chromatogramSet.SampleType)) - { - continue; - } - double? peakArea = fitter.GetNormalizedPeakArea(new CalibrationPoint(iReplicate, null)); - if (!peakArea.HasValue) - { - continue; - } - blankPeakAreas.Add(peakArea.Value); + return args.Standards.Min(pt => pt.X); } + + return null; + } + + public static double? CalculateLodFromTurningPointWithStdErr(LodCalculationArgs args) + { + return BootstrapFiguresOfMeritCalculator.ComputeLod(args.Standards); + } + + public static double? BlankPlusSdMultiple(LodCalculationArgs args, double sdMultiple) + { + var blankPeakAreas = args.Blanks; if (!blankPeakAreas.Any()) { return null; @@ -112,7 +110,20 @@ public static LodCalculation Parse(string name) { return null; } - return calibrationCurve.GetXValueForLimitOfDetection(meanPlusSd); + return args.CalibrationCurve.GetXValueForLimitOfDetection(meanPlusSd); + } + public class LodCalculationArgs + { + public LodCalculationArgs(CalibrationCurve calibrationCurve, IEnumerable standards, + IEnumerable blanks) + { + CalibrationCurve = calibrationCurve; + Standards = ImmutableList.ValueOf(standards); + Blanks = ImmutableList.ValueOf(blanks); + } + public CalibrationCurve CalibrationCurve { get; } + public ImmutableList Standards { get; } + public ImmutableList Blanks { get; } } } } diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/OptimizeTransitionDetails.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/OptimizeTransitionDetails.cs new file mode 100644 index 0000000000..8cee70cb5f --- /dev/null +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/OptimizeTransitionDetails.cs @@ -0,0 +1,147 @@ +using System; +using pwiz.Common.SystemUtil; +using System.Collections.Generic; +using System.Linq; +using pwiz.Skyline.Model.GroupComparison; + +namespace pwiz.Skyline.Model.DocSettings.AbsoluteQuantification +{ + public class OptimizeTransitionDetails + { + public OptimizeTransitionDetails() + { + SingleQuantLimits = new List(); + AcceptedQuantLimits = new List(); + RejectedQuantLimits = new List(); + } + public TransitionsQuantLimit Original { get; set; } + public List SingleQuantLimits { get; } + public List AcceptedQuantLimits { get; } + public List RejectedQuantLimits { get; } + + public TransitionsQuantLimit Optimized + { + get + { + return AcceptedQuantLimits.OrderByDescending(q => q.TransitionIdentityPaths.Count).FirstOrDefault(); + } + } + } + + public class OptimizeTransitionSettings : Immutable + { + public static readonly OptimizeTransitionSettings DEFAULT = new OptimizeTransitionSettings + { + OptimizeType = OptimizeType.LOQ, + MinimumNumberOfTransitions = 5, + }; + + private static OptimizeTransitionSettings _globalSettings = DEFAULT; + public static OptimizeTransitionSettings GlobalSettings + { + get + { + return _globalSettings; + } + set + { + if (!Equals(value, _globalSettings)) + { + _globalSettings = value; + GlobalSettingsChange?.Invoke(); + } + } + } + public static event Action GlobalSettingsChange; + + + private OptimizeTransitionSettings() + { + + } + public int RandomSeed { get; private set; } + + public OptimizeTransitionSettings ChangeRandomSeed(int value) + { + return ChangeProp(ImClone(this), im => im.RandomSeed = value); + } + + public int MinimumNumberOfTransitions { get; private set; } + + public OptimizeTransitionSettings ChangeMinimumNumberOfTransitions(int value) + { + return ChangeProp(ImClone(this), im => im.MinimumNumberOfTransitions = value); + } + + public OptimizeType OptimizeType { get; private set; } + + public OptimizeTransitionSettings ChangeOptimizeType(OptimizeType value) + { + return ChangeProp(ImClone(this), im => im.OptimizeType = value); + } + + public bool PreserveNonQuantitative { get; private set; } + + public OptimizeTransitionSettings ChangePreserveNonQuantitative(bool value) + { + return ChangeProp(ImClone(this), im => im.PreserveNonQuantitative = value); + } + + public bool CombinePointsWithSameConcentration { get; private set; } + + public OptimizeTransitionSettings ChangeCombinePointsWithSameConcentration(bool value) + { + return ChangeProp(ImClone(this), im => im.CombinePointsWithSameConcentration = value); + } + protected bool Equals(OptimizeTransitionSettings other) + { + return RandomSeed == other.RandomSeed && + MinimumNumberOfTransitions == other.MinimumNumberOfTransitions && + OptimizeType.Equals(other.OptimizeType) && + PreserveNonQuantitative == other.PreserveNonQuantitative && + CombinePointsWithSameConcentration == other.CombinePointsWithSameConcentration; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((OptimizeTransitionSettings)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = RandomSeed; + hashCode = (hashCode * 397) ^ MinimumNumberOfTransitions; + hashCode = (hashCode * 397) ^ OptimizeType.GetHashCode(); + hashCode = (hashCode * 397) ^ PreserveNonQuantitative.GetHashCode(); + hashCode = (hashCode * 397) ^ CombinePointsWithSameConcentration.GetHashCode(); + return hashCode; + } + } + + public CalibrationCurveFitter GetCalibrationCurveFitter(PeptideQuantifier peptideQuantifier, SrmSettings srmSettings) + { + return new CalibrationCurveFitter(peptideQuantifier, null, srmSettings) + { + CombinePointsWithSameConcentration = CombinePointsWithSameConcentration + }; + } + + public QuantificationSettings GetQuantificationSettings(SrmSettings settings) + { + var quantificationSettings = settings.PeptideSettings.Quantification; + quantificationSettings = quantificationSettings.ChangeLodCalculation(LodCalculation.TURNING_POINT_STDERR) + .ChangeMaxLoqBias(null).ChangeRegressionFit(RegressionFit.BILINEAR); + if (!quantificationSettings.MaxLoqCv.HasValue) + { + quantificationSettings = quantificationSettings.ChangeMaxLoqCv(20); + } + + return quantificationSettings; + } + } +} diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/OptimizeType.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/OptimizeType.cs new file mode 100644 index 0000000000..093bfb11f9 --- /dev/null +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/OptimizeType.cs @@ -0,0 +1,36 @@ +/* + * Original author: Nicholas Shulman , + * MacCoss Lab, Department of Genome Sciences, UW + * + * Copyright 2023 University of Washington - Seattle, WA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace pwiz.Skyline.Model.DocSettings.AbsoluteQuantification +{ + public class OptimizeType + { + public static readonly OptimizeType LOD = new OptimizeType(@"lod"); + public static readonly OptimizeType LOQ = new OptimizeType(@"loq"); + private OptimizeType(string name) + { + Name = name; + } + + public string Name { get; } + public override string ToString() + { + return Name; + } + } +} diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/QuantificationSettings.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/QuantificationSettings.cs index a5d32f58d7..c24fbf683c 100644 --- a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/QuantificationSettings.cs +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/QuantificationSettings.cs @@ -240,6 +240,16 @@ public static QuantificationSettings Deserialize(XmlReader reader) { return reader.Deserialize(new QuantificationSettings()); } + + public IFiguresOfMeritCalculator GetFiguresOfMeritCalculator() + { + if (RegressionFit.BILINEAR == RegressionFit && MaxLoqCv.HasValue && !MaxLoqBias.HasValue) + { + return new BootstrapFiguresOfMeritCalculator(MaxLoqCv.Value / 100); + } + + return new SimpleFiguresOfMeritCalculator(LodCalculation, MaxLoqCv / 100, MaxLoqBias / 100); + } #endregion } } diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/QuantificationStrings.Designer.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/QuantificationStrings.Designer.cs index b313e5f7e0..862bacd80e 100644 --- a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/QuantificationStrings.Designer.cs +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/QuantificationStrings.Designer.cs @@ -403,6 +403,33 @@ public static string CalibrationForm_MakeExcludeStandardMenuItem_Include_Standar } } + /// + /// Looks up a localized string similar to Bootstrap Curve. + /// + public static string CalibrationGraphControl_DoUpdate_Bootstrap_Curve { + get { + return ResourceManager.GetString("CalibrationGraphControl_DoUpdate_Bootstrap_Curve", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Limit of Detection. + /// + public static string CalibrationGraphControl_DoUpdate_Limit_of_Detection { + get { + return ResourceManager.GetString("CalibrationGraphControl_DoUpdate_Limit_of_Detection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lower Limit of Quantification. + /// + public static string CalibrationGraphControl_DoUpdate_Lower_Limit_of_Quantification { + get { + return ResourceManager.GetString("CalibrationGraphControl_DoUpdate_Lower_Limit_of_Quantification", resourceCulture); + } + } + /// /// Looks up a localized string similar to Concentration Ratio. /// @@ -458,7 +485,7 @@ public static string LodCalculation_NONE_None { } /// - /// Looks up a localized string similar to Bilinear turning point. + /// Looks up a localized string similar to Bilinear turning point (obsolete). /// public static string LodCalculation_TURNING_POINT_Bilinear_turning_point { get { @@ -466,6 +493,15 @@ public static string LodCalculation_TURNING_POINT_Bilinear_turning_point { } } + /// + /// Looks up a localized string similar to Bilinear turning point standard error. + /// + public static string LodCalculation_TURNING_POINT_STDERR_Bilinear_turning_point_standard_error { + get { + return ResourceManager.GetString("LodCalculation_TURNING_POINT_STDERR_Bilinear_turning_point_standard_error", resourceCulture); + } + } + /// /// Looks up a localized string similar to All. /// @@ -529,6 +565,15 @@ public static string NormalizeOption_TOTAL_Total { } } + /// + /// Looks up a localized string similar to Optimize transitions. + /// + public static string OptimizeDocumentTransitionsForm_Apply_Optimize_transitions { + get { + return ResourceManager.GetString("OptimizeDocumentTransitionsForm_Apply_Optimize_transitions", resourceCulture); + } + } + /// /// Looks up a localized string similar to Normalized Area: {0}. /// @@ -538,6 +583,15 @@ public static string QuantificationResult_ToString_Normalized_Area___0_ { } } + /// + /// Looks up a localized string similar to LOD: {0} LLOQ: {1}. + /// + public static string QuantLimit_ToString_LOD___0__LLOQ___1_ { + get { + return ResourceManager.GetString("QuantLimit_ToString_LOD___0__LLOQ___1_", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0} Ratio To {1}. /// diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/QuantificationStrings.resx b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/QuantificationStrings.resx index 5d10a50ffd..9975e1780e 100644 --- a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/QuantificationStrings.resx +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/QuantificationStrings.resx @@ -256,7 +256,7 @@ None - Bilinear turning point + Bilinear turning point (obsolete) Blank plus 2 * SD @@ -335,6 +335,25 @@ Default + + Bilinear turning point standard error + + + Bootstrap Curve + + + Limit of Detection + + + Lower Limit of Quantification + + + Optimize transitions + + + LOD: {0} LLOQ: {1} + "LOD" stands for "Limit of detection" and "LLOQ" stands for "Lower limit of quantification" + {0} ({1}) Example: Standard (MyMolecule) diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/RegressionFit.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/RegressionFit.cs index d024082b2a..f812022b26 100644 --- a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/RegressionFit.cs +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/RegressionFit.cs @@ -21,7 +21,6 @@ using System.Linq; using MathNet.Numerics.LinearRegression; using pwiz.Common.Collections; -using pwiz.Common.DataAnalysis; using pwiz.Common.SystemUtil; using pwiz.Skyline.Model.Databinding.Entities; @@ -40,7 +39,8 @@ public abstract class RegressionFit : LabeledValues public static readonly RegressionFit QUADRATIC = new QuadraticFit(); - public static readonly RegressionFit BILINEAR = new BilinearFit(); + //public static readonly RegressionFit LEGACY_BILINEAR = new LegacyBilinearFit(); + public static readonly RegressionFit BILINEAR = new BilinearRegressionFit(); public static readonly RegressionFit LINEAR_IN_LOG_SPACE = new LinearInLogSpace(); public static readonly ImmutableList All = ImmutableList.ValueOf(new[] @@ -136,80 +136,6 @@ protected override CalibrationCurve FitPoints(IList points) } } - private class BilinearFit : RegressionFit - { - public BilinearFit() : base(@"bilinear", () => QuantificationStrings.RegressionFit_BILINEAR_Bilinear) - { - } - - protected override CalibrationCurve FitPoints(IList weightedPoints) - { - double? bestLod = null; - double bestScore = double.MaxValue; - var xValues = weightedPoints.Select(pt => pt.X).Distinct().OrderBy(x => x).ToArray(); - for (int i = 0; i < xValues.Length - 1; i++) - { - var simplexConstant = new NelderMeadSimplex.SimplexConstant((xValues[i] + xValues[i + 1]) / 2, - (xValues[i + 1] - xValues[i]) / 4); - var regressionResult = NelderMeadSimplex.Regress(new[] { simplexConstant }, 0, 50, - constants => LodObjectiveFunction(constants[0], weightedPoints)); - if (regressionResult.ErrorValue < bestScore) - { - bestLod = regressionResult.Constants[0]; - bestScore = regressionResult.ErrorValue; - } - } - if (!bestLod.HasValue) - { - return LinearFit(weightedPoints); - } - return GetCalibrationCurveWithLod(bestLod.Value, weightedPoints); - } - private static CalibrationCurve.Bilinear GetCalibrationCurveWithLod(double lod, IList weightedPoints) - { - var linearPoints = weightedPoints.Select(pt => pt.X > lod ? pt : new WeightedPoint(lod, pt.Y, pt.Weight)).ToArray(); - if (linearPoints.Select(p => p.X).Distinct().Count() <= 1) - { - return null; - } - - try - { - var linearCalibrationCurve = LinearFit(linearPoints) as CalibrationCurve.Linear; - if (linearCalibrationCurve == null) - { - return null; - } - return new CalibrationCurve.Bilinear(linearCalibrationCurve, lod); - } - catch (Exception) - { - return null; - } - } - - /// - /// Optimization function used when doing NelderMeadSimplex to find the best Limit of Detection. - /// - private static double LodObjectiveFunction(double lod, IList weightedPoints) - { - CalibrationCurve.Bilinear calibrationCurve = GetCalibrationCurveWithLod(lod, weightedPoints); - if (calibrationCurve == null) - { - return double.MaxValue; - } - double totalDelta = 0; - double totalWeight = 0; - foreach (var pt in weightedPoints) - { - double delta = pt.Y - calibrationCurve.GetY(pt.X).Value; - totalWeight += pt.Weight; - totalDelta += pt.Weight * delta * delta; - } - return totalDelta / totalWeight; - } - } - private class LinearInLogSpace : RegressionFit { public LinearInLogSpace() : base(@"linear_in_log_space", () => AbsoluteQuantificationResources.LinearInLogSpace_Label_Linear_in_Log_Space) @@ -233,5 +159,6 @@ protected WeightedPoint LogOfPoint(WeightedPoint pt) return CalibrationCurve.LinearInLogSpace.LogOfPoint(pt); } } + } } diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/SampleType.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/SampleType.cs index bc9be894fa..bba3421423 100644 --- a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/SampleType.cs +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/SampleType.cs @@ -21,6 +21,7 @@ using System.Drawing; using System.Globalization; using System.Linq; +using pwiz.Common.Collections; using pwiz.Common.SystemUtil; using pwiz.Skyline.Model.Databinding; using SymbolType=ZedGraph.SymbolType; @@ -49,17 +50,19 @@ private SampleType(string name, Func getLabelFunc, Color color, SymbolTy SymbolType = symbolType; } + public static readonly ImmutableList ALL = ImmutableList.ValueOf(new[] + { + UNKNOWN, + STANDARD, + QC, + SOLVENT, + BLANK, + DOUBLE_BLANK + }); + public static IList ListSampleTypes() { - return new[] - { - UNKNOWN, - STANDARD, - QC, - SOLVENT, - BLANK, - DOUBLE_BLANK - }; + return ALL; } public static SampleType FromName(string name) diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/ScoredBilinearCurve.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/ScoredBilinearCurve.cs new file mode 100644 index 0000000000..e9724bf16c --- /dev/null +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/ScoredBilinearCurve.cs @@ -0,0 +1,151 @@ +using System.Collections.Generic; +using System.Linq; +using pwiz.Skyline.Util; + +namespace pwiz.Skyline.Model.DocSettings.AbsoluteQuantification +{ + public class ScoredBilinearCurve + { + public CalibrationCurve CalibrationCurve { get; private set; } + public double StdDevBaseline { get; private set; } + + public double Slope + { + get + { + return (CalibrationCurve as CalibrationCurve.Bilinear)?.Slope ?? 0; + } + } + + public double Intercept + { + get + { + return (CalibrationCurve as CalibrationCurve.Bilinear)?.Intercept ?? 0; + } + } + + public double BaselineHeight + { + get + { + if (CalibrationCurve is CalibrationCurve.Bilinear bilinear) + { + return bilinear.GetY(bilinear.TurningPoint); + } + + return 0; + } + } + + public double Error + { + get; private set; + } + + public static ScoredBilinearCurve FromPoints(IList points) + { + return FromCalibrationCurve(RegressionFit.BILINEAR.Fit(points), points); + } + + public static ScoredBilinearCurve FromCalibrationCurve(CalibrationCurve calibrationCurve, IList points) + { + if (calibrationCurve == null) + { + return null; + } + var fit = new ScoredBilinearCurve {CalibrationCurve = calibrationCurve}; + IList reweightedPoints = points; + if (calibrationCurve is CalibrationCurve.Bilinear bilinear) + { + var baselinePoints = points.Where(pt => pt.X <= bilinear.TurningPoint).ToList(); + var baselineStats = new Statistics(baselinePoints.Select(pt => pt.Y)); + if (baselineStats.Length == 0) + { + fit.StdDevBaseline = 0; + } + else + { + var minBaselineWeight = baselinePoints.Min(pt => pt.Weight); + reweightedPoints = points.Where(pt => pt.X > bilinear.TurningPoint) + .Concat(baselinePoints.Select(pt => new WeightedPoint(pt.X, pt.Y, minBaselineWeight))).ToList(); + } + } + else + { + fit.StdDevBaseline = 0; + } + + fit.Error = CalculateError(calibrationCurve, reweightedPoints); + + return fit; + } + + public static ScoredBilinearCurve WithOffset(double xOffset, IList points) + { + if (points.Count == 0) + { + return FlatBilinearCurve(points); + } + var linearPoints = points.Where(pt => pt.X > xOffset).ToList(); + if (linearPoints.Select(pt=>pt.X).Distinct().Count() < 2) + { + return FlatBilinearCurve(points); + } + var linearCurve = RegressionFit.LINEAR.Fit(linearPoints) as CalibrationCurve.Linear; + if (linearCurve == null || linearCurve.Slope <= 0) + { + return FlatBilinearCurve(points); + } + + if (linearPoints.Count == points.Count) + { + return FromCalibrationCurve(linearCurve, points); + } + + var baselinePoints = points.Where(pt => pt.X <= xOffset).ToList(); + var baselineHeight = baselinePoints.Average(pt => pt.Y); + var turningPoint = linearCurve.GetX(baselineHeight).GetValueOrDefault(); + if (turningPoint < 0) + { + return FlatBilinearCurve(points); + } + + var curve = new CalibrationCurve.Bilinear(linearCurve, turningPoint); + return FromCalibrationCurve(curve, points); + } + + private static ScoredBilinearCurve FlatBilinearCurve(IList points) + { + if (points.Count == 0) + { + return FromCalibrationCurve(new CalibrationCurve.Linear(0, 0), points); + } + + var baselineHeight = points.Average(pt => pt.Y); + var linearCurve = new CalibrationCurve.Linear(0, baselineHeight); + var bilinearCurve = new CalibrationCurve.Bilinear(linearCurve, points.Max(pt => pt.X)); + return FromCalibrationCurve(bilinearCurve, points.Select(pt => new WeightedPoint(pt.X, pt.Y, 1)).ToList()); + } + + private static double CalculateError(CalibrationCurve calibrationCurve, IList points) + { + double totalError = 0; + double totalWeight = 0; + foreach (var point in points) + { + double expected = calibrationCurve.GetY(point.X); + double difference = expected - point.Y; + totalError += difference * difference * point.Weight; + totalWeight += point.Weight; + } + + if (totalWeight == 0) + { + return double.MaxValue; + } + return totalError / totalWeight; + } + + } +} diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/TransitionsQuantLimit.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/TransitionsQuantLimit.cs new file mode 100644 index 0000000000..f50691ea42 --- /dev/null +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/TransitionsQuantLimit.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using pwiz.Common.Collections; +using pwiz.Skyline.Model.Hibernate; + +namespace pwiz.Skyline.Model.DocSettings.AbsoluteQuantification +{ + public class QuantLimit : IFormattable, IComparable + { + public QuantLimit(double lod, double loq) + { + Lod = lod; + Loq = loq; + } + + public double Lod { get; } + public double Loq { get; } + + public double GetQuantLimit(OptimizeType optimizeType) + { + if (optimizeType == OptimizeType.LOD) + { + return Lod; + } + + return Loq; + } + + public override string ToString() + { + return ToString(Formats.CalibrationCurve, null); + } + + public string ToString(string format, IFormatProvider formatProvider) + { + return string.Format(formatProvider, QuantificationStrings.QuantLimit_ToString_LOD___0__LLOQ___1_, Lod.ToString(format, formatProvider), + Loq.ToString(format, formatProvider)); + } + + public int CompareTo(object obj) + { + if (obj == null) + { + return 1; + } + + var that = (QuantLimit)obj; + int result = Lod.CompareTo(that.Lod); + if (result == 0) + { + result = Loq.CompareTo(that.Loq); + } + + return result; + } + } + + public class TransitionsQuantLimit + { + public TransitionsQuantLimit(QuantLimit quantLimit, IdentityPath transitionIdentityPath) + : this(quantLimit, ImmutableList.Singleton(transitionIdentityPath)) + { + } + + public TransitionsQuantLimit(QuantLimit quantLimit, IEnumerable transitionIdentityPaths) + { + QuantLimit = quantLimit; + TransitionIdentityPaths = ImmutableList.ValueOf(transitionIdentityPaths); + } + public QuantLimit QuantLimit { get; } + public ImmutableList TransitionIdentityPaths { get; } + + public double GetQuantLimit(OptimizeType optimizeType) + { + return QuantLimit.GetQuantLimit(optimizeType); + } + } +} diff --git a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/WeightedPoint.cs b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/WeightedPoint.cs index f13ed4b878..1e93d1c466 100644 --- a/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/WeightedPoint.cs +++ b/pwiz_tools/Skyline/Model/DocSettings/AbsoluteQuantification/WeightedPoint.cs @@ -33,5 +33,15 @@ public WeightedPoint(double x, double y, double weight) : this() public double X { get; private set; } public double Y { get; private set; } public double Weight { get; private set; } + + public override string ToString() + { + if (Weight == 1) + { + return string.Format(@"({0},{1})", X, Y); + } + + return string.Format(@"({0},{1},{2})", X, Y, Weight); + } } } \ No newline at end of file diff --git a/pwiz_tools/Skyline/Model/Export.cs b/pwiz_tools/Skyline/Model/Export.cs index 64d2b93273..a0dabc7587 100644 --- a/pwiz_tools/Skyline/Model/Export.cs +++ b/pwiz_tools/Skyline/Model/Export.cs @@ -3835,7 +3835,10 @@ protected InputTarget GetTarget(PeptideDocNode nodePep, TransitionGroupDocNode n var windowIM = 0.4; if (Document.Settings.TransitionSettings.IonMobilityFiltering != null) { - var result = Document.Settings.GetIonMobilityFilter(nodePep, nodeTranGroup, nodeTran, null, null, _oneOverK0UpperLimit); + var libraryIonMobilities = Document.Settings.GetIonMobilities(Document.Molecules.SelectMany( + node => node.TransitionGroups.Select(nodeGroup => nodeGroup.GetLibKey(Document.Settings, node))) + .ToArray(), null); + var result = Document.Settings.GetIonMobilityFilter(nodePep, nodeTranGroup, nodeTran, libraryIonMobilities, null, _oneOverK0UpperLimit); if (result.HasIonMobilityValue) { ionMobility = result.IonMobility.Mobility.Value; diff --git a/pwiz_tools/Skyline/Model/GroupComparison/PeptideQuantifier.cs b/pwiz_tools/Skyline/Model/GroupComparison/PeptideQuantifier.cs index 3ec7bcbef8..68a4c0c018 100644 --- a/pwiz_tools/Skyline/Model/GroupComparison/PeptideQuantifier.cs +++ b/pwiz_tools/Skyline/Model/GroupComparison/PeptideQuantifier.cs @@ -226,6 +226,54 @@ public double GetIsotopologArea(SrmSettings settings, int replicateIndex, Isotop return numerator / denominator; } + public PeptideQuantifier WithQuantifiableTransitions( + IEnumerable quantifiableTransitionIdentityPaths) + { + ICollection identityPathSet = quantifiableTransitionIdentityPaths as ICollection ?? + quantifiableTransitionIdentityPaths.ToHashSet(); + if (identityPathSet.Count > 1 && !(identityPathSet is HashSet)) + { + identityPathSet = identityPathSet.ToHashSet(); + } + var newTransitionGroups = new List(); + foreach (var transitionGroupDocNode in PeptideDocNode.TransitionGroups) + { + if (SkipTransitionGroup(transitionGroupDocNode)) + { + newTransitionGroups.Add(transitionGroupDocNode); + continue; + } + + var newTransitions = new List(); + foreach (var transitionDocNode in transitionGroupDocNode.Transitions) + { + var identityPath = new IdentityPath(PeptideGroup, + PeptideDocNode.Peptide, transitionGroupDocNode.TransitionGroup, + transitionDocNode.Transition); + newTransitions.Add(transitionDocNode.ChangeQuantitative(identityPathSet.Contains(identityPath))); + } + newTransitionGroups.Add((TransitionGroupDocNode) transitionGroupDocNode.ChangeChildren(newTransitions.ToArray())); + } + + var newPeptideDocNode = (PeptideDocNode) PeptideDocNode.ChangeChildren(newTransitionGroups.ToArray()); + return new PeptideQuantifier(_normalizationData, PeptideGroup, newPeptideDocNode, + QuantificationSettings); + } + + public PeptideQuantifier WithQuantificationSettings(QuantificationSettings quantificationSettings) + { + return new PeptideQuantifier(_normalizationData, PeptideGroup, PeptideDocNode, + quantificationSettings); + } + + public PeptideQuantifier MakeAllTransitionsQuantitative() + { + var allTransitionIdentityPaths = PeptideDocNode.TransitionGroups.SelectMany(tg => + tg.Transitions.Select(t => new IdentityPath(PeptideGroup, PeptideDocNode.Peptide, + tg.TransitionGroup, t.Transition))).ToHashSet(); + return WithQuantifiableTransitions(allTransitionIdentityPaths); + } + private Quantity GetTransitionQuantity( SrmSettings srmSettings, IDictionary peptideStandards, diff --git a/pwiz_tools/Skyline/Model/PeptideDocNode.cs b/pwiz_tools/Skyline/Model/PeptideDocNode.cs index 11d5073167..225f6d46e8 100644 --- a/pwiz_tools/Skyline/Model/PeptideDocNode.cs +++ b/pwiz_tools/Skyline/Model/PeptideDocNode.cs @@ -774,6 +774,14 @@ public IEnumerable TransitionGroups } } + public IEnumerable QuantifiableTransitions + { + get + { + return TransitionGroups.SelectMany(t => t.Transitions).Where(t => t.ExplicitQuantitative); + } + } + public bool HasHeavyTransitionGroups { get diff --git a/pwiz_tools/Skyline/Properties/Settings.cs b/pwiz_tools/Skyline/Properties/Settings.cs index 60dbeb14ce..056cc6e9cd 100644 --- a/pwiz_tools/Skyline/Properties/Settings.cs +++ b/pwiz_tools/Skyline/Properties/Settings.cs @@ -1229,8 +1229,7 @@ public CalibrationCurveOptions CalibrationCurveOptions var calibrationCurveOptions = (CalibrationCurveOptions) this[@"CalibrationCurveOptions"]; if (calibrationCurveOptions == null) { - calibrationCurveOptions = new CalibrationCurveOptions(); - CalibrationCurveOptions = calibrationCurveOptions; + CalibrationCurveOptions = calibrationCurveOptions = CalibrationCurveOptions.DEFAULT; } return calibrationCurveOptions; } diff --git a/pwiz_tools/Skyline/Skyline.cs b/pwiz_tools/Skyline/Skyline.cs index 2a8078ff85..c067f29d51 100644 --- a/pwiz_tools/Skyline/Skyline.cs +++ b/pwiz_tools/Skyline/Skyline.cs @@ -2973,6 +2973,7 @@ private void DestroyImmediateWindow() #endregion + #region Help menu private void homeMenuItem_Click(object sender, EventArgs e) diff --git a/pwiz_tools/Skyline/Skyline.csproj b/pwiz_tools/Skyline/Skyline.csproj index 54f4951f99..98425c3ef6 100644 --- a/pwiz_tools/Skyline/Skyline.csproj +++ b/pwiz_tools/Skyline/Skyline.csproj @@ -371,6 +371,18 @@ + + Form + + + CalibrationCurveOptionsDlg.cs + + + UserControl + + + CalibrationGraphControl.cs + @@ -441,6 +453,29 @@ EditLinkedPeptidesDlg.cs + + Form + + + OptimizeTransitionsForm.cs + + + Form + + + OptimizeDocumentTransitionsForm.cs + + + True + True + OptimizeTransitionsResources.resx + + + UserControl + + + OptimizeTransitionsSettingsControl.cs + Form @@ -521,6 +556,8 @@ True ColumnCaptions.resx + + True True @@ -651,6 +688,12 @@ + + + + + + @@ -2700,6 +2743,12 @@ AsyncChromatogramsGraph2.cs + + CalibrationCurveOptionsDlg.cs + + + CalibrationGraphControl.cs + DetectionsToolbar.cs Designer @@ -2994,6 +3043,19 @@ MassErrorChartPropertyDlg.cs + + OptimizeTransitionsForm.cs + + + OptimizeDocumentTransitionsForm.cs + + + ResXFileCodeGenerator + OptimizeTransitionsResources.Designer.cs + + + OptimizeTransitionsSettingsControl.cs + PermuteIsotopeModificationsDlg.cs diff --git a/pwiz_tools/Skyline/SkylineGraphs.cs b/pwiz_tools/Skyline/SkylineGraphs.cs index 33c613ae30..1c8e100dc5 100644 --- a/pwiz_tools/Skyline/SkylineGraphs.cs +++ b/pwiz_tools/Skyline/SkylineGraphs.cs @@ -44,6 +44,7 @@ using pwiz.Skyline.Controls.Clustering; using pwiz.Skyline.Controls.Graphs.Calibration; using pwiz.Skyline.Controls.GroupComparison; +using pwiz.Skyline.EditUI.OptimizeTransitions; using pwiz.Skyline.Model.AuditLog; using pwiz.Skyline.Model.DocSettings.AbsoluteQuantification; using pwiz.Skyline.Model.ElementLocators.ExportAnnotations; @@ -76,6 +77,7 @@ public partial class SkylineWindow : private CalibrationForm _calibrationForm; private AuditLogForm _auditLogForm; private CandidatePeakForm _candidatePeakForm; + private OptimizeTransitionsForm _optimizeTransitionsForm; public static int MAX_GRAPH_CHROM = 100; // Never show more than this many chromatograms, lest we hit the Windows handle limit private readonly List _listGraphChrom = new List(); // List order is MRU, with oldest in position 0 private bool _inGraphUpdate; @@ -663,6 +665,10 @@ private IDockableForm DeserializeForm(string persistentString) { return _immediateWindow ?? CreateImmediateWindow(); } + if (Equals(persistentString, typeof(OptimizeTransitionsForm).ToString())) + { + return _optimizeTransitionsForm ?? CreateOptimizeTransitionsForm(); + } if (persistentString.StartsWith(typeof(GraphChromatogram).ToString())) { if (_listGraphChrom.Count >= MAX_GRAPH_CHROM) @@ -6030,5 +6036,47 @@ void candidatePeakForm_FormClosed(object sender, FormClosedEventArgs e) _candidatePeakForm = null; } #endregion + #region OptimizeTransitionsForm + public void ShowOptimizeTransitionsForm() + { + if (_optimizeTransitionsForm != null) + { + _optimizeTransitionsForm.Activate(); + } + else + { + _optimizeTransitionsForm = CreateOptimizeTransitionsForm(); + _optimizeTransitionsForm.Show(dockPanel, GetFloatingRectangleForNewWindow()); + } + } + + private OptimizeTransitionsForm CreateOptimizeTransitionsForm() + { + _optimizeTransitionsForm = new OptimizeTransitionsForm(this); + _optimizeTransitionsForm.FormClosed += OptimizeTransitionsForm_OnFormClosed; + + return _optimizeTransitionsForm; + } + + private void OptimizeTransitionsForm_OnFormClosed(object sender, FormClosedEventArgs e) + { + _optimizeTransitionsForm = null; + } + #endregion + #region OptimizeDocumentTransitionsForm + public void ShowOptimizeDocumentTransitionsForm() + { + var optimizeDocumentTransitionsForm = OwnedForms.OfType().FirstOrDefault(); + if (optimizeDocumentTransitionsForm != null) + { + optimizeDocumentTransitionsForm.Activate(); + return; + } + + optimizeDocumentTransitionsForm = new OptimizeDocumentTransitionsForm(this) { Owner = this }; + optimizeDocumentTransitionsForm.Show(this); + } + #endregion + } } diff --git a/pwiz_tools/Skyline/Test/BilinearFitTest.cs b/pwiz_tools/Skyline/Test/BilinearFitTest.cs new file mode 100644 index 0000000000..05d08e4c82 --- /dev/null +++ b/pwiz_tools/Skyline/Test/BilinearFitTest.cs @@ -0,0 +1,50 @@ +using MathNet.Numerics.LinearRegression; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using pwiz.SkylineTestUtil; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace pwiz.SkylineTest +{ + [TestClass] + public class BilinearFitTest : AbstractUnitTest + { + [TestMethod] + public void TestWeightedRegression() + { + var x = new[] {0.05, 0.07, 0.1, 0.3, 0.5, 0.7, 1.0}; + var y = new[] + { + 0.17310666666666666, 0.1674433333333333, 0.15965666666666667, 0.18022333333333332, 0.18300000000000002, + 0.17776666666666666, 0.17531666666666668 + }; + var weights = new[] + { + 20.0, 14.285714285714285, 10.0, 3.3333333333333335, 2.0, 1.4285714285714286, 1.0 + }; + var result = WeightedRegression.Weighted(x.Zip(y, (a, b) => Tuple.Create(new []{a}, b)), weights, true); + // Assert.AreEqual(0.00239897, result[0], 1e-6); + // Assert.AreEqual(0.16965172, result[1], 1e-6); + var difference1 = CalculateSumOfDifferences(x, y, weights, result[1], result[0], false); + var difference2 = CalculateSumOfDifferences(x, y, weights, .00239897, .16965172, false); + var difference3 = CalculateSumOfDifferences(x, y, weights, 0.00113951, 0.17091118, false); + Assert.IsTrue(difference1 < difference2); + Assert.IsTrue(difference1 < difference3); + } + + public static double CalculateSumOfDifferences(IList x, IList y, IList weights, double slope, + double intercept, bool squareArea) + { + double sum = 0; + for (int i = 0; i < x.Count; i++) + { + var expected = slope * x[i] + intercept; + var difference = y[i] - expected; + sum += difference * difference * weights[i]; + } + + return sum; + } + } +} diff --git a/pwiz_tools/Skyline/Test/CalibrationCurveOptionsTest.cs b/pwiz_tools/Skyline/Test/CalibrationCurveOptionsTest.cs new file mode 100644 index 0000000000..f9c7faa2f7 --- /dev/null +++ b/pwiz_tools/Skyline/Test/CalibrationCurveOptionsTest.cs @@ -0,0 +1,27 @@ +using System; +using System.IO; +using System.Xml.Serialization; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using pwiz.Skyline.Model.DocSettings.AbsoluteQuantification; +using pwiz.SkylineTestUtil; + +namespace pwiz.SkylineTest +{ + [TestClass] + public class CalibrationCurveOptionsTest : AbstractUnitTest + { + [TestMethod] + public void TestCalibrationCurveOptionsSerialization() + { + var calibrationCurveOptions = CalibrationCurveOptions.DEFAULT.ChangeLogXAxis(true).ChangeLogYAxis(false); + var serializer = new XmlSerializer(typeof(CalibrationCurveOptions)); + var stringWriter = new StringWriter(); + serializer.Serialize(stringWriter, calibrationCurveOptions); + Console.Out.WriteLine(stringWriter.ToString()); + var roundTrip = (CalibrationCurveOptions)serializer.Deserialize(new StringReader(stringWriter.ToString())); + Assert.AreEqual(calibrationCurveOptions.LogYAxis, roundTrip.LogYAxis); + Assert.AreEqual(calibrationCurveOptions.LogXAxis, roundTrip.LogXAxis); + Assert.AreEqual(calibrationCurveOptions, roundTrip); + } + } +} diff --git a/pwiz_tools/Skyline/Test/Quantification/PiecewiseLinearFittingTest.cs b/pwiz_tools/Skyline/Test/Quantification/PiecewiseLinearFittingTest.cs new file mode 100644 index 0000000000..1af4c32a20 --- /dev/null +++ b/pwiz_tools/Skyline/Test/Quantification/PiecewiseLinearFittingTest.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using pwiz.Common.Collections; +using pwiz.Skyline.Model.DocSettings.AbsoluteQuantification; +using pwiz.Skyline.Util.Extensions; +using pwiz.SkylineTestUtil; +using ZedGraph; +using SampleType = pwiz.Skyline.Model.DocSettings.AbsoluteQuantification.SampleType; + +namespace pwiz.SkylineTest.Quantification +{ + [TestClass] + public class PiecewiseLinearFittingTest : AbstractUnitTest + { + private const float symbolSize = 16; + private const float lineWidth = 6; + Tuple[] points = new[] + { + Tuple.Create(0.005, 1.64867E-06), + Tuple.Create(0.005, 3.46991E-06), + Tuple.Create(0.005, 3.11628E-07), + Tuple.Create(0.01, 3.52311E-06), + Tuple.Create(0.01, 2.98539E-06), + Tuple.Create(0.01, 1.69306E-06), + Tuple.Create(0.03, 1.32789E-06), + Tuple.Create(0.03, 2.93961E-06), + Tuple.Create(0.03, 2.02407E-06), + Tuple.Create(0.05, 1.40259E-06), + Tuple.Create(0.05, 3.29722E-06), + Tuple.Create(0.05, 2.17824E-06), + Tuple.Create(0.07, 3.71022E-06), + Tuple.Create(0.07, 2.78271E-06), + Tuple.Create(0.07, 4.32119E-07), + Tuple.Create(0.1, 3.18625E-06), + Tuple.Create(0.1, 3.29426E-06), + Tuple.Create(0.1, 1.89346E-06), + Tuple.Create(0.3, 5.48088E-06), + Tuple.Create(0.3, 5.73241E-06), + Tuple.Create(0.3, 3.5879E-06), + Tuple.Create(0.5, 7.44818E-06), + Tuple.Create(0.5, 7.14181E-06), + Tuple.Create(0.5, 5.34057E-06), + Tuple.Create(0.7, 1.10748E-05), + Tuple.Create(0.7, 1.20063E-05), + Tuple.Create(0.7, 9.19692E-06), + Tuple.Create(1.0, 1.28049E-05), + Tuple.Create(1.0, 1.10805E-05), + Tuple.Create(1.0, 1.03991E-05) + }; + + + [TestMethod] + public void TestPiecewiseLinearFitting() + { + var folder = "D:\\test\\poster"; + var weightedPoints = points.Select(p => new WeightedPoint(p.Item1, p.Item2, 1 / p.Item1 / p.Item1)).ToList(); + foreach (var xOffset in weightedPoints.Select(pt => pt.X).Distinct().OrderBy(x => x)) + { + var scoredBilinearCurve = ScoredBilinearCurve.WithOffset(xOffset, weightedPoints); + var image = DisplayCurve(xOffset, weightedPoints, scoredBilinearCurve); + var path = Path.Combine("d:\\test\\poster", "PiecewiseLinearFittingTest_" + (xOffset * 1000).ToString("0000") + ".png"); + image.Save(path, ImageFormat.Png); + Console.Out.WriteLine("XOffset: {0} Curve: {1} Error: {2}", xOffset, scoredBilinearCurve.CalibrationCurve, scoredBilinearCurve.Error); + } + + var bootstrapImage = DisplayBootstrapCurves(weightedPoints); + bootstrapImage.Save(Path.Combine(folder, "BootstrapCurves.png")); + } + + private Image DisplayCurve(double xOffset, IList weightedPoints, ScoredBilinearCurve scoredBilinearCurve) + { + var xMin = weightedPoints.Min(pt => pt.X); + var xMax = weightedPoints.Max(pt => pt.X); + + var metrics = scoredBilinearCurve.CalibrationCurve.GetMetrics(weightedPoints); + var zedGraphControl = CreateZedGraphControl(); + zedGraphControl.GraphPane.Title.Text = "Candidate Calibration Curve: Linear portion is where x > " + xOffset; + + var linearPoints = new PointPairList(weightedPoints.Where(pt => pt.X > xOffset) + .Select(pt => new PointPair(pt.X, pt.Y)).ToList()); + if (linearPoints.Count > 0) + { + var linearPointsCurve = + new LineItem("Points in linear range", linearPoints, Color.Green, SymbolType.Circle, lineWidth); + linearPointsCurve.Symbol.Fill = new Fill(Color.Green); + linearPointsCurve.Symbol.Size = symbolSize; + linearPointsCurve.Line.IsVisible = false; + zedGraphControl.GraphPane.CurveList.Add(linearPointsCurve); + } + var noisePoints = new PointPairList(weightedPoints.Where(pt => pt.X <= xOffset) + .Select(pt => new PointPair(pt.X, pt.Y)).ToList()); + if (noisePoints.Count > 0) + { + var noisePointsCurve = + new LineItem("Points in noise range", noisePoints, Color.DarkOrange, SymbolType.Diamond, lineWidth); + noisePointsCurve.Symbol.Fill = new Fill(Color.DarkOrange); + noisePointsCurve.Symbol.Size = symbolSize; + noisePointsCurve.Line.IsVisible = false; + zedGraphControl.GraphPane.CurveList.Add(noisePointsCurve); + } + + if (scoredBilinearCurve.CalibrationCurve is CalibrationCurve.Bilinear bilinear) + { + var turningPointCurve = new LineItem("Turning point of calibration curve", + new PointPairList(new[] { new PointPair(bilinear.TurningPoint, bilinear.GetY(bilinear.TurningPoint)) }), Color.Blue, + SymbolType.Square, lineWidth); + turningPointCurve.Symbol.Fill = new Fill(Color.Blue); + turningPointCurve.Symbol.Size = symbolSize; + turningPointCurve.Line.IsVisible = false; + zedGraphControl.GraphPane.CurveList.Add(turningPointCurve); + + var height = bilinear.GetY(bilinear.TurningPoint); + + + var linearCurvePoints = new PointPairList(); + const int nPts = 10000; + var xLinearMin = linearPoints.Select(pt => pt.X).Append(bilinear.TurningPoint).Min(); + for (int iPt = 0; iPt < nPts; iPt++) + { + double x = (xLinearMin * (nPts - iPt) + xMax * iPt) / nPts; + double y = bilinear.GetLinearCalibrationCurve().GetY(x); + linearCurvePoints.Add(x, y); + } + + var linearCurve = new LineItem("Linear part of calibration curve", linearCurvePoints, Color.Black, + SymbolType.None, lineWidth); + linearCurve.Line.Style = DashStyle.Solid; + zedGraphControl.GraphPane.CurveList.Add(linearCurve); + + var horizontalCurve = new LineItem("Horizontal part of calibration curve", + new PointPairList(new[] + { new PointPair(xMin, height), new PointPair(bilinear.TurningPoint, height) }), Color.Black, + SymbolType.None, lineWidth); + horizontalCurve.Line.Style = DashStyle.Dot; + zedGraphControl.GraphPane.CurveList.Add(horizontalCurve); + } + + // var points = new PointPairList(weightedPoints.Select(pt => pt.X).ToList(), + // weightedPoints.Select(pt => pt.Y).ToList()); + // var pointsCurve = new LineItem(null, points, SampleType.STANDARD.Color, SampleType.STANDARD.SymbolType); + // pointsCurve.Symbol.Fill = new Fill(SampleType.STANDARD.Color); + // pointsCurve.Symbol.Size = 20; + // pointsCurve.Line.IsVisible = false; + // zedGraphControl.GraphPane.CurveList.Add(pointsCurve); + // var calibrationPoints = new PointPairList(); + // // const int nPts = 10000; + // // for (int iPt = 0; iPt < nPts; iPt++) + // // { + // // double x = (xMin * (nPts - iPt) + xMax * iPt) / nPts; + // // double y = scoredBilinearCurve.CalibrationCurve.GetY(x); + // // calibrationPoints.Add(x, y); + // // } + // + // var calibrationCurve = new LineItem(null, calibrationPoints, Color.Black, SymbolType.None, 2); + // zedGraphControl.GraphPane.CurveList.Add(calibrationCurve); + SetScale(zedGraphControl.GraphPane); + List labelLines = new List + { + metrics.ToString(), + "Weighted sum of squares deviation from observed points: " + (scoredBilinearCurve.Error * 1e12).ToString("0.0000") + }; + TextObj text = new TextObj(TextUtil.LineSeparate(labelLines), .01, 0, + CoordType.ChartFraction, AlignH.Left, AlignV.Top) + { + IsClippedToChartRect = true, + ZOrder = ZOrder.E_BehindCurves, + }; + text.FontSpec.Border.IsVisible = false; + text.FontSpec.StringAlignment = StringAlignment.Near; + text.FontSpec.Size = 24f; + + zedGraphControl.GraphPane.GraphObjList.Add(text); + zedGraphControl.AxisChange(); + return zedGraphControl.MasterPane.GetImage(1000, 1000, 96); + } + + private Image DisplayBootstrapCurves(IList weightedPoints) + { + var bootstrapFiguresOfMeritCalculator = new BootstrapFiguresOfMeritCalculator(.4); + var bootstrapCurves = new List>(); + var lod = BootstrapFiguresOfMeritCalculator.ComputeLod(weightedPoints); + var loq = bootstrapFiguresOfMeritCalculator.ComputeBootstrappedLoq(weightedPoints, bootstrapCurves); + var calibrationCurve = RegressionFit.BILINEAR.Fit(weightedPoints); + var xMin = weightedPoints.Min(pt => pt.X); + var xMax = weightedPoints.Max(pt => pt.X); + var standards = new LineItem("Standard", + new PointPairList(weightedPoints.Select(pt => new PointPair(pt.X, pt.Y)).ToList()), + SampleType.STANDARD.Color, SampleType.STANDARD.SymbolType); + standards.Line.IsVisible = false; + standards.Symbol.Size = symbolSize; + var zedGraphControl = new ZedGraphControl(); + zedGraphControl.GraphPane.CurveList.Add(standards); + List bootstrapCurveItems = new List(); + var boostrapColor = Color.FromArgb(40, Color.Teal); + foreach (var bootstrapPoints in bootstrapCurves) + { + string title = bootstrapCurveItems.Count == 0 ? QuantificationStrings.CalibrationGraphControl_DoUpdate_Bootstrap_Curve : null; + var curve = new LineItem(title, new PointPairList(bootstrapPoints), boostrapColor, SymbolType.None, + lineWidth); + bootstrapCurveItems.Add(curve); + } + + GetYRange(zedGraphControl.GraphPane.CurveList.Concat(bootstrapCurveItems), out double minY, out double maxY); + zedGraphControl.GraphPane.CurveList.Add(new LineItem(QuantificationStrings.CalibrationGraphControl_DoUpdate_Limit_of_Detection, new PointPairList(new[] { lod, lod }, new[] { minY, maxY }), Color.Black, SymbolType.None) + { + Line = { Style = DashStyle.Dot, Width = lineWidth } + }); + + zedGraphControl.GraphPane.CurveList.Add(new LineItem(QuantificationStrings.CalibrationGraphControl_DoUpdate_Lower_Limit_of_Quantification, new PointPairList(new[] { loq, loq }, new[] { minY, maxY }), Color.Black, SymbolType.None) + { + Line = { Style = DashStyle.Dash, Width = lineWidth } + }); + zedGraphControl.GraphPane.CurveList.AddRange(bootstrapCurveItems); + SetScale(zedGraphControl.GraphPane); + return zedGraphControl.MasterPane.GetImage(1000, 1000, 96); + + } + + private ZedGraphControl CreateZedGraphControl() + { + var zedGraphControl = new ZedGraphControl(); + zedGraphControl.MasterPane.Border.IsVisible = false; + zedGraphControl.GraphPane.Border.IsVisible = false; + zedGraphControl.GraphPane.Chart.Border.IsVisible = false; + zedGraphControl.GraphPane.Title.FontSpec.Size = 24f; + zedGraphControl.GraphPane.IsFontsScaled = false; + zedGraphControl.GraphPane.XAxis.MajorTic.IsOpposite = false; + zedGraphControl.GraphPane.XAxis.MinorTic.IsOpposite = false; + zedGraphControl.GraphPane.YAxis.MajorTic.IsOpposite = false; + zedGraphControl.GraphPane.YAxis.MinorTic.IsOpposite = false; + zedGraphControl.GraphPane.Legend.FontSpec.Size = 18f; + zedGraphControl.GraphPane.XAxis.Type = AxisType.Log; + zedGraphControl.GraphPane.XAxis.Title.Text = "Analyte Concentration"; + zedGraphControl.GraphPane.XAxis.Title.FontSpec.Size = 24f; + zedGraphControl.GraphPane.YAxis.Type = AxisType.Log; + zedGraphControl.GraphPane.YAxis.Title.Text = "Normalized Peak Area"; + zedGraphControl.GraphPane.YAxis.Title.FontSpec.Size = 24f; + return zedGraphControl; + } + + private void SetScale(GraphPane graphPane) + { + var allPoints = graphPane.CurveList + .SelectMany(curve => Enumerable.Range(0, curve.Points.Count).Select(i => curve.Points[i])).ToList(); + var xMin = allPoints.Min(pt => pt.X); + var xMax = allPoints.Max(pt => pt.X); + graphPane.XAxis.Scale.Min = xMin * .9; + graphPane.XAxis.Scale.Max = xMax * 1.1; + var yMin = allPoints.Min(pt => pt.Y); + var yMax = allPoints.Max(pt => pt.Y); + graphPane.YAxis.Scale.Min = yMin * .9; + graphPane.YAxis.Scale.Max = yMax * 1.1; + } + + private void GetYRange(IEnumerable curves, out double yMin, out double yMax) + { + var allPoints = + curves.SelectMany(curve => Enumerable.Range(0, curve.Points.Count).Select(i => curve.Points[i])).ToList(); + yMin = allPoints.Min(pt => pt.Y); + yMax = allPoints.Max(pt => pt.Y); + } + } +} diff --git a/pwiz_tools/Skyline/Test/Reporting/ColumnCaptionLocalizationTest.cs b/pwiz_tools/Skyline/Test/Reporting/ColumnCaptionLocalizationTest.cs index f60efd1cdc..2f43659ebd 100644 --- a/pwiz_tools/Skyline/Test/Reporting/ColumnCaptionLocalizationTest.cs +++ b/pwiz_tools/Skyline/Test/Reporting/ColumnCaptionLocalizationTest.cs @@ -31,6 +31,7 @@ using pwiz.Common.DataBinding.Documentation; using pwiz.Skyline.Controls.GroupComparison; using pwiz.Skyline.Controls.Spectra; +using pwiz.Skyline.EditUI.OptimizeTransitions; using pwiz.Skyline.Model; using pwiz.Skyline.Model.AuditLog.Databinding; using pwiz.Skyline.Model.Databinding; @@ -52,7 +53,8 @@ public class ColumnCaptionLocalizationTest : AbstractUnitTest private static readonly IList STARTING_TYPES = ImmutableList.ValueOf(new[] { typeof(SkylineDocument), typeof(FoldChangeBindingSource.FoldChangeRow), typeof(AuditLogRow), - typeof(CandidatePeakGroup), typeof(MatchingPrecursors), typeof(SpectrumClass) + typeof(CandidatePeakGroup), typeof(OptimizeDocumentTransitionsForm.Row), + typeof(MatchingPrecursors), typeof(SpectrumClass) }); /// /// This test method just outputs the entire text that should go in "ColumnCaptions.resx". diff --git a/pwiz_tools/Skyline/Test/Test.csproj b/pwiz_tools/Skyline/Test/Test.csproj index cc4ac19db9..80364b53fe 100644 --- a/pwiz_tools/Skyline/Test/Test.csproj +++ b/pwiz_tools/Skyline/Test/Test.csproj @@ -162,7 +162,9 @@ + + @@ -176,6 +178,7 @@ + @@ -298,6 +301,10 @@ {09FC3CB3-FCCD-4906-A370-160B28725936} ProteomeDb + + {B99650EE-AF46-47B4-A4A9-212ADE7809B7} + ZedGraph + {DACEE7D5-5A6A-4001-9602-FAB1A9A2DE59} ProteowizardWrapper diff --git a/pwiz_tools/Skyline/TestFunctional/CalibrationTest.cs b/pwiz_tools/Skyline/TestFunctional/CalibrationTest.cs index b56743730a..49acfced7a 100644 --- a/pwiz_tools/Skyline/TestFunctional/CalibrationTest.cs +++ b/pwiz_tools/Skyline/TestFunctional/CalibrationTest.cs @@ -44,8 +44,9 @@ public void TestCalibration() protected override void DoTest() { - Settings.Default.CalibrationCurveOptions.DisplaySampleTypes = new[] - {SampleType.UNKNOWN.Name, SampleType.QC.Name, SampleType.STANDARD.Name}; + Settings.Default.CalibrationCurveOptions = + Settings.Default.CalibrationCurveOptions.ChangeDisplaySampleTypes(new[] + { SampleType.UNKNOWN, SampleType.QC, SampleType.STANDARD }); RunUI(() => SkylineWindow.ShowCalibrationForm()); var calibrationForm = FindOpenForm(); Assert.AreEqual(QuantificationStrings.CalibrationForm_DisplayCalibrationCurve_No_results_available, @@ -62,7 +63,7 @@ protected override void DoTest() RunUI(() => SkylineWindow.SequenceTree.SelectedPath = SkylineWindow.DocumentUI.GetPathTo( (int)SrmDocument.Level.Molecules, 0)); WaitForGraphs(); - Assert.AreEqual(QuantificationStrings.CalibrationForm_DisplayCalibrationCurve_Use_the_Quantification_tab_on_the_Peptide_Settings_dialog_to_control_the_conversion_of_peak_areas_to_concentrations_, + AssertEx.AreEqual(QuantificationStrings.CalibrationForm_DisplayCalibrationCurve_Use_the_Quantification_tab_on_the_Peptide_Settings_dialog_to_control_the_conversion_of_peak_areas_to_concentrations_, GetGraphTitle(calibrationForm)); PauseForScreenShot("Quantification not configured"); { diff --git a/pwiz_tools/Skyline/TestFunctional/FiguresOfMeritTest.cs b/pwiz_tools/Skyline/TestFunctional/FiguresOfMeritTest.cs index a41cba1367..97176a98be 100644 --- a/pwiz_tools/Skyline/TestFunctional/FiguresOfMeritTest.cs +++ b/pwiz_tools/Skyline/TestFunctional/FiguresOfMeritTest.cs @@ -229,7 +229,13 @@ private void VerifyFiguresOfMeritValues(FiguresOfMeritOptions options, { return null; } - var calibrationCurve = peptideEntity.GetCalibrationCurveFitter().GetCalibrationCurve(); + + var calibrationCurveFitter = peptideEntity.GetCalibrationCurveFitter(); + var calibrationCurve = calibrationCurveFitter.GetCalibrationCurve(); + if (calibrationCurveFitter.FiguresOfMeritCalculator is BootstrapFiguresOfMeritCalculator bootstrapFiguresOfMeritCalculator) + { + return calibrationCurveFitter.GetFiguresOfMerit(calibrationCurve).LimitOfQuantification; + } var concentrationMultiplier = peptideEntity.ConcentrationMultiplier.GetValueOrDefault(1); double? bestLoq = null; foreach (var grouping in peptideResults.OrderByDescending(g => g.Key)) diff --git a/pwiz_tools/Skyline/TestFunctional/TestFunctional.csproj b/pwiz_tools/Skyline/TestFunctional/TestFunctional.csproj index 6dc880472d..bd0d879a09 100644 --- a/pwiz_tools/Skyline/TestFunctional/TestFunctional.csproj +++ b/pwiz_tools/Skyline/TestFunctional/TestFunctional.csproj @@ -234,6 +234,7 @@ + diff --git a/pwiz_tools/Skyline/TestFunctional/TransitionOptimizationTest.cs b/pwiz_tools/Skyline/TestFunctional/TransitionOptimizationTest.cs new file mode 100644 index 0000000000..c85f6bd65d --- /dev/null +++ b/pwiz_tools/Skyline/TestFunctional/TransitionOptimizationTest.cs @@ -0,0 +1,65 @@ +/* + * Original author: Nicholas Shulman , + * MacCoss Lab, Department of Genome Sciences, UW + * + * Copyright 2023 University of Washington - Seattle, WA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using pwiz.Skyline.Controls.Graphs.Calibration; +using pwiz.Skyline.EditUI.OptimizeTransitions; +using pwiz.Skyline.Model; +using pwiz.SkylineTestUtil; + +namespace pwiz.SkylineTestFunctional +{ + [TestClass] + public class TransitionOptimizationTest : AbstractFunctionalTest + { + [TestMethod] + public void TestTransitionOptimization() + { + TestFilesZip = @"TestFunctional\TransitionOptimizationTest.zip"; + RunFunctionalTest(); + } + + protected override void DoTest() + { + RunUI(()=> + { + SkylineWindow.OpenFile(TestFilesDir.GetTestPath("TransitionOptimizationTest.sky")); + SkylineWindow.SelectedPath = SkylineWindow.Document.GetPathTo((int)SrmDocument.Level.Molecules, 0); + }); + WaitForDocumentLoaded(); + var optimizeTransitionsForm = + ShowDialog(SkylineWindow.ShowOptimizeTransitionsForm); + WaitForCondition(() => optimizeTransitionsForm.IsComplete); + RunUI(() => + { + optimizeTransitionsForm.SelectOriginalTransitionRows(); + Assert.AreEqual(optimizeTransitionsForm.DataGridView.RowCount, optimizeTransitionsForm.DataGridView.SelectedRows.Count); + optimizeTransitionsForm.SelectOptimizedTransitionRows(); + Assert.AreNotEqual(optimizeTransitionsForm.DataGridView.RowCount, optimizeTransitionsForm.DataGridView.SelectedRows.Count); + }); + RunDlg( + optimizeTransitionsForm.CalibrationGraphControl.ShowCalibrationCurveOptions, + dlg => dlg.OkDialog()); + RunUI(() => { optimizeTransitionsForm.ShowOptimizeDocumentTransitionsForm(); }); + var optimizeDocumentTransitionsForm = WaitForOpenForm(); + RunUI(()=>optimizeDocumentTransitionsForm.Preview()); + OkDialog(optimizeDocumentTransitionsForm, optimizeDocumentTransitionsForm.Close); + OkDialog(optimizeTransitionsForm, optimizeTransitionsForm.Close); + } + } +} diff --git a/pwiz_tools/Skyline/TestFunctional/TransitionOptimizationTest.zip b/pwiz_tools/Skyline/TestFunctional/TransitionOptimizationTest.zip new file mode 100644 index 0000000000..051437831e Binary files /dev/null and b/pwiz_tools/Skyline/TestFunctional/TransitionOptimizationTest.zip differ diff --git a/pwiz_tools/Skyline/TestRunnerLib/TestRunnerFormLookup.csv b/pwiz_tools/Skyline/TestRunnerLib/TestRunnerFormLookup.csv index e9144c9262..a7361a68d6 100644 --- a/pwiz_tools/Skyline/TestRunnerLib/TestRunnerFormLookup.csv +++ b/pwiz_tools/Skyline/TestRunnerLib/TestRunnerFormLookup.csv @@ -28,6 +28,7 @@ BuildLibraryDlg.FilesPage,TestFullScanId BuildLibraryNotification,* CalculateIsolationSchemeDlg,TestCalculateIsolationWindows CalibrateIrtDlg,IrtFunctionalTest +CalibrationCurveOptionsDlg,TestTransitionOptimization CalibrationForm,TestCalibration CandidatePeakForm,TestCandidatePeaks ChangeIrtPeptidesDlg,IrtFunctionalTest @@ -154,6 +155,8 @@ MultiButtonMsgDlg,TestShareSettings NameLayoutForm,TestPivotEditor NoModeUIDlg,UIModeSettingsTest OpenDataSourceDialog,TestRetentionTimeAlignment +OptimizeDocumentTransitionsForm,TestTransitionOptimization +OptimizeTransitionsForm,TestTransitionOptimization PanoramaFilePicker, TestPanorama PanoramaDirectoryPicker, TestPanorama PasteDlg.FastaTab,ProteinMetadataFunctionalTests diff --git a/pwiz_tools/Skyline/TestTutorial/SmallMoleculesQuantificationTutorial.cs b/pwiz_tools/Skyline/TestTutorial/SmallMoleculesQuantificationTutorial.cs index 3b5ad308b4..92c9e15459 100644 --- a/pwiz_tools/Skyline/TestTutorial/SmallMoleculesQuantificationTutorial.cs +++ b/pwiz_tools/Skyline/TestTutorial/SmallMoleculesQuantificationTutorial.cs @@ -402,8 +402,12 @@ protected override void DoTest() }); PauseForScreenShot("Document Grid - Molecule Ratio Results - manually widen to show all columns", 25); - Settings.Default.CalibrationCurveOptions.LogXAxis = true; - Settings.Default.CalibrationCurveOptions.LogYAxis = true; + RunUI(() => + { + Settings.Default.CalibrationCurveOptions = Settings.Default.CalibrationCurveOptions + .ChangeLogXAxis(true) + .ChangeLogYAxis(true); + }); var calibrationForm = FindOpenForm(); RunUI(()=>calibrationForm.UpdateUI(false));