Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 93 additions & 97 deletions MetaMorpheus/EngineLayer/Gptmd/GptmdEngine.cs

Large diffs are not rendered by default.

161 changes: 161 additions & 0 deletions MetaMorpheus/EngineLayer/Gptmd/IGptmdFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#nullable enable
using System;
using Omics.Fragmentation;
using System.Collections.Generic;
using Omics;
using System.Linq;
using Omics.Modifications;

namespace EngineLayer;

public interface IGptmdFilter : IEquatable<IGptmdFilter>
{
public static string GetFilterTypeName(IGptmdFilter filter) => filter.GetType().Name;

bool Passes(
IBioPolymerWithSetMods candidatePeptide,
SpectralMatch psm,
double newScore,
double originalScore,
List<MatchedFragmentIon> matchedIons,
int peptideOneBasedModSite,
int peptideLength,
Modification modAttemptingToAdd);

bool IEquatable<IGptmdFilter>.Equals(IGptmdFilter? other)
{
return other != null && GetType() == other.GetType();
}
}

/// <summary>
/// Requires that the new score is greater than the original score.
/// </summary>
public sealed class ImprovedScoreFilter : IGptmdFilter
{
public bool Passes(
IBioPolymerWithSetMods candidatePeptide,
SpectralMatch psm,
double newScore,
double originalScore,
List<MatchedFragmentIon> matchedIons,
int peptideOneBasedModSite,
int peptideLength,
Modification modAttemptingToAdd)
{
return newScore > originalScore;
}
}

/// <summary>
/// Requires the mod site to be covered by at least one N-terminal and one C-terminal ion.
/// That is, ions from both directions must include the mod, even if not flanking it.
/// </summary>
public sealed class DualDirectionalIonCoverageFilter : IGptmdFilter
{
public bool Passes(
IBioPolymerWithSetMods candidatePeptide,
SpectralMatch psm,
double newScore,
double originalScore,
List<MatchedFragmentIon> matchedIons,
int peptideOneBasedModSite,
int peptideLength,
Modification modAttemptingToAdd)
{
if (matchedIons == null || matchedIons.Count == 0)
return false;

int site = peptideOneBasedModSite;

bool coveredFromNTerm = matchedIons.Any(m =>
m.NeutralTheoreticalProduct.ProductType == ProductType.M ||
(m.NeutralTheoreticalProduct.Terminus is FragmentationTerminus.N or FragmentationTerminus.FivePrime &&
m.NeutralTheoreticalProduct.ResiduePosition >= site)
);

bool coveredFromCTerm = matchedIons.Any(m =>
m.NeutralTheoreticalProduct.ProductType == ProductType.M ||
(m.NeutralTheoreticalProduct.Terminus is FragmentationTerminus.C or FragmentationTerminus.ThreePrime &&
m.NeutralTheoreticalProduct.ResiduePosition < site)
);

if (modAttemptingToAdd.LocationRestriction.Contains("terminal", StringComparison.InvariantCultureIgnoreCase))
return coveredFromCTerm || coveredFromNTerm;

return coveredFromNTerm && coveredFromCTerm;
}
}

/// <summary>
/// Requires the mod site to be covered by at least one N-terminal or one C-terminal ion.
/// That is, ions from one direction must include the mod, even if not flanking it.
/// </summary>
public sealed class UniDirectionalIonCoverageFilter : IGptmdFilter
{
public bool Passes(
IBioPolymerWithSetMods candidatePeptide,
SpectralMatch psm,
double newScore,
double originalScore,
List<MatchedFragmentIon> matchedIons,
int peptideOneBasedModSite,
int peptideLength,
Modification modAttemptingToAdd)
{
if (matchedIons == null || matchedIons.Count == 0)
return false;

int site = peptideOneBasedModSite;

bool coveredFromNTerm = matchedIons.Any(m =>
m.NeutralTheoreticalProduct.ProductType == ProductType.M ||
(m.NeutralTheoreticalProduct.Terminus is FragmentationTerminus.N or FragmentationTerminus.FivePrime &&
m.NeutralTheoreticalProduct.ResiduePosition >= site)
);

bool coveredFromCTerm = matchedIons.Any(m =>
m.NeutralTheoreticalProduct.ProductType == ProductType.M ||
(m.NeutralTheoreticalProduct.Terminus is FragmentationTerminus.C or FragmentationTerminus.ThreePrime &&
m.NeutralTheoreticalProduct.ResiduePosition < site)
);

return coveredFromNTerm || coveredFromCTerm;
}
}

/// <summary>
/// Requires flanking ions — a fragment from *before* and one from *after* the mod site,
/// regardless of fragmentation direction.
/// </summary>
public sealed class FlankingIonCoverageFilter : IGptmdFilter
{
// TODO: Consider N-terminal and C-terminal mods as special cases, where the flanking ion not possible except for the M ion.
public bool Passes(
IBioPolymerWithSetMods candidatePeptide,
SpectralMatch psm,
double newScore,
double originalScore,
List<MatchedFragmentIon> matchedIons,
int peptideOneBasedModSite,
int peptideLength,
Modification modAttemptingToAdd)
{
if (matchedIons == null || matchedIons.Count == 0)
return false;

int site = peptideOneBasedModSite;

bool leftFlank = matchedIons.Any(m =>
m.NeutralTheoreticalProduct.ResiduePosition == site - 1);

bool rightFlank = matchedIons.Any(m =>
m.NeutralTheoreticalProduct.ResiduePosition == site);

if (modAttemptingToAdd.LocationRestriction.Contains("terminal", StringComparison.InvariantCultureIgnoreCase))
return leftFlank || rightFlank;

return leftFlank && rightFlank;
}
}

16 changes: 15 additions & 1 deletion MetaMorpheus/GUI/TaskWindows/GPTMDTaskWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,21 @@
</TextBox.Style>
</TextBox>
</StackPanel>
<StackPanel Grid.Column="0" Grid.Row="2">
<GroupBox Grid.Column="1" Grid.Row="1" Header="G-PTM-D Filter Stringency" Margin="0,-10,0,24">
<ListBox x:Name="FilterOptionsListBox" ItemsSource="{Binding FilterOptions}" SelectionMode="Multiple"
Margin="0,0,0,0" Padding="0" BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,0,0" VerticalAlignment="Center"
ToolTip="{Binding Summary}">
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Name}" Margin="8,0,0,0" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
<StackPanel Grid.Column="0" Grid.Row="1">
<TreeView x:Name="gptmdModsTreeView" ItemsSource="{Binding}" DataContext="{x:Type guiFunctions:ModTypeForTreeViewModel}" Height="400">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type guiFunctions:ModTypeForTreeViewModel}" ItemsSource="{Binding Children}">
Expand Down
31 changes: 31 additions & 0 deletions MetaMorpheus/GUI/TaskWindows/GPTMDTaskWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Windows.Input;
using TaskLayer;
using GuiFunctions;
using EngineLayer;

namespace MetaMorpheusGUI
{
Expand All @@ -23,6 +24,7 @@ namespace MetaMorpheusGUI
/// </summary>
public partial class GptmdTaskWindow : Window
{
public ObservableCollection<GptmdFilterViewModel> FilterOptions { get; } = new();
private readonly ObservableCollection<ModTypeForTreeViewModel> FixedModTypeForTreeViewObservableCollection = new ObservableCollection<ModTypeForTreeViewModel>();
private readonly ObservableCollection<ModTypeForTreeViewModel> VariableModTypeForTreeViewObservableCollection = new ObservableCollection<ModTypeForTreeViewModel>();
private readonly ObservableCollection<ModTypeForLoc> LocalizeModTypeForTreeViewObservableCollection = new ObservableCollection<ModTypeForLoc>();
Expand All @@ -38,6 +40,7 @@ public GptmdTaskWindow(GptmdTask myGPTMDtask)

AutomaticallyAskAndOrUpdateParametersBasedOnProtease = false;
PopulateChoices();
FilterOptionsListBox.ItemsSource = FilterOptions;
UpdateFieldsFromTask(TheTask);
AutomaticallyAskAndOrUpdateParametersBasedOnProtease = true;
DeisotopingControl.DataContext = DeconHostViewModel;
Expand Down Expand Up @@ -183,6 +186,11 @@ private void UpdateFieldsFromTask(GptmdTask task)
{
ye.VerifyCheckState();
}

foreach (var filter in FilterOptions)
{
filter.IsSelected = TheTask.GptmdParameters.GptmdFilters.Any(f => f.GetType() == filter.Filter.GetType());
}
}

private void PopulateChoices()
Expand Down Expand Up @@ -240,6 +248,28 @@ private void PopulateChoices()
}
}
gptmdModsTreeView.DataContext = GptmdModTypeForTreeViewObservableCollection;

FilterOptions.Clear();
FilterOptions.Add(new GptmdFilterViewModel(
new ImprovedScoreFilter(),
"Improved Score",
"Requires that the new score is greater than the original score."
));
FilterOptions.Add(new GptmdFilterViewModel(
new DualDirectionalIonCoverageFilter(),
"Dual Directional Ion Coverage",
"Requires the mod site to be covered by at least one N-terminal and one C-terminal ion. That is, ions from both directions must include the mod, even if not flanking it."
));
FilterOptions.Add(new GptmdFilterViewModel(
new UniDirectionalIonCoverageFilter(),
"Uni-Directional Ion Coverage",
"Requires the mod site to be covered by at least one N-terminal or one C-terminal ion. That is, ions from one direction must include the mod, even if not flanking it."
));
FilterOptions.Add(new GptmdFilterViewModel(
new FlankingIonCoverageFilter(),
"Flanking Ion Coverage",
"Requires flanking ions — a fragment from *before* and one from *after* the mod site, regardless of fragmentation direction."
));
}

private void CancelButton_Click(object sender, RoutedEventArgs e)
Expand Down Expand Up @@ -489,6 +519,7 @@ private void SaveButton_Click(object sender, RoutedEventArgs e)
{
TheTask.GptmdParameters.ListOfModsGptmd.AddRange(heh.Children.Where(b => b.Use).Select(b => (b.Parent.DisplayName, b.ModName)));
}
TheTask.GptmdParameters.GptmdFilters = FilterOptions.Where(f => f.IsSelected).Select(f => f.Filter).ToList();

TheTask.CommonParameters = commonParamsToSave;

Expand Down
25 changes: 25 additions & 0 deletions MetaMorpheus/GuiFunctions/ViewModels/GptmdFilterViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using EngineLayer;

namespace GuiFunctions;

public class GptmdFilterViewModel : BaseViewModel
{
private bool _isSelected = false;
public IGptmdFilter Filter { get; }
public string Name { get; }
public string Summary { get; }

public bool IsSelected
{
get => _isSelected;
set { _isSelected = value; OnPropertyChanged(nameof(IsSelected)); }
}

public GptmdFilterViewModel(IGptmdFilter filter, string name, string summary, bool isSelected = true)
{
Filter = filter;
Name = name;
Summary = summary;
_isSelected = isSelected;
}
}
3 changes: 3 additions & 0 deletions MetaMorpheus/TaskLayer/GPTMDTask/GPTMDParameters.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using EngineLayer.Gptmd;

namespace EngineLayer
{
Expand All @@ -10,6 +11,7 @@ public class GptmdParameters
/// </summary>
public GptmdParameters()
{
GptmdFilters = new();
ListOfModsGptmd = GlobalVariables.AllModsKnown.Where(b =>
b.ModificationType.Equals("Common Artifact")
|| b.ModificationType.Equals("Common Biological")
Expand All @@ -19,5 +21,6 @@ public GptmdParameters()
}

public List<(string, string)> ListOfModsGptmd { get; set; }
public List<IGptmdFilter> GptmdFilters { get; set; }
}
}
2 changes: 1 addition & 1 deletion MetaMorpheus/TaskLayer/GPTMDTask/GPTMDTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ protected override MyTaskResults RunSpecific(string OutputFolder, List<DbForTask
// GPTMD doesn't work as well if you do FDR on a file-by-file basis. Presumably this is because it takes multiple files to get enough PSMs for all the different notches
new FdrAnalysisEngine(allPsms, tempSearchMode.NumNotches, CommonParameters, this.FileSpecificParameters, new List<string> { taskId }, doPEP: false).Run();
Dictionary<string, HashSet<Tuple<int, Modification>>> allModDictionary = new();
new GptmdEngine(allPsms, gptmdModifications, combos, filePathToPrecursorMassTolerance, CommonParameters, this.FileSpecificParameters, new List<string> { taskId }, allModDictionary).Run();
new GptmdEngine(allPsms, gptmdModifications, combos, filePathToPrecursorMassTolerance, CommonParameters, this.FileSpecificParameters, new List<string> { taskId }, allModDictionary, GptmdParameters.GptmdFilters).Run();

//Move this text after search because proteins don't get loaded until search begins.
ProseCreatedWhileRunning.Append("The combined search database contained " + proteinList.Count(p => !p.IsDecoy) + $" non-decoy {GlobalVariables.AnalyteType.GetBioPolymerLabel().ToLower()} entries including " + proteinList.Where(p => p.IsContaminant).Count() + " contaminant sequences. ");
Expand Down
21 changes: 18 additions & 3 deletions MetaMorpheus/TaskLayer/MetaMorpheusTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using SpectralAveraging;
using Omics;
using Omics.Digestion;
using Omics.Fragmentation.Peptide;
using Omics.Modifications;
using Omics.SpectrumMatch;
using UsefulProteomicsDatabases;
Expand All @@ -25,8 +24,6 @@
using Proteomics.ProteolyticDigestion;
using Transcriptomics;
using Transcriptomics.Digestion;
using Easy.Common.Extensions;
using Readers;

namespace TaskLayer
{
Expand Down Expand Up @@ -134,6 +131,24 @@ public abstract class MetaMorpheusTask
.ConfigureType<OxyriboAveragine>(type => type
.WithConversionFor<TomlString>(convert => convert
.ToToml(custom => custom.GetType().Name)))
.ConfigureType<List<IGptmdFilter>>(type => type
.WithConversionFor<TomlString>(convert => convert
.ToToml(filters => string.Join("\t", filters.Select(f => f.GetType().Name)))
.FromToml(tmlString => tmlString.Value
.Split('\t', StringSplitOptions.RemoveEmptyEntries)
.Select(typeName =>
// Find the type in the current AppDomain by name
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.FirstOrDefault(t => t.Name == typeName && typeof(IGptmdFilter).IsAssignableFrom(t))
)
.Where(t => t != null)
.Select(t => Activator.CreateInstance(t) as IGptmdFilter)
.Where(f => f != null)
.ToList()
)
)
)
);


Expand Down
Loading