Ejemplo n.º 1
0
        // Unit test of annotations handler class
        private static void TestSpectrumPeakAnnotations()
        {
            var noNote      = SpectrumPeakAnnotation.Create(new CustomIon(null, Adduct.FromChargeNoMass(-2), 100, 101, "noNote"), null);
            var noName      = SpectrumPeakAnnotation.Create(new CustomIon(null, Adduct.FromChargeNoMass(-2), 100, 101, null), "noted");
            var fullIon     = new CustomIon("C12H5O3", Adduct.FromStringAssumeChargeOnly("M-H2O+"), null, null, "full");
            var full        = SpectrumPeakAnnotation.Create(fullIon, "noted");
            var fullToo     = SpectrumPeakAnnotation.Create(fullIon, "noted");
            var nameOnlyTab = SpectrumPeakAnnotation.Create(new CustomIon(null, Adduct.FromChargeNoMass(-2), 100, 101, "test\ttabs"), "noted"); // Check tab escape
            var fullIonTabs = new CustomIon("C12H5", Adduct.FromChargeNoMass(-2), null, null, "full\ttabs");
            var tabbedFull  = SpectrumPeakAnnotation.Create(fullIonTabs, "noted\ttabs");                                                        // Check tab escape

            Assume.AreEqual(full, fullToo);
            Assume.AreNotEqual(full, tabbedFull);
            var tests = new List <List <SpectrumPeakAnnotation> >
            {
                new List <SpectrumPeakAnnotation> {
                    noNote, noName, full
                },
                new List <SpectrumPeakAnnotation> {
                    fullToo, nameOnlyTab, tabbedFull
                }
            };
            var cached    = SpectrumPeakAnnotation.ToCacheFormat(tests);
            var roundtrip = SpectrumPeakAnnotation.FromCacheFormat(cached);
            int i         = 0;

            foreach (var annotsPerPeak in tests)
            {
                Assume.IsTrue(CollectionUtil.EqualsDeep(annotsPerPeak, roundtrip[i++]));
            }
        }
Ejemplo n.º 2
0
        private static void TestAddingTransition()
        {
            RunUI(() =>
            {
                SkylineWindow.ExpandPrecursors();
                var node = SkylineWindow.SequenceTree.Nodes[0].FirstNode.FirstNode;
                SkylineWindow.SequenceTree.SelectedNode = node;
            });
            var moleculeDlg = ShowDialog <EditCustomMoleculeDlg>(SkylineWindow.AddSmallMolecule);

            RunUI(() =>
            {
                moleculeDlg.FormulaBox.Formula = C12H12;
                moleculeDlg.NameText           = testNametextA;
                moleculeDlg.Adduct             = Adduct.NonProteomicProtonatedFromCharge(1);
            });
            OkDialog(moleculeDlg, moleculeDlg.OkDialog);
            var newDoc     = SkylineWindow.Document;
            var compareIon = new CustomIon(C12H12, Adduct.NonProteomicProtonatedFromCharge(1), null, null, testNametextA);

            Assert.AreEqual(compareIon, newDoc.MoleculeTransitions.ElementAt(0).Transition.CustomIon);
            Assert.AreEqual(1, newDoc.MoleculeTransitions.ElementAt(0).Transition.Charge);

            // Verify that tree selection doesn't change just because we changed an ID object
            // (formerly the tree node would collapse and focus would jump up a level)
            RunUI(() =>
            {
                Assert.AreEqual(SkylineWindow.SequenceTree.SelectedNode, SkylineWindow.SequenceTree.Nodes[0].FirstNode.FirstNode);
            });
        }
Ejemplo n.º 3
0
        public static SpectrumPeakAnnotation Create(SmallMoleculeLibraryAttributes mol, Adduct adduct, string comment, double?mzTheoretical)
        {
            double?massTheoretical = mzTheoretical.HasValue ? adduct.MassFromMz(mzTheoretical.Value, MassType.Monoisotopic).Value : (double?)null;
            var    ion             = new CustomIon(mol, adduct, massTheoretical);

            if ((mzTheoretical ?? 0.0) > 0)
            {
                if (Equals(ion.MonoisotopicMassMz, 0.0))
                {
                    // We didn't have enough info to calculate mz, use the provided theoretical value
                    var massMono    = adduct.MassFromMz(mzTheoretical.Value, MassType.Monoisotopic);
                    var massAverage = adduct.MassFromMz(mzTheoretical.Value, MassType.Average);
                    ion = new CustomIon(ion.GetSmallMoleculeLibraryAttributes(), ion.Adduct, massMono, massAverage);
                }
                else
                {
                    // Check our calculated value against provided theoretical value, allowing quite a lot of wiggle (not everybody is using the same precision out there)
                    var delta = .5; // Generous error for sanity check
                    if (Math.Abs(ion.MonoisotopicMassMz - mzTheoretical.Value) > delta)
                    {
                        Assume.Fail(string.Format(@"SpectrumPeakAnnotation: mzTheoretical {0} and mzActual {1} disagree by more than {2} in {3} {4}",
                                                  mzTheoretical, ion.MonoisotopicMassMz, delta, ion, comment ?? string.Empty));
                    }
                }
            }
            return(ion.IsEmpty && string.IsNullOrEmpty(comment) ?
                   EMPTY :
                   new SpectrumPeakAnnotation(ion, comment));
        }
Ejemplo n.º 4
0
        // Deserialize  a collection of annotations (with multiple annotations per peak)
        public static List <List <SpectrumPeakAnnotation> > FromCacheFormat(string cached)
        {
            var result = new List <List <SpectrumPeakAnnotation> >();

            if (string.IsNullOrEmpty(cached))
            {
                return(result);
            }
            var annotationsPerPeak = cached.Split('\n');

            foreach (var annotations in annotationsPerPeak)
            {
                if (string.IsNullOrEmpty(annotations))
                {
                    result.Add(null);
                }
                else
                {
                    var list = new List <SpectrumPeakAnnotation>();
                    foreach (var annot in annotations.Split('\r'))
                    {
                        var lastTab = annot.LastIndexOf(TextUtil.SEPARATOR_TSV_STR, StringComparison.Ordinal);
                        var ion     = lastTab < 0 ? CustomIon.EMPTY : CustomIon.FromTSV(annot.Substring(0, lastTab));
                        var comment = lastTab < 0 ? string.Empty : annot.Substring(lastTab + 1).UnescapeTabAndCrLf();
                        list.Add(ion.IsEmpty && string.IsNullOrEmpty(comment) ? EMPTY : new SpectrumPeakAnnotation(ion, comment));
                    }
                    result.Add(list);
                }
            }
            return(result);
        }
Ejemplo n.º 5
0
        public override void ReadXml(XmlReader reader)
        {
            // Read tag attributes
            base.ReadXml(reader);
            Fragment = reader.GetAttribute(ATTR.cut);
            if (IsFragment)
            {
                Restrict          = reader.GetAttribute(ATTR.no_cut);
                Terminus          = reader.GetAttribute(ATTR.sense, ToSeqTerminus);
                MinFragmentLength = reader.GetNullableIntAttribute(ATTR.min_length) ??
                                    DEFAULT_MIN_FRAGMENT_LENGTH;
            }
            else
            {
                var charges = TextUtil.ParseInts(reader.GetAttribute(ATTR.charges)); // Old version?
                if (charges.Length > 1)
                {
                    throw new InvalidDataException(Resources.MeasuredIon_ReadXml_Multiple_charge_states_for_custom_ions_are_no_longer_supported_);
                }
                var    parsedIon = CustomIon.Deserialize(reader);
                Adduct adduct;
                if (charges.Any())  // Old style - fix it up a little for our revised ideas about custom ion ionization
                {
                    adduct = Adduct.FromChargeNoMass(charges[0]);
                    if (string.IsNullOrEmpty(parsedIon.NeutralFormula)) // Adjust the user-supplied masses
                    {
                        SettingsCustomIon = new SettingsCustomIon(parsedIon.NeutralFormula, adduct,
                                                                  Math.Round(parsedIon.MonoisotopicMass + charges[0] * BioMassCalc.MONOISOTOPIC.GetMass(BioMassCalc.H), SequenceMassCalc.MassPrecision), // Assume user provided neutral mass.  Round new value easiest XML roundtripping.
                                                                  Math.Round(parsedIon.AverageMass + charges[0] * BioMassCalc.AVERAGE.GetMass(BioMassCalc.H), SequenceMassCalc.MassPrecision),           // Assume user provided neutral mass.  Round new value easiest XML roundtripping.
                                                                  parsedIon.Name);
                    }
                    else // Adjust the formula to include ion atoms
                    {
                        if (charges[0] > 1) // XML deserializer will have added an H already
                        {
                            var adductProtonated = Adduct.FromChargeProtonated(charges[0] - 1);
                            var formula          = adductProtonated.ApplyToFormula(parsedIon.NeutralFormula);
                            parsedIon = new CustomIon(formula, adduct, parsedIon.MonoisotopicMass, parsedIon.AverageMass, Name);
                        }
                    }
                }
                else
                {
                    adduct = Adduct.FromStringAssumeChargeOnly(reader.GetAttribute(ATTR.charge)); // Ionization mass is already in formula
                }
                if (SettingsCustomIon == null)
                {
                    SettingsCustomIon = new SettingsCustomIon(parsedIon.NeutralFormula, adduct,
                                                              parsedIon.MonoisotopicMass,
                                                              parsedIon.AverageMass,
                                                              parsedIon.Name);
                }
                IsOptional = reader.GetBoolAttribute(ATTR.optional);
            }
            // Consume tag
            reader.Read();

            Validate();
        }
Ejemplo n.º 6
0
        // Cannot find a way to use anything but a file with SQLite
        // public const string _bibliospecLiteLibPath = @"C:\Libraries\yeast.blib";

        private List <SpectrumPeakAnnotation> MakeTestPeakAnnotation(Adduct adduct, float mz, string name, string note)
        {
            var ion = new CustomIon(null, adduct,
                                    adduct.MassFromMz(mz, MassType.Monoisotopic),
                                    adduct.MassFromMz(mz, MassType.Average),
                                    name);
            var annot = SpectrumPeakAnnotation.Create(ion, note);

            return(new List <SpectrumPeakAnnotation> {
                annot
            });
        }
Ejemplo n.º 7
0
        private static void TestAddingTransitionAsMasses()
        {
            RunUI(() =>
            {
                SkylineWindow.ExpandPrecursors();
                var node = SkylineWindow.SequenceTree.Nodes[0].FirstNode.FirstNode;
                SkylineWindow.SequenceTree.SelectedNode = node;
            });
            var editMoleculeDlg = ShowDialog <EditCustomMoleculeDlg>(SkylineWindow.AddSmallMolecule);
            var formula         = C12H12;
            var monoMass        = BioMassCalc.MONOISOTOPIC.CalculateMassFromFormula(formula);
            var averageMass     = BioMassCalc.AVERAGE.CalculateMassFromFormula(formula);
            var adduct          = Adduct.M_PLUS;

            RunUI(() =>
            {
                // Verify the interaction of explicitly set mz and charge without formula
                editMoleculeDlg.NameText = testNametextA;
                editMoleculeDlg.Adduct   = adduct;
                var mzMono    = editMoleculeDlg.Adduct.MzFromNeutralMass(monoMass);
                var mzAverage = editMoleculeDlg.Adduct.MzFromNeutralMass(averageMass);
                editMoleculeDlg.FormulaBox.MonoMass    = monoMass;
                editMoleculeDlg.FormulaBox.AverageMass = averageMass;
                var massPrecisionTolerance             = 0.00001;
                Assert.AreEqual(mzMono, double.Parse(editMoleculeDlg.FormulaBox.MonoText), massPrecisionTolerance);
                Assert.AreEqual(mzAverage, double.Parse(editMoleculeDlg.FormulaBox.AverageText), massPrecisionTolerance);
            });
            OkDialog(editMoleculeDlg, editMoleculeDlg.OkDialog);

            var newDoc     = SkylineWindow.Document;
            var compareIon = new CustomIon(null, adduct, monoMass, averageMass, testNametextA);

            Assert.AreEqual(compareIon, newDoc.MoleculeTransitions.ElementAt(0).Transition.CustomIon);
            Assert.AreEqual(1, newDoc.MoleculeTransitions.ElementAt(0).Transition.Charge);

            // Verify that tree selection doesn't change just because we changed an ID object
            // (formerly the tree node would collapse and focus would jump up a level)
            RunUI(() =>
            {
                Assert.AreEqual(SkylineWindow.SequenceTree.SelectedNode, SkylineWindow.SequenceTree.Nodes[0].FirstNode.FirstNode);
            });
        }
Ejemplo n.º 8
0
        protected override void DoTest()
        {
            const int molCount = 1;
            var       doc      = SkylineWindow.Document;

            for (var loop = 0; loop < 3; loop++)
            {
                // Should be able to synchronize if both sibs have formula, or neither, but not one of each
                string testname;
                switch (loop)
                {
                case 0:
                    testname = "ions_testing_masses.sky";     // Masses only
                    break;

                case 1:
                    testname = "ions_testing_mixed.sky";     // Starts out as two precursors, with no transitions, one is labeled 15N
                    break;

                default:
                    testname = "ions_testing.sky";     // Starts out as two precursors, first has a precursor transition, other has label 15N and serialized ion formula
                    break;
                }
                string testPath = TestFilesDir.GetTestPath(testname);
                RunUI(() => SkylineWindow.OpenFile(testPath));
                doc = WaitForDocumentChange(doc);

                SelectNode(SrmDocument.Level.Molecules, 0);

                Assert.AreEqual(molCount, SkylineWindow.Document.MoleculeCount);
                RunUI(SkylineWindow.ExpandPrecursors);

                Settings.Default.SynchronizeIsotopeTypes = true;

                // Select the first transition group
                SelectNode(SrmDocument.Level.TransitionGroups, 0);
                // Add precursor transition to it
                var pickList = ShowDialog <PopupPickList>(SkylineWindow.ShowPickChildrenInTest);
                var lloop    = loop;
                RunUI(() =>
                {
                    pickList.ApplyFilter(false);
                    switch (lloop)
                    {
                    default:
                        pickList.SetItemChecked(0, true);     // 0th item is M transition
                        break;

                    case 1:
                        pickList.SetItemChecked(0, true);     // 0th item is the M-1 transition
                        pickList.SetItemChecked(1, true);     // 1th item is the M transition
                        break;
                    }
                    pickList.AutoManageChildren = false;
                    pickList.IsSynchSiblings    = true; // Cause a precursor transition to be added to heavy group
                });
                OkDialog(pickList, pickList.OnOk);
                WaitForClosedForm(pickList);
                doc = WaitForDocumentChange(doc);
                switch (loop)
                {
                default:
                    // There should now be a precursor transition for the second, heavy labeled transition group, and it
                    // should match the mz of the transition group
                    Assert.AreEqual(2, doc.MoleculeTransitions.Count());
                    Assert.AreNotEqual(doc.MoleculeTransitionGroups.ToArray()[0].PrecursorMz, doc.MoleculeTransitions.ToArray()[1].Mz);
                    Assert.AreEqual(doc.MoleculeTransitionGroups.ToArray()[1].PrecursorMz, doc.MoleculeTransitions.ToArray()[1].Mz);
                    break;

                case 1:
                    // There should now be two precursor transitions M-1 and M for the second, heavy labeled transition group, and the
                    // second precursor transition should match the mz of the transition group
                    Assert.AreEqual(4, doc.MoleculeTransitions.Count());
                    Assert.AreNotEqual(doc.MoleculeTransitionGroups.ToArray()[0].PrecursorMz, doc.MoleculeTransitions.ToArray()[3].Mz);
                    Assert.AreEqual(doc.MoleculeTransitionGroups.ToArray()[1].PrecursorMz, doc.MoleculeTransitions.ToArray()[3].Mz);
                    break;
                }
            }

            //
            // Now with isotope distributions
            //
            var documentPath = TestFilesDir.GetTestPath("ions_testing_isotopes.sky");

            RunUI(() => SkylineWindow.OpenFile(documentPath));
            var newDoc = WaitForDocumentLoaded();

            var transitionSettingsFilter = ShowDialog <TransitionSettingsUI>(SkylineWindow.ShowTransitionSettingsUI);

            RunUI(() =>
            {
                transitionSettingsFilter.SelectedTab = TransitionSettingsUI.TABS.Filter;
                transitionSettingsFilter.SmallMoleculeFragmentTypes    = "p";     // Allow precursors
                transitionSettingsFilter.SmallMoleculePrecursorAdducts = "[M+H]"; // Allow precursors
            });
            OkDialog(transitionSettingsFilter, transitionSettingsFilter.OkDialog);
            newDoc = WaitForDocumentChange(newDoc);

            SelectNode(SrmDocument.Level.Molecules, 0);

            Assert.AreEqual(molCount, SkylineWindow.Document.MoleculeCount);
            RunUI(SkylineWindow.ExpandPrecursors);

            Settings.Default.SynchronizeIsotopeTypes = true;

            // Select the first transition group
            SelectNode(SrmDocument.Level.TransitionGroups, 0);

            // Add isotope labeled precursor transitions to it
            var pickList0 = ShowDialog <PopupPickList>(SkylineWindow.ShowPickChildrenInTest);

            RunUI(() =>
            {
                pickList0.ApplyFilter(false);
                pickList0.SetItemChecked(0, true);
                pickList0.SetItemChecked(1, true);
                pickList0.SetItemChecked(2, true);
                pickList0.AutoManageChildren = false;
                Assert.IsTrue(pickList0.CanSynchSiblings);
                pickList0.IsSynchSiblings = true; // Cause precursor transitions to be added to heavy group
            });
            OkDialog(pickList0, pickList0.OnOk);
            WaitForClosedForm(pickList0);
            Assert.AreEqual(SkylineWindow.Document.MoleculeTransitionGroups.ToArray()[1].PrecursorMz,
                            SkylineWindow.Document.MoleculeTransitions.ToArray()[4].Mz);
            Assert.AreEqual(SkylineWindow.Document.MoleculeTransitionGroups.ToArray()[1].PrecursorMz + 1.003492,
                            SkylineWindow.Document.MoleculeTransitions.ToArray()[5].Mz, 1e-6);

            // Verify that adding a custom transition prevents the synch siblings checkbox from appearing
            RunUI(() =>
            {
                SkylineWindow.ExpandPrecursors();
                var node = SkylineWindow.SequenceTree.Nodes[0].FirstNode.FirstNode;
                SkylineWindow.SequenceTree.SelectedNode = node;
            });
            newDoc = WaitForDocumentChange(newDoc);
            var moleculeDlg   = ShowDialog <EditCustomMoleculeDlg>(SkylineWindow.AddSmallMolecule);
            var C12H12        = "C12H12";
            var testNametextA = "y1";

            RunUI(() =>
            {
                moleculeDlg.FormulaBox.Formula = C12H12;
                moleculeDlg.NameText           = testNametextA;
                moleculeDlg.Adduct             = Adduct.SINGLY_PROTONATED;
            });
            OkDialog(moleculeDlg, moleculeDlg.OkDialog);
            newDoc = WaitForDocumentChange(newDoc);
            var       compareIon   = new CustomIon(C12H12, Adduct.SINGLY_PROTONATED, null, null, testNametextA);
            const int transY1Index = 3;

            Assert.AreEqual(compareIon, newDoc.MoleculeTransitions.ElementAt(transY1Index).Transition.CustomIon);
            Assert.AreEqual(1, newDoc.MoleculeTransitions.ElementAt(transY1Index).Transition.Charge);
            var pickList1 = ShowDialog <PopupPickList>(SkylineWindow.ShowPickChildrenInTest);

            RunUI(() =>
            {
                pickList1.AutoManageChildren = true;
                Assert.IsFalse(pickList1.CanSynchSiblings);
            });
            OkDialog(pickList1, pickList1.OnOk);
            Assert.AreEqual(7, newDoc.MoleculeTransitions.Count());
            // Long as we're here, check mz filtering
            var transitionSettings = ShowDialog <TransitionSettingsUI>(SkylineWindow.ShowTransitionSettingsUI);

            RunUI(() =>
            {
                transitionSettings.SelectedTab = TransitionSettingsUI.TABS.Instrument;
                transitionSettings.MinMz       = 160; // Should drop that custom ion
            });
            OkDialog(transitionSettings, transitionSettings.OkDialog);
            newDoc = WaitForDocumentChange(newDoc);
            Assert.AreEqual(6, newDoc.MoleculeTransitions.Count());
            RunUI(() => { SkylineWindow.Undo(); });
            newDoc = WaitForDocumentChange(newDoc);
            Assert.AreEqual(7, newDoc.MoleculeTransitions.Count());

            RunUI(() =>
            {
                SkylineWindow.ExpandPrecursors();
                var node = SkylineWindow.SequenceTree.Nodes[0].FirstNode.Nodes[1];
                SkylineWindow.SequenceTree.SelectedNode = node;
            });
            var pickList2 = ShowDialog <PopupPickList>(SkylineWindow.ShowPickChildrenInTest);

            RunUI(() =>
            {
                Assert.IsFalse(pickList2.CanSynchSiblings); // The light set has a non-precursor transition
            });
            OkDialog(pickList2, pickList2.OnOk);

            var transitionSettings2 = ShowDialog <TransitionSettingsUI>(SkylineWindow.ShowTransitionSettingsUI);

            RunUI(() =>
            {
                transitionSettings2.SelectedTab = TransitionSettingsUI.TABS.Instrument;
                transitionSettings2.MinMz       = 300; // Should drop that custom ion
            });
            OkDialog(transitionSettings2, transitionSettings2.OkDialog);
            newDoc = WaitForDocumentChange(newDoc);
            Assert.AreEqual(6, newDoc.MoleculeTransitions.Count());

            var pickList3 = ShowDialog <PopupPickList>(SkylineWindow.ShowPickChildrenInTest);

            RunUI(() =>
            {
                Assert.IsTrue(pickList3.CanSynchSiblings);
            });
            OkDialog(pickList3, pickList3.OnOk);

            // Add another precursor, with explicit isotope declaration in the adduct [M4C13+H]
            // Open its picklist, clear filter so all isotopes appear
            // Select them all, and check synch siblings
            // Should be 3*3 transitions in doc
            SelectNode(SrmDocument.Level.Molecules, 0);
            var moleculeDlg2 = ShowDialog <EditCustomMoleculeDlg>(SkylineWindow.AddSmallMolecule);
            var adduct       = moleculeDlg.FormulaBox.Adduct.ChangeIsotopeLabels(new Dictionary <string, int> {
                { "C'", 4 }
            });                                                                                                          // Not L10N

            RunUI(() =>
            {
                moleculeDlg2.Adduct = adduct;
            });
            OkDialog(moleculeDlg2, moleculeDlg2.OkDialog);
            newDoc = WaitForDocumentChange(newDoc);
            // Select the new transition group
            SelectNode(SrmDocument.Level.TransitionGroups, 2);
            // Add precursor transition to it
            var pickList4 = ShowDialog <PopupPickList>(SkylineWindow.ShowPickChildrenInTest);

            RunUI(() =>
            {
                pickList4.ApplyFilter(false);
                pickList4.SetItemChecked(0, true);
                pickList4.SetItemChecked(1, true);
                pickList4.SetItemChecked(2, true);
                pickList4.AutoManageChildren = false;
                pickList4.IsSynchSiblings    = true; // Cause a precursor transition to be added to heavy group
            });
            OkDialog(pickList4, pickList4.OnOk);
            WaitForClosedForm(pickList4);
            newDoc = WaitForDocumentChange(newDoc);
            Assert.AreEqual(9, newDoc.MoleculeTransitionCount);
        }
Ejemplo n.º 9
0
 public static SpectrumPeakAnnotation Create(CustomIon ion, string comment)
 {
     return(ion.IsEmpty && string.IsNullOrEmpty(comment) ?
            EMPTY :
            new SpectrumPeakAnnotation(ion, comment));
 }
Ejemplo n.º 10
0
 private SpectrumPeakAnnotation(CustomIon ion, string comment)
 {
     Ion     = ion ?? CustomIon.EMPTY;
     Comment = comment ?? string.Empty;
     Assume.IsFalse(IsNullOrEmpty(this), @"empty peak annotation"); // You should be using Create() if there's any risk of creating empty objects
 }
Ejemplo n.º 11
0
        private void ProcessTransitionGroup(IDictionary <LibKey, SpectrumMzInfo> spectra,
                                            PeptideGroupDocNode nodePepGroup, PeptideDocNode nodePep, TransitionGroupDocNode nodeTranGroup, int replicateIndex)
        {
            LibKey key;

            if (nodePep.IsProteomic)
            {
                var sequence = Document.Settings.GetPrecursorCalc(nodeTranGroup.TransitionGroup.LabelType, nodePep.ExplicitMods)
                               .GetModifiedSequence(nodePep.Peptide.Target, SequenceModFormatType.lib_precision, false);
                key = new LibKey(sequence, nodeTranGroup.PrecursorAdduct.AdductCharge);
            }
            else
            {
                // For small molecules, the "modification" is expressed in the adduct
                key = new LibKey(nodeTranGroup.CustomMolecule.GetSmallMoleculeLibraryAttributes(), nodeTranGroup.PrecursorAdduct);
            }
            var mi              = new List <SpectrumPeaksInfo.MI>();
            var rt              = 0.0;
            var im              = IonMobilityAndCCS.EMPTY;
            var imGroup         = TransitionGroupIonMobilityInfo.EMPTY; // CCS may be available only at group level
            var groupChromInfos = nodeTranGroup.GetSafeChromInfo(replicateIndex);

            if (!groupChromInfos.IsEmpty)
            {
                var chromInfo = groupChromInfos.First(info => info.OptimizationStep == 0);
                imGroup = chromInfo.IonMobilityInfo;
            }
            var    maxApex       = float.MinValue;
            var    maxApexMs1    = float.MinValue;
            string chromFileName = null;
            double?mobilityMs1   = null; // Track MS1 ion mobility in order to derive high energy ion mobility offset value

            foreach (var nodeTran in nodeTranGroup.Transitions)
            {
                var chromInfos = nodeTran.GetSafeChromInfo(replicateIndex);
                if (chromInfos.IsEmpty)
                {
                    continue;
                }
                var chromInfo = chromInfos.First(info => info.OptimizationStep == 0);
                if (chromInfo.Area == 0)
                {
                    continue;
                }
                if (nodeTran.IsMs1)
                {
                    // Track MS1 ion mobility in order to derive high energy ion mobility offset value
                    if (chromInfo.Height > maxApexMs1)
                    {
                        maxApexMs1  = chromInfo.Height;
                        mobilityMs1 = chromInfo.IonMobility.IonMobility.Mobility;
                    }
                    continue;
                }
                if (chromFileName == null)
                {
                    var chromFileInfo = Document.Settings.MeasuredResults.Chromatograms[replicateIndex].MSDataFileInfos.FirstOrDefault(file => ReferenceEquals(file.Id, chromInfo.FileId));
                    if (chromFileInfo != null)
                    {
                        chromFileName = chromFileInfo.FilePath.GetFileName();
                    }
                }
                List <SpectrumPeakAnnotation> annotations = null;
                if (nodeTran.Transition.IsNonReporterCustomIon()) // CONSIDER(bspratt) include annotation for all non-peptide-fragment transitions?
                {
                    var smallMoleculeLibraryAttributes = nodeTran.Transition.CustomIon.GetSmallMoleculeLibraryAttributes();
                    var ion = new CustomIon(smallMoleculeLibraryAttributes, nodeTran.Transition.Adduct, nodeTran.GetMoleculeMass());
                    annotations = new List <SpectrumPeakAnnotation> {
                        SpectrumPeakAnnotation.Create(ion, nodeTran.Annotations.Note)
                    };
                }
                mi.Add(new SpectrumPeaksInfo.MI {
                    Mz = nodeTran.Mz, Intensity = chromInfo.Area, Quantitative = nodeTran.ExplicitQuantitative, Annotations = annotations
                });
                if (chromInfo.Height > maxApex)
                {
                    maxApex = chromInfo.Height;
                    rt      = chromInfo.RetentionTime;
                    var mobility = chromInfo.IonMobility.IonMobility;
                    var mobilityHighEnergyOffset = 0.0;
                    if (mobilityMs1.HasValue && mobility.HasValue && mobility.Mobility != mobilityMs1)
                    {
                        // Note any difference in MS1 and MS2 ion mobilities - the "high energy ion mobility offset"
                        mobilityHighEnergyOffset = mobility.Mobility.Value - mobilityMs1.Value;
                        mobility = mobility.ChangeIonMobility(mobilityMs1);
                    }
                    im = IonMobilityAndCCS.GetIonMobilityAndCCS(mobility, chromInfo.IonMobility.CollisionalCrossSectionSqA ?? imGroup.CollisionalCrossSection, mobilityHighEnergyOffset);
                }
            }
            if (chromFileName == null)
            {
                return;
            }
            SpectrumMzInfo spectrumMzInfo;

            if (!spectra.TryGetValue(key, out spectrumMzInfo))
            {
                spectrumMzInfo = new SpectrumMzInfo
                {
                    SourceFile     = DocumentFilePath,
                    Key            = key,
                    PrecursorMz    = nodeTranGroup.PrecursorMz,
                    SpectrumPeaks  = new SpectrumPeaksInfo(mi.ToArray()),
                    RetentionTimes = new List <SpectrumMzInfo.IonMobilityAndRT>(),
                    IonMobility    = im,
                    Protein        = nodePepGroup.Name,
                    RetentionTime  = rt
                };
                spectra[key] = spectrumMzInfo;
            }
            var isBest = replicateIndex == nodePep.BestResult;

            if (isBest)
            {
                spectrumMzInfo.IonMobility   = im;
                spectrumMzInfo.RetentionTime = rt;
            }
            spectrumMzInfo.RetentionTimes.Add(new SpectrumMzInfo.IonMobilityAndRT(chromFileName, im, rt, isBest));
        }