Skip to content

Commit 05d9e41

Browse files
committed
Merge branch 'precursorFragmentGroup' of https://github.com/zhuoxinshi/MetaMorpheus into precursorFragmentGroup
2 parents c3210b2 + 7d49359 commit 05d9e41

30 files changed

+65658
-1065
lines changed

.github/workflows/DockerUpload.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Docker Uploads
2+
3+
on:
4+
push:
5+
branches: [ master ] # dev tag on merges to master
6+
release:
7+
types: [released, edited] # latest + version tag on new releases
8+
9+
# Docker (Linux) push on branch merges (not PRs)
10+
jobs:
11+
docker-dev:
12+
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
13+
runs-on: ubuntu-latest
14+
timeout-minutes: 20
15+
env:
16+
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
17+
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
- name: Set up .NET
22+
uses: actions/setup-dotnet@v4
23+
with:
24+
dotnet-version: 8.0.x
25+
- name: Restore dependencies
26+
run: dotnet restore ./MetaMorpheus/MetaMorpheus.sln
27+
- name: Build (CMD)
28+
run: dotnet publish /p:Configuration=Release ./MetaMorpheus/CMD/CMD.csproj -f net8.0
29+
- name: Login to Docker Hub
30+
uses: docker/login-action@v3
31+
with:
32+
username: ${{ env.DOCKER_USER }}
33+
password: ${{ env.DOCKER_TOKEN }}
34+
- name: Build and push :dev
35+
uses: docker/build-push-action@v6
36+
with:
37+
context: "${{ github.workspace }}/MetaMorpheus"
38+
push: true
39+
tags: smithchemwisc/metamorpheus:dev
40+
41+
# Push to "latest" and specific release tags on new release
42+
push-release:
43+
if: github.event_name == 'release'
44+
runs-on: ubuntu-latest
45+
timeout-minutes: 20
46+
env:
47+
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
48+
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
49+
steps:
50+
- name: Checkout
51+
uses: actions/checkout@v4
52+
- name: Set up .NET
53+
uses: actions/setup-dotnet@v4
54+
with:
55+
dotnet-version: 8.0.x
56+
- name: Restore dependencies
57+
run: dotnet restore ./MetaMorpheus/MetaMorpheus.sln
58+
- name: Build (CMD)
59+
run: dotnet publish /p:Configuration=Release ./MetaMorpheus/CMD/CMD.csproj -f net8.0
60+
- name: Login to Docker Hub
61+
uses: docker/login-action@v3
62+
with:
63+
username: ${{ env.DOCKER_USER }}
64+
password: ${{ env.DOCKER_TOKEN }}
65+
- name: Build and push :latest
66+
uses: docker/build-push-action@v6
67+
with:
68+
context: "${{ github.workspace }}/MetaMorpheus"
69+
push: true
70+
tags: smithchemwisc/metamorpheus:${{ github.event.release.tag_name }}, smithchemwisc/metamorpheus:latest

MetaMorpheus/Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
## Base image is the Alpine Linux distro with .NET Core runtime
2-
FROM mcr.microsoft.com/dotnet/runtime:6.0-alpine
2+
FROM mcr.microsoft.com/dotnet/runtime:8.0-alpine
33

44
## Copies contents of the build folder into the Docker image
5-
ADD CMD/bin/Release/net6.0/ /metamorpheus/
5+
ADD CMD/bin/Release/net8.0/ /MetaMorpheus/
66

77
## Installs bash (seemingly necessary for NextFlow)
88
RUN apk add --no-cache bash
@@ -11,4 +11,4 @@ RUN apk add --no-cache bash
1111
RUN apk add --no-cache procps
1212

1313
## Set the entrypoint of the Docker image to CMD.dll
14-
ENTRYPOINT ["dotnet", "/metamorpheus/CMD.dll"]
14+
ENTRYPOINT ["dotnet", "/MetaMorpheus/CMD.dll"]
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
## Base image is the Alpine Linux distro with .NET Core runtime
2-
FROM mcr.microsoft.com/dotnet/runtime:6.0-nanoserver-1809
1+
## This is an experimental script.
2+
## The docker container built from this script runs a stripped down version of windows
3+
## The docker container built by this script has never been deployed to docker hub
4+
5+
## Base image is a windows nanoserver with .NET Core runtime
6+
FROM mcr.microsoft.com/dotnet/runtime:8.0-nanoserver-1809
37

48
## Copies contents of the installer folder into the Docker image
59
ADD /InstalledFiles/ /metamorpheus/
610

711
## Set the entrypoint of the Docker image to CMD.exe
8-
ENTRYPOINT ["/metamorpheus/CMD.exe"]
12+
ENTRYPOINT ["/metamorpheus/CMD.exe"]

MetaMorpheus/EngineLayer/Calibration/DataPointAcquisitionEngine.cs

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
using Chemistry;
22
using MassSpectrometry;
33
using MzLibUtil;
4-
using Proteomics.AminoAcidPolymer;
54
using System;
6-
using System.Collections.Concurrent;
75
using System.Collections.Generic;
86
using System.Linq;
97
using System.Threading.Tasks;
@@ -76,13 +74,6 @@ protected override MetaMorpheusEngineResults RunSpecific()
7674

7775
var representativeSinglePeptide = identification.BestMatchingBioPolymersWithSetMods.First().SpecificBioPolymer;
7876

79-
// Get the peptide, don't forget to add the modifications!!!!
80-
var SequenceWithChemicalFormulas = representativeSinglePeptide.SequenceWithChemicalFormulas;
81-
if (SequenceWithChemicalFormulas == null || representativeSinglePeptide.AllModsOneIsNterminus.Any(b => b.Value.NeutralLosses != null))
82-
continue;
83-
84-
Peptide coolPeptide = new Peptide(SequenceWithChemicalFormulas);
85-
8677
List<LabeledDataPoint> ms2tuple = SearchMS2Spectrum(GoodScans[matchIndex], identification, ProductMassTolerance);
8778

8879
lock (_ms2Lock)
@@ -91,7 +82,7 @@ protected override MetaMorpheusEngineResults RunSpecific()
9182
}
9283

9384
// Calculate theoretical isotopic distribution of the full peptide
94-
var dist = IsotopicDistribution.GetDistribution(coolPeptide.GetChemicalFormula(), FineResolutionForIsotopeDistCalculation, 0.001);
85+
var dist = IsotopicDistribution.GetDistribution(representativeSinglePeptide.ThisChemicalFormula, FineResolutionForIsotopeDistCalculation, 0.001);
9586

9687
double[] theoreticalMasses = dist.Masses.ToArray();
9788
double[] theoreticalIntensities = dist.Intensities.ToArray();
@@ -137,6 +128,7 @@ protected override MetaMorpheusEngineResults RunSpecific()
137128
theIndex = direction == 1 ? ms2spectrumIndex + 1 : identification.PrecursorScanNumber ?? ms2spectrumIndex;
138129

139130
bool addedAscan = true;
131+
bool positiveMode = identification.ScanPrecursorCharge > 0;
140132

141133
int highestKnownChargeForThisPeptide = peptideCharge;
142134
while (theIndex >= 1 && theIndex <= MyMsDataFile.NumSpectra && addedAscan) //as long as we're finding the peptide in ms1 scans
@@ -157,16 +149,26 @@ protected override MetaMorpheusEngineResults RunSpecific()
157149
break;
158150

159151
bool startingToAddCharges = false;
160-
int chargeToLookAt = 1;
152+
int chargeToLookAt = positiveMode ? 1 : -1;
153+
int chargeLimit = positiveMode ? highestKnownChargeForThisPeptide + 1 : highestKnownChargeForThisPeptide - 1;
154+
161155
do
162156
{
163-
if (theoreticalMasses[0].ToMz(chargeToLookAt) > scanWindowRange.Maximum)
157+
double mz = theoreticalMasses[0].ToMz(chargeToLookAt);
158+
159+
// If m/z is above the scan window, try next charge
160+
if (mz > scanWindowRange.Maximum)
164161
{
165-
chargeToLookAt++;
162+
chargeToLookAt += positiveMode ? 1 : -1;
166163
continue;
167164
}
168-
if (theoreticalMasses[0].ToMz(chargeToLookAt) < scanWindowRange.Minimum)
165+
166+
// If m/z is below the scan window, break early, more charge will only make the mz smaller.
167+
if (mz < scanWindowRange.Minimum)
168+
{
169169
break;
170+
}
171+
170172
var trainingPointsToAverage = new List<LabeledDataPoint>();
171173
foreach (double a in theoreticalMasses)
172174
{
@@ -187,15 +189,17 @@ protected override MetaMorpheusEngineResults RunSpecific()
187189
var closestPeakIndex = fullMS1spectrum.GetClosestPeakIndex(theMZ);
188190
var closestPeakMZ = fullMS1spectrum.XArray[closestPeakIndex];
189191

190-
highestKnownChargeForThisPeptide = Math.Max(highestKnownChargeForThisPeptide, chargeToLookAt);
192+
highestKnownChargeForThisPeptide = positiveMode
193+
? Math.Max(highestKnownChargeForThisPeptide, chargeToLookAt)
194+
: Math.Min(highestKnownChargeForThisPeptide, chargeToLookAt);
191195
trainingPointsToAverage.Add(new LabeledDataPoint(closestPeakMZ, -1, double.NaN, double.NaN, Math.Log(fullMS1spectrum.YArray[closestPeakIndex]), theMZ, null));
192196
}
193197
// If started adding and suddenly stopped, go to next one, no need to look at higher charges
194198
if (trainingPointsToAverage.Count == 0 && startingToAddCharges)
195199
{
196200
break;
197201
}
198-
if ((trainingPointsToAverage.Count == 0 || (trainingPointsToAverage.Count == 1 && theoreticalIntensities[0] < 0.65)) && (peptideCharge <= chargeToLookAt))
202+
if ((trainingPointsToAverage.Count == 0 || (trainingPointsToAverage.Count == 1 && theoreticalIntensities[0] < 0.65)) && (positiveMode ? peptideCharge <= chargeToLookAt : peptideCharge >= chargeToLookAt))
199203
{
200204
break;
201205
}
@@ -213,8 +217,8 @@ protected override MetaMorpheusEngineResults RunSpecific()
213217
trainingPointsToAverage.Select(b => b.TheoreticalMz).Average(),
214218
identification));
215219
}
216-
chargeToLookAt++;
217-
} while (chargeToLookAt <= highestKnownChargeForThisPeptide + 1);
220+
chargeToLookAt += positiveMode ? 1 : -1;
221+
} while (positiveMode ? chargeToLookAt <= chargeLimit : chargeToLookAt >= chargeLimit);
218222
theIndex += direction;
219223
}
220224
return (result, numMs1MassChargeCombinationsConsidered, numMs1MassChargeCombinationsThatAreIgnoredBecauseOfTooManyPeaks);
@@ -240,16 +244,17 @@ private static List<LabeledDataPoint> SearchMS2Spectrum(Ms2ScanWithSpecificMass
240244
if(envelopesThatMatch.Count == 0)
241245
continue;
242246
//only allow one envelope per charge state
243-
bool[] chargeStateFound = new bool[envelopesThatMatch.Max(x => x.Charge) + 1];
247+
bool[] chargeStateFound = new bool[envelopesThatMatch.Max(x => Math.Abs(x.Charge)) + 1];
244248

245249
foreach (var envelopeThatMatched in envelopesThatMatch)
246250
{
251+
int charge = Math.Abs(envelopeThatMatched.Charge);
247252
//if we haven't seen this charge state already
248-
if (!chargeStateFound[envelopeThatMatched.Charge])
253+
if (!chargeStateFound[charge])
249254
{
250-
chargeStateFound[envelopeThatMatched.Charge] = true;
255+
chargeStateFound[charge] = true;
251256

252-
double exptPeakMz = envelopeThatMatched.MonoisotopicMass.ToMz(envelopeThatMatched.Charge);
257+
double exptPeakMz = envelopeThatMatched.MonoisotopicMass.ToMz(charge);
253258
double exptPeakIntensity = envelopeThatMatched.TotalIntensity;
254259
double injTime = ms2DataScan.TheScan.InjectionTime ?? double.NaN;
255260

@@ -259,8 +264,8 @@ private static List<LabeledDataPoint> SearchMS2Spectrum(Ms2ScanWithSpecificMass
259264
ms2DataScan.OneBasedScanNumber,
260265
Math.Log(ms2DataScan.TotalIonCurrent),
261266
Math.Log(injTime),
262-
Math.Log(exptPeakIntensity),
263-
matchedIon.NeutralTheoreticalProduct.NeutralMass.ToMz(envelopeThatMatched.Charge),
267+
Math.Log(exptPeakIntensity),
268+
matchedIon.NeutralTheoreticalProduct.NeutralMass.ToMz(charge),
264269
identification));
265270
}
266271
}

MetaMorpheus/EngineLayer/ClassicSearch/ClassicSearchEngine.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Collections.Concurrent;
1212
using EngineLayer.Util;
1313
using Omics;
14+
using Readers.SpectralLibrary;
1415

1516
namespace EngineLayer.ClassicSearch
1617
{

MetaMorpheus/EngineLayer/ClassicSearch/MiniClassicSearchEngine.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System;
88
using System.Collections.Generic;
99
using System.Linq;
10+
using Readers.SpectralLibrary;
1011

1112
namespace EngineLayer.ClassicSearch
1213
{

MetaMorpheus/EngineLayer/GlycoSearch/Glycan.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,13 @@ public static List<Glycan> Struct2Glycan(string theGlycanStruct, int id, bool is
313313
else
314314
{
315315
GlycanType glycanType = GlycanType.N_glycan;
316-
Glycan N_glycan = new Glycan(theGlycanStruct, mass, kind, glycanIons.OrderBy(p => p.IonMass).ToList(), false, "N", glycanType);
317-
N_glycan.GlyId = id;
318-
glycans.Add(N_glycan);
316+
Glycan N_glycan_Nxs = new Glycan(theGlycanStruct, mass, kind, glycanIons.OrderBy(p => p.IonMass).ToList(), false, "Nxs", glycanType);
317+
N_glycan_Nxs.GlyId = id;
318+
Glycan N_glycan_Nxt = new Glycan(theGlycanStruct, mass, kind, glycanIons.OrderBy(p => p.IonMass).ToList(), false, "Nxt", glycanType);
319+
N_glycan_Nxt.GlyId = id+1;
320+
321+
glycans.Add(N_glycan_Nxs);
322+
glycans.Add(N_glycan_Nxt);
319323
return glycans;
320324
}
321325

MetaMorpheus/EngineLayer/GlycoSearch/GlycanDatabase.cs

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,24 @@ public static IEnumerable<Glycan> LoadKindGlycan(string filePath, bool ToGenerat
9191
}
9292
else // Load the N-glycan with one motif : N
9393
{
94-
var nGlycan = new Glycan(kind, "N", GlycanType.N_glycan); // Use the kind[] to create a glycan object.
95-
nGlycan.GlyId = id;
94+
var nGlycan_Nxs = new Glycan(kind, "Nxs", GlycanType.N_glycan); // Use the kind[] to create a glycan object.
95+
nGlycan_Nxs.GlyId = id;
9696
id++;
9797
if (ToGenerateIons)
9898
{
99-
nGlycan.Ions = OGlycanCompositionCombinationChildIons(kind);
99+
nGlycan_Nxs.Ions = OGlycanCompositionCombinationChildIons(kind);
100100
}
101-
yield return nGlycan;
101+
yield return nGlycan_Nxs;
102+
103+
104+
var nGlycan_Nxt = new Glycan(kind, "Nxt", GlycanType.N_glycan); // Use the kind[] to create a glycan object.
105+
nGlycan_Nxt.GlyId = id;
106+
id++;
107+
if (ToGenerateIons)
108+
{
109+
nGlycan_Nxt.Ions = OGlycanCompositionCombinationChildIons(kind);
110+
}
111+
yield return nGlycan_Nxt;
102112
}
103113
}
104114
}
@@ -138,20 +148,14 @@ public static IEnumerable<Glycan> LoadStructureGlycan(string filePath, bool IsOG
138148
{
139149
string line = glycans.ReadLine(); // Read the line from the database file. Ex. (N(H(A))(A))
140150

141-
// For each O-glycan, two versions will be generated: one modified on serine (S), and the other on threonine (T).
142-
if (IsOGlycan)
143-
{
144-
foreach (var glycan in Glycan.Struct2Glycan(line, id, IsOGlycan)) // Modify the line to handle multiple Glycan objects returned by Struct2Glycan.
145-
{
146-
yield return glycan;
147-
}
148-
id = id +2; // Each line will generate two glycan objects
149-
}
150-
//For N-glycan, we only generate one kind of N-glycan.
151-
else
151+
// For each glycan, two versions will be generated:
152+
// For O-glycan, one modified on serine (S), and the other on threonine (T).
153+
// For N-glycan, one modified on N-glycosylation on motif Asn-X-Ser(Nxs), and the other on Asn-X-Thr(Nxt).
154+
foreach (var glycan in Glycan.Struct2Glycan(line, id, IsOGlycan)) // Modify the line to handle multiple Glycan objects returned by Struct2Glycan.
152155
{
153-
yield return Glycan.Struct2Glycan(line, id++, IsOGlycan).FirstOrDefault(); // For N-Glycan, we only return the first Glycan object.
156+
yield return glycan;
154157
}
158+
id = id + 2; // Each line will generate two glycan objects
155159
}
156160
}
157161
}

MetaMorpheus/EngineLayer/GlycoSearch/GlycoSearchEngine.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -481,20 +481,29 @@ private void FindOGlycan(Ms2ScanWithSpecificMass theScan, int scanIndex, int sco
481481

482482
private void FindNGlycan(Ms2ScanWithSpecificMass theScan, int scanIndex, int scoreCutOff, PeptideWithSetModifications theScanBestPeptide, int ind, double possibleGlycanMassLow, double[] oxoniumIonIntensities, ref List<GlycoSpectralMatch> possibleMatches)
483483
{
484-
List<int> modPos = GlycoSpectralMatch.GetPossibleModSites(theScanBestPeptide, new string[] { "Nxt", "Nxs" }).Select(p => p.Key).ToList();
485-
if (modPos.Count < 1)
484+
List<int> modPos_Nxs = GlycoSpectralMatch.GetPossibleModSites(theScanBestPeptide, new string[] { "Nxs" }).Select(p => p.Key).ToList();
485+
List<int> modPos_Nxt = GlycoSpectralMatch.GetPossibleModSites(theScanBestPeptide, new string[] { "Nxt" }).Select(p => p.Key).ToList();
486+
if (modPos_Nxs.Count < 1 && modPos_Nxt.Count < 1) // if there is no possible glycosylation site, we can skip this peptide.
486487
{
487488
return;
488489
}
489490

490491
int iDLow = GlycoPeptides.BinarySearchGetIndex(NGlycans.Select(p => (double)p.Mass / 1E5).ToArray(), possibleGlycanMassLow);
491-
492492
while (iDLow < NGlycans.Length && PrecusorSearchMode.Within(theScan.PrecursorMass, theScanBestPeptide.MonoisotopicMass + (double)NGlycans[iDLow].Mass / 1E5))
493493
{
494494
double bestLocalizedScore = scoreCutOff;
495495
int bestSite = 0;
496496
List<MatchedFragmentIon> bestMatchedIons = new List<MatchedFragmentIon>();
497497
PeptideWithSetModifications[] peptideWithSetModifications = new PeptideWithSetModifications[1];
498+
499+
// Get the correct modification position based on the glycan target type
500+
List<int> modPos = NGlycans[iDLow].Target.ToString() == "Nxs" ? modPos_Nxs : modPos_Nxt;
501+
if (modPos.Count < 1)
502+
{
503+
iDLow++;
504+
continue; // if there is no possible glycosylation site, we can skip this glycan.
505+
}
506+
498507
foreach (int possibleSite in modPos)
499508
{
500509
var testPeptide = GlycoPeptides.GenerateGlycopeptide(possibleSite, theScanBestPeptide, NGlycans[iDLow]);

0 commit comments

Comments
 (0)