package com.compomics.util.experiment.identification.spectrum_annotation.spectrum_annotators; import com.compomics.util.experiment.biology.AminoAcid; import com.compomics.util.experiment.biology.AminoAcidPattern; import com.compomics.util.experiment.biology.AminoAcidSequence; import com.compomics.util.experiment.biology.Ion; import com.compomics.util.experiment.biology.NeutralLoss; import com.compomics.util.experiment.biology.PTM; import com.compomics.util.experiment.biology.PTMFactory; import com.compomics.util.experiment.identification.spectrum_annotation.NeutralLossesMap; import com.compomics.util.experiment.identification.spectrum_annotation.SpectrumAnnotator; import com.compomics.util.experiment.identification.matches.IonMatch; import com.compomics.util.experiment.identification.matches.ModificationMatch; import com.compomics.util.experiment.identification.amino_acid_tags.Tag; import com.compomics.util.experiment.identification.amino_acid_tags.TagComponent; import com.compomics.util.experiment.biology.MassGap; import com.compomics.util.experiment.massspectrometry.MSnSpectrum; import com.compomics.util.experiment.identification.spectrum_annotation.AnnotationSettings; import com.compomics.util.preferences.SequenceMatchingPreferences; import com.compomics.util.experiment.identification.spectrum_annotation.SpecificAnnotationSettings; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; /** * Annotates a spectrum with information from a tag. * * @author Marc Vaudel */ public class TagSpectrumAnnotator extends SpectrumAnnotator { /** * The tag to annotate on the spectrum. */ private Tag tag; /** * Returns the tag to annotate. * * @return the tag to annotate */ public Tag getTag() { return tag; } /** * Sets a new tag to match. * * @param newTag the new tag * @param precursorCharge the new precursor charge */ public void setTag(Tag newTag, int precursorCharge) { if (this.tag == null || !this.tag.isSameAs(newTag, SequenceMatchingPreferences.defaultStringMatching) || this.precursorCharge != precursorCharge) { // Set new values this.tag = newTag; this.precursorCharge = precursorCharge; theoreticalFragmentIons = fragmentFactory.getFragmentIons(newTag); if (massShift != 0 || massShiftNTerm != 0 || massShiftCTerm != 0) { updateMassShifts(); } } } /** * Returns the possible neutral losses expected by default for a given tag. * /!\ this method will work only if the PTM found in the tag are in the * PTMFactory. * * @param tag the tag of interest * @param ptmSequenceMatchingSettings the sequence matching settings for PTM * to peptide mapping * * @return the expected possible neutral losses * * @throws IOException exception thrown whenever an error occurred while * interacting with a file while mapping potential modification sites * @throws InterruptedException exception thrown whenever a threading issue * occurred while mapping potential modification sites * @throws ClassNotFoundException exception thrown whenever an error * occurred while deserializing an object from the ProteinTree */ public static NeutralLossesMap getDefaultLosses(Tag tag, SequenceMatchingPreferences ptmSequenceMatchingSettings) throws IOException, InterruptedException, ClassNotFoundException { PTMFactory pTMFactory = PTMFactory.getInstance(); NeutralLossesMap neutralLossesMap = new NeutralLossesMap(); int tagLength = tag.getLengthInAminoAcid(); int aaMin = tagLength; int aaMax = 0; int offset = 0; for (TagComponent component : tag.getContent()) { if (component instanceof AminoAcidPattern) { AminoAcidPattern aminoAcidPattern = (AminoAcidPattern) component; for (int i = 0; i < aminoAcidPattern.length(); i++) { if (aminoAcidPattern.getAminoAcidsAtTarget().contains(AminoAcid.D.getSingleLetterCodeAsChar()) || aminoAcidPattern.getAminoAcidsAtTarget().contains(AminoAcid.E.getSingleLetterCodeAsChar()) || aminoAcidPattern.getAminoAcidsAtTarget().contains(AminoAcid.S.getSingleLetterCodeAsChar()) || aminoAcidPattern.getAminoAcidsAtTarget().contains(AminoAcid.T.getSingleLetterCodeAsChar())) { int index = i + offset; aaMin = Math.min(index, aaMin); aaMax = Math.max(index, aaMax); } } offset += aminoAcidPattern.length(); } else if (component instanceof AminoAcidSequence) { AminoAcidSequence aminoAcidSequence = (AminoAcidSequence) component; for (int i = 0; i < aminoAcidSequence.length(); i++) { if (aminoAcidSequence.charAt(i) == 'D' || aminoAcidSequence.charAt(i) == 'E' || aminoAcidSequence.charAt(i) == 'S' || aminoAcidSequence.charAt(i) == 'T') { int index = i + offset; aaMin = Math.min(index, aaMin); aaMax = Math.max(index, aaMax); } } offset += aminoAcidSequence.length(); } else if (component instanceof MassGap) { offset++; } else { throw new UnsupportedOperationException("Spectrum annotator not implemented for " + component.getClass() + "."); } } if (aaMin < tagLength) { neutralLossesMap.addNeutralLoss(NeutralLoss.H2O, aaMin + 1, tagLength - aaMax); } aaMin = tagLength; aaMax = 0; offset = 0; for (TagComponent component : tag.getContent()) { if (component instanceof AminoAcidPattern) { AminoAcidPattern aminoAcidPattern = (AminoAcidPattern) component; for (int i = 0; i < aminoAcidPattern.length(); i++) { if (aminoAcidPattern.getAminoAcidsAtTarget().contains(AminoAcid.K.getSingleLetterCodeAsChar()) || aminoAcidPattern.getAminoAcidsAtTarget().contains(AminoAcid.N.getSingleLetterCodeAsChar()) || aminoAcidPattern.getAminoAcidsAtTarget().contains(AminoAcid.Q.getSingleLetterCodeAsChar()) || aminoAcidPattern.getAminoAcidsAtTarget().contains(AminoAcid.R.getSingleLetterCodeAsChar())) { int index = i + offset; aaMin = Math.min(index, aaMin); aaMax = Math.max(index, aaMax); } } offset += aminoAcidPattern.length(); } else if (component instanceof AminoAcidSequence) { AminoAcidSequence aminoAcidSequence = (AminoAcidSequence) component; for (int i = 0; i < aminoAcidSequence.length(); i++) { if (aminoAcidSequence.charAt(i) == 'K' || aminoAcidSequence.charAt(i) == 'N' || aminoAcidSequence.charAt(i) == 'Q' || aminoAcidSequence.charAt(i) == 'R') { int index = i + offset; aaMin = Math.min(index, aaMin); aaMax = Math.max(index, aaMax); } } offset += aminoAcidSequence.length(); } else if (component instanceof MassGap) { offset++; } else { throw new UnsupportedOperationException("Spectrum annotator not implemented for " + component.getClass() + "."); } } if (aaMin < tagLength) { neutralLossesMap.addNeutralLoss(NeutralLoss.NH3, aaMin + 1, tagLength - aaMax); } int modMin = tagLength; int modMax = 0; offset = 0; for (TagComponent component : tag.getContent()) { if (component instanceof AminoAcidPattern) { AminoAcidPattern aminoAcidPattern = (AminoAcidPattern) component; for (int i = 1; i <= aminoAcidPattern.length(); i++) { for (ModificationMatch modificationMatch : aminoAcidPattern.getModificationsAt(i)) { PTM ptm = pTMFactory.getPTM(modificationMatch.getTheoreticPtm()); if (ptm == null) { throw new IllegalArgumentException("PTM " + modificationMatch.getTheoreticPtm() + " not loaded in PTM factory."); } for (NeutralLoss neutralLoss : ptm.getNeutralLosses()) { ArrayList<Integer> indexes = tag.getPotentialModificationSites(ptm, ptmSequenceMatchingSettings); // @TODO: could end in a null pointer? if (!indexes.isEmpty()) { Collections.sort(indexes); modMin = indexes.get(0); modMax = indexes.get(indexes.size() - 1); } neutralLossesMap.addNeutralLoss(neutralLoss, modMin, tag.getLengthInAminoAcid() - modMax + 1); } } } offset += aminoAcidPattern.length(); } else { offset++; } } return neutralLossesMap; } /** * Returns the spectrum annotations of a spectrum in a list of IonMatches. * * Note that, except for +1 precursors, fragments ions will be expected to * have a charge strictly smaller than the precursor ion charge. * * @param annotationSettings the annotation settings * @param specificAnnotationSettings the specific annotation settings * @param spectrum the spectrum to match * @param tag the tag of interest * * @return an ArrayList of IonMatch containing the ion matches with the * given settings */ public ArrayList<IonMatch> getSpectrumAnnotation(AnnotationSettings annotationSettings, SpecificAnnotationSettings specificAnnotationSettings, MSnSpectrum spectrum, Tag tag) { ArrayList<IonMatch> result = new ArrayList<IonMatch>(); setMassTolerance(specificAnnotationSettings.getFragmentIonAccuracy(), specificAnnotationSettings.isFragmentIonPpm(), annotationSettings.getTiesResolution()); if (spectrum != null) { setSpectrum(spectrum, spectrum.getIntensityLimit(annotationSettings.getAnnotationIntensityLimit())); } setTag(tag, specificAnnotationSettings.getPrecursorCharge()); ArrayList<Integer> precursorCharges = new ArrayList<Integer>(); // we have to keep the precursor charges separate from the fragment ion charges for (int i = 1; i <= precursorCharge; i++) { precursorCharges.add(i); } HashMap<Ion.IonType, HashSet<Integer>> ionTypes = specificAnnotationSettings.getIonTypes(); if (theoreticalFragmentIons != null) { for (Ion.IonType ionType : ionTypes.keySet()) { HashMap<Integer, ArrayList<Ion>> ionMap = theoreticalFragmentIons.get(ionType.index); if (ionMap != null) { HashSet<Integer> subtypes = ionTypes.get(ionType); for (int subType : subtypes) { ArrayList<Ion> ions = ionMap.get(subType); if (ions != null) { for (Ion ion : ions) { if (lossesValidated(specificAnnotationSettings.getNeutralLossesMap(), ion)) { ArrayList<Integer> tempCharges; // have to treat precursor charges separately, as to not increase the max charge for the other ions if (ionType == Ion.IonType.PRECURSOR_ION) { tempCharges = precursorCharges; } else { tempCharges = specificAnnotationSettings.getSelectedCharges(); } for (int charge : tempCharges) { if (chargeValidated(ion, charge, precursorCharge)) { IonMatch ionMatch = matchInSpectrum(ion, charge); if (ionMatch != null) { result.add(ionMatch); } } } } } } } } } } return result; } @Override public ArrayList<IonMatch> getCurrentAnnotation(MSnSpectrum spectrum, AnnotationSettings annotationSettings, SpecificAnnotationSettings specificAnnotationSettings) { return getSpectrumAnnotation(annotationSettings, specificAnnotationSettings, spectrum, tag); } }