R/server_functions.R

#' Return a table with amino acid information
#' 
#' This functionality should be located in a separate peptide package in the 
#' future
#' 
#' @return A data.frame with a row for each of the 20 common amino acids and 
#' columns for code, weight, composition, hydrophobicity and pK value
#' 
#' @noRd
#' 
getAAtable <- function(){
    file <- system.file(package='MSGFgui', 'extdata', 'AAtable.csv')
    AAtable <- read.csv(file, header=TRUE, as.is=TRUE)
    AAtable
}
#' Return a table with adduct information
#' 
#' This functionality should be located in a separate peptide package in the 
#' future
#' 
#' @return A data.frame with a row for 32 common adducts and columns for charge
#' and delta mass
#' 
#' @noRd
#' 
getAdductTable <- function(){
    file <- system.file(package='MSGFgui', 'extdata', 'adductTable.csv')
    AdductTable <- read.csv(file, header=TRUE, as.is=TRUE)
    AdductTable
}
#' Calculate the mass of a peptide sequence
#' 
#' This functionality should be located in a separate peptide package in the 
#' future
#' 
#' @param pepseq A character string with the one-letter code for the peptide
#' sequence
#' 
#' @param modifications A vector of delta-masses for the modifications on the
#' peptide
#' 
#' @param mono Logical. Should the mass be calculated as the monoisotopic mass? 
#' Defaults to FALSE (calculates the average mass)
#' 
#' @param neutral Logical. Should the peptide be considered without water? 
#' Defaults to FALSE
#' 
#' @return The mass in dalton of the peptide
#' 
#' @noRd
#' 
pepMass <- function(pepseq, modifications, mono=FALSE, neutral=FALSE){
    AAtable <- getAAtable()
    pepseq <- toupper(pepseq)
    if(!all(unlist(strsplit(pepseq, '')) %in% AAtable$Code1)){
        stop('Invalid peptide sequence(s).')
    } else {}
    if(mono){
        if(neutral){
            mass <- sapply(pepseq, function(x) sum(AAtable$Mono[match(strsplit(x, '')[[1]], AAtable$Code1)], na.rm=FALSE))
        } else {
            mass <- sapply(pepseq, function(x) sum(AAtable$Mono[match(strsplit(x, '')[[1]], AAtable$Code1)], na.rm=FALSE) + 18.01056)
        }
    } else {
        if(neutral){
            mass <- sapply(pepseq, function(x) sum(AAtable$Avg[match(strsplit(x, '')[[1]], AAtable$Code1)], na.rm=FALSE))
        } else {
            mass <- sapply(pepseq, function(x) sum(AAtable$Avg[match(strsplit(x, '')[[1]], AAtable$Code1)], na.rm=FALSE) + 18.02)
        }
    }
    if(!missing(modifications)) {
        mass = mass + sum(unlist(modifications))
    }
    mass
}
#' Random ID generator based on UUID
#' 
#' This function returns a unique string of the UUID format each time it is 
#' called
#' 
#' @return A UUID compliant random string
#' 
#' @references Based on implementation by thelatemail 
#' \url{http://stackoverflow.com/questions/10492817/how-can-i-generate-a-guid-in-r}
#' 
#' @noRd
#' 
sampleID <- function() {
    baseuuid <- paste(sample(c(letters[1:6],0:9),30,replace=TRUE),collapse="")
    
    paste(
        substr(baseuuid,1,8),
        "-",
        substr(baseuuid,9,12),
        "-",
        "4",
        substr(baseuuid,13,15),
        "-",
        sample(c("8","9","a","b"),1),
        substr(baseuuid,16,18),
        "-",
        substr(baseuuid,19,30),
        sep="",
        collapse=""
    )
}
#' Converts a string to a msgfParModification object
#' 
#' This function takes a string describing a modification as returned by the
#' client, and creates a msgfParModification object.
#' 
#' @param modString A string of the format: 
#' 'N:string;(C:string | W:num);R:string;T:string;P:string'
#' 
#' @return An msgfParModification object matching the information in the string
#' 
#' @importFrom MSGFplus msgfParModification
#' 
#' @noRd
#' 
modStringToMod <- function(modString) {
    mod <- strsplit(modString, ';', fixed=TRUE)[[1]]
    modPar <- list()
    for(i in mod) {
        par = strsplit(i, ':', fixed=TRUE)[[1]]
        if(par[1] == 'N') {
            modPar$name <- par[2]
        } else if(par[1] == 'C') {
            modPar$composition <- par[2]
        } else if(par[1] == 'W') {
            modPar$mass <- as.numeric(par[2])
        } else if(par[1] == 'R') {
            modPar$residues <- par[2]
        } else if(par[1] == 'T') {
            modPar$type <- par[2]
        } else if(par[1] == 'P') {
            modPar$position <- par[2]
        }
    }
    do.call('msgfParModification', modPar)
}
#' Create a list with information on a sample to be send to the client
#' 
#' This function takes the information for a sample and formats a subset of it 
#' to be send to a client. It tries to strike the balance between size and 
#' information needed for fluid interaction from the client.
#' 
#' @note This function is a great candidate for refactoring
#' 
#' @param mzID An mzID object
#' 
#' @param mzML an mzRamp object corresponding to the mzID object
#' 
#' @param name the name of the object
#' 
#' @param id A unique id across the session for the sample e.g. created by 
#' sampleID()
#' 
#' @return A list with elements: name, id, modifications and mzid
#' 
#' @import mzID
#' 
#' @noRd
#' 
renderMzID <- function(mzID, mzML, metaML, name, id) {
    idNames <- c('peptide_ref', 'experimentalmasstocharge', 'chargestate', 'ms-gf:denovoscore', 'ms-gf:qvalue', 'id')
    evidenceNames <- c('post', 'pre', 'end', 'start', 'peptide_ref', 'dbsequence_ref')
    databaseNames <- c('accession', 'length', 'id', 'description')
    peptide_refPrefix <- 'Pep'
    dbsequence_refPrefix <- 'DBSeq'
    
    ans <- list()
    
    ans$name <- name
    
    ans$id <- id
    
    ans$modifications <- parameters(mzID)$ModificationRules
    
    mzID <- removeDecoy(mzID)
    tempMzID <- list()
    
    tempMzID$id <- id(mzID)[, names(id(mzID)) %in% idNames]
    scanIndex <- rep(1:length(idScanMap(mzID)), sapply(idScanMap(mzID), length))[match(1:nrow(id(mzID)), unlist(idScanMap(mzID)))]
    tempMzID$id$scan_ref <- scans(mzID)$id[scanIndex]
    tempMzID$id$rt <- metaML$retentionTime[match(scans(mzID)$acquisitionnum, metaML$acquisitionNum)][scanIndex]
    tempMzID$id$peptide_ref <- as.numeric(sub(peptide_refPrefix, '', tempMzID$id$peptide_ref))
    
    tempMzID$peptides <- peptides(mzID)
    tempMzID$peptides$id <- as.numeric(sub(peptide_refPrefix, '', tempMzID$peptides$id))
    tempMzID$modification <- modifications(mzID)
    
    tempMzID$evidence <- evidence(mzID)[, names(evidence(mzID)) %in% evidenceNames]
    tempMzID$evidence$peptide_ref <- as.numeric(sub(peptide_refPrefix, '', tempMzID$evidence$peptide_ref))
    tempMzID$evidence$dbsequence_ref <- as.numeric(sub(dbsequence_refPrefix, '', tempMzID$evidence$dbsequence_ref))
    
    tempMzID$database <- database(mzID)[, names(database(mzID)) %in% databaseNames]
    tempMzID$database$id <- as.numeric(sub(dbsequence_refPrefix, '', tempMzID$database$id))
    
    ans$mzid <- tempMzID
    
    invisible(ans)
}
#' Create a list with raw scan information to be send to the client
#' 
#' This function creates a simple representation of n annotated scan along with 
#' an optional parent ion trace.
#' 
#' @param mzML An mzRamp object to extract the scan from
#' 
#' @param scan The acquisition number of the scan to extract
#' 
#' @param seq The sequence of the predicted peptide
#' 
#' @param modifications a list of modifications. The index correspond to the 
#' residue
#' 
#' @param fragPPM The sensitivity used for matching fragment ions
#' 
#' @param tracePPM The sensitivity to trace the parent ion
#' 
#' @param ions The fragment ion types to match
#' 
#' @param neutralLosses Logical. Should neutral losses be matches. Defaults to 
#' TRUE
#' 
#' @param showTrace Logical should parent ion trace be computed. Defaults to 
#' FALSE
#' 
#' @return A list with the elements: scan, trace and id
#' 
#' @noRd
#' 
renderScan <- function(mzML, metaML, scan, seq, modifications=list(), fragPPM=60, tracePPM=5, ions='abcxyz', neutralLosses=TRUE, showTrace=FALSE) {
    ans <- list()
    data <- openMSfile(mzML)
    ans$scan <- annotateSpec(getScan(data, metaML, scan, tracePPM), seq, modifications, fragPPM, ions, neutralLosses)
    ans$trace <- if(showTrace) {tryCatch(traceParent(data, metaML, scan, tracePPM), error=function(e){NULL})} else {NULL}
    close(data)
    ans$id <- sampleID()
    
    ans
}
#' Get the score distrubutions for target and decoy matches for a set of samples
#' 
#' This functions fits a kernel density estimation to the scores for targets and
#' decoy matches for a set of samples
#' 
#' @param samples A list of samples, where each sample is a list containing the 
#' elements: name, id, mzID and mzML where the two latter are mzID and mzRamp 
#' objects
#' 
#' @return A list with elements: decoy and target, each containing the output of
#' a density() call
#' 
#' @import mzID
#' 
#' @noRd
#' 
getScoreDistribution <- function(samples) {
    ans <- list()
    
    ans$decoy <- density(unlist(lapply(samples, function(sample) {
        id(sample$mzID)$`ms-gf:rawscore`[id(sample$mzID)$peptide_ref %in% evidence(sample$mzID)$peptide_ref[evidence(sample$mzID)$isdecoy]]
    })), from=0)
    
    ans$target <- density(unlist(lapply(samples, function(sample) {
        id(sample$mzID)$`ms-gf:rawscore`[id(sample$mzID)$peptide_ref %in% evidence(sample$mzID)$peptide_ref[!evidence(sample$mzID)$isdecoy]]
    })), from=0)
    
    ans
}
#' Extract the spectrum from a mzRamp object with annotation
#' 
#' This function extract a scan based on the acquisition number and, if it is an
#' MS2 scan annotate the parent ion.
#' 
#' @param data An mzRamp object
#' 
#' @param scan An acquisition number
#' 
#' @param ppm The sensitivity used to annotate the parent ion
#' 
#' @return A data.frame with columns: mz, intensity and parent
#' 
#' @noRd
#' 
getScan <- function(data, metaML, scan, ppm=20) {
    index <- which(metaML$acquisitionNum == scan)
    scanInfo <- header(data, index)
    spec <- peaks(data, index)
    spec <- data.frame(mz=spec[,1], intensity=spec[,2], parent=FALSE)
    
    if(scanInfo$msLevel > 1){
        precursor <- which.min(abs(spec$mz-scanInfo$precursorMZ))
        if(abs(spec$mz[precursor]-scanInfo$precursorMZ) < (scanInfo$precursorMZ/1000000)*ppm){
            spec$parent[precursor] <- TRUE
        }
    }
    spec
}
#' Calculate the theoretical fragmentation pattern of a peptide
#' 
#' This function takes a peptide sequence with optional modifications, and
#' calculate the mz values for the ions corresponding to backbone cleavage
#' 
#' @note This algorithm is very bare-bone and can be improved tremendously. 
#' Should reside in a peptide utility package
#' 
#' @param pepseq A character string with the one-letter code for the peptide
#' sequence
#' 
#' @param modifications A list of delta-masses for the modifications on the
#' peptide
#' 
#' @param ions The fragment ion types to match
#' 
#' @param neutralLosses Logical. Should neutral losses be calculated. Defaults 
#' to TRUE
#' 
#' @return A dataframe with columns: ion, index and mz
#' 
#' @noRd
#' 
fragPattern <- function(pepseq, modifications=list(), ions='abcxyz', neutralLosses=TRUE){
    AAtable <- getAAtable()
    ions <- strsplit(ions, '')[[1]]
    pepseq <- toupper(pepseq)
    if(!(sum(strsplit(pepseq, '')[[1]] %in% AAtable$Code1)==nchar(pepseq))){
        stop('Invalid peptide sequence.')
    } else {}
    pepseq <- strsplit(pepseq,'')[[1]]
    residueIndex <- 1:(length(pepseq)-1)
    bion <- data.frame(ion=rep(c('b', 'b*', 'b\u00BA'), each=length(residueIndex)), index=residueIndex, mz=NA, stringsAsFactors=FALSE)
    yion <- data.frame(ion=rep(c('y', 'y*', 'y\u00BA'), each=length(residueIndex)), index=residueIndex, mz=NA, stringsAsFactors=FALSE)
    aion <- data.frame(ion=rep(c('a', 'a*', 'a\u00BA'), each=length(residueIndex)), index=residueIndex, mz=NA, stringsAsFactors=FALSE)
    zion <- data.frame(ion=rep(c('z', 'z*', 'z\u00BA'), each=length(residueIndex)), index=residueIndex, mz=NA, stringsAsFactors=FALSE)
    cion <- data.frame(ion=rep(c('c', 'c*', 'c\u00BA'), each=length(residueIndex)), index=residueIndex, mz=NA, stringsAsFactors=FALSE)
    xion <- data.frame(ion=rep(c('x', 'x*', 'x\u00BA'), each=length(residueIndex)), index=residueIndex, mz=NA, stringsAsFactors=FALSE)
    for(i in residueIndex){
        abcionseq <- paste(pepseq[1:i], collapse='')
        modification <- modifications[1:i]
        abcMass <- pepMass(abcionseq, modification, mono=TRUE, neutral=TRUE)
        if('b' %in% ions) bion$mz[i] <- abcMass+2*1.0078250 - 1.007850
        if('a' %in% ions) aion$mz[i] <- abcMass+2*1.0078250 - 29.00273
        if('c' %in% ions) cion$mz[i] <- abcMass+2*1.0078250 + 16.01872
        if(neutralLosses){
            if(any(c('R', 'K', 'N', 'Q') %in% strsplit(abcionseq, '')[[1]])){
                if('b' %in% ions) bion$mz[i+length(pepseq)-1] <- bion$mz[i] - 17.02654
                if('a' %in% ions) aion$mz[i+length(pepseq)-1] <- aion$mz[i] - 17.02654
                if('c' %in% ions) cion$mz[i+length(pepseq)-1] <- cion$mz[i] - 17.02654
            } else {}
            if(any(c('S', 'T', 'E', 'D') %in% strsplit(abcionseq, '')[[1]])){
                if('b' %in% ions) bion$mz[i+2*(length(pepseq)-1)] <- bion$mz[i] - 18.01056
                if('a' %in% ions) aion$mz[i+2*(length(pepseq)-1)] <- aion$mz[i] - 18.01056
                if('c' %in% ions) cion$mz[i+2*(length(pepseq)-1)] <- cion$mz[i] - 18.01056
            } else {}
        }
        xyzionseq <- paste(pepseq[(length(pepseq)+1-i):length(pepseq)], collapse='')
        modification <- modifications[(length(pepseq)+1-i):length(pepseq)]
        xyzMass <- pepMass(xyzionseq, modification, mono=TRUE, neutral=TRUE)
        if('y' %in% ions) yion$mz[i] <- xyzMass+18.01056 + 1.0078250
        if('x' %in% ions) xion$mz[i] <- xyzMass+18.01056 + 27.99491 - 1.0078250
        if('z' %in% ions) zion$mz[i] <- xyzMass+18.01056 - 16.01872
        if(neutralLosses){
            if(any(c('R', 'K', 'N', 'Q') %in% strsplit(xyzionseq, '')[[1]])){
                if('y' %in% ions) yion$mz[i+length(pepseq)-1] <- yion$mz[i] - 17.02654
                if('x' %in% ions) xion$mz[i+length(pepseq)-1] <- xion$mz[i] - 17.02654
                if('z' %in% ions) zion$mz[i+length(pepseq)-1] <- zion$mz[i] - 17.02654
            } else {}
            if(any(c('S', 'T', 'E', 'D') %in% strsplit(xyzionseq, '')[[1]])){
                if('y' %in% ions) yion$mz[i+2*(length(pepseq)-1)] <- yion$mz[i] - 18.01056
                if('x' %in% ions) xion$mz[i+2*(length(pepseq)-1)] <- xion$mz[i] - 18.01056
                if('z' %in% ions) zion$mz[i+2*(length(pepseq)-1)] <- zion$mz[i] - 18.01056
            } else {}
        }
    }
    ans <- rbind(aion, bion, cion, xion, yion, zion)
    ans <- ans[-which(sapply(ans$mz, is.na)), ]
    ans
}
#' Annotate a spectrum based on a peptide
#' 
#' This function takes a spectrum as returned by getSpec() and, based on a
#' peptide sequence and a sensitivity, annotate it with fragment ions.
#' 
#' @param spec A data.frame as returned by getSpec()
#' 
#' @param pepseq A character string with the one-letter code for the peptide
#' sequence
#' 
#' @param modifications A vector of delta-masses for the modifications on the
#' peptide
#' 
#' @param ppm The sensitivity used during matching
#' 
#' @param ions The fragment ion types to match
#' 
#' @param neutralLosses Logical. Should neutral losses be calculated. Defaults 
#' to TRUE
#' 
#' @return A data.frame as spec but extended with the columns: ion and index
#' 
#' @noRd
#' 
annotateSpec <- function(spec, pepseq, modifications=list(), ppm=20, ions='abcxyz', neutralLosses=TRUE) {
    ionlab <- data.frame(spec, ion=NA, index=NA, stringsAsFactors=FALSE)
    ionlist <- fragPattern(pepseq, modifications, ions=ions, neutralLosses=neutralLosses)
    for(i in 1:nrow(ionlist)){
        ind <- which(ionlab$mz < ionlist$mz[i]+(ionlist$mz[i]/1000000)*ppm & ionlab$mz > ionlist$mz[i]-(ionlist$mz[i]/1000000)*ppm)
        if(length(ind) == 1){
            ionlab$ion[ind] <- ionlist$ion[i]
            ionlab$index[ind] <- ionlist$index[i]
        } else if(length(ind) > 1){
            ind <- ind[which.min(abs(ionlab$mz[ind]-ionlist$mz[i]))]
            ionlab$ion[ind] <- ionlist$ion[i]
            ionlab$index[ind] <- ionlist$index[i]
        } else {}
    }
    ionlab
}
#' Extract an ion chromatogram from an mzRamp object
#' 
#' This function takes a number of scans and a high and low mz value and extract
#' an EIC from an mzRamp object. The EIC is base peak and not TIC.
#' 
#' @param data An mzRamp object
#' 
#' @param scans A vector of scan indexes. Usually only MS1 scans
#' 
#' @param low The lower mz bound
#' 
#' @param high The higher mz bound
#' 
#' @return A matrix with 1 column giving the intensity for each scan
#' 
#' @noRd
#' 
getEIC <- function(data, scans, low, high) {
    spectra <- peaks(data, scans)
    if(class(spectra) == 'matrix') spectra <- list(spectra)
    uSpectra <- do.call('rbind', spectra)
    uSpectra[uSpectra[,1] < low | uSpectra[,1] > high, 2] <- NA
    matrix(sapply(split(uSpectra[,2], rep(1:length(spectra), times=sapply(spectra, nrow))), function(x) {if(all(is.na(x))) 0 else max(x, na.rm=TRUE)}), ncol=1)
}
#' Extract the chromatographic trace of an ion
#' 
#' This function start from an ion in a scan and traces this ion backwards and
#' forwards in chromatographic time ending up with an EIC. The algorithm tries 
#' to include the full chromatographic peak without extending out to noise
#' 
#' @param data An mzRamp object
#' 
#' @param index The scan number to start at
#' 
#' @param mz The mz value of the ion to trace
#' 
#' @param ppm The tolerated sensitivity
#' 
#' @param meanwidth The expected peakwidth in scan number. Has only an effect 
#' in performance. It will not fail on wider peaks.
#' 
#' @return A data.frame with columns: intensity, retention and acquisitionNum
#' 
#' @noRd
#' 
getIonTrace <- function(data, metaML, index, mz, ppm, meanwidth=50){
    widthMod <- 1.5
    ms1Indexes <- which(metaML$msLevel == 1)
    indexIndex <- which(ms1Indexes == index)
    mzRes <- (mz/1000000)*ppm*2
    mzMin <- mz-mzRes/2
    mzMax <- mz+mzRes/2
    maxIter <- 10
    
    
    indexWindow <- c((indexIndex-ceiling(widthMod*meanwidth)), (indexIndex+ceiling(widthMod*meanwidth)))
    windowSeed <- ceiling(diff(indexWindow)/2)
    if (indexWindow[1] < 1) {
        windowSeed <- windowSeed - length(seq(from=indexWindow[1], to=0))
        indexWindow[1] <- 1
    }
    if (indexWindow[2] > length(ms1Indexes)) {
        indexWindow[2] <- length(ms1Indexes)
    }
    unfinished <- 0
    iter <- 1
    while (TRUE && iter <= maxIter) {
        if (unfinished) {
            if (unfinished == 1) {
                addScans <- seq(from=indexWindow[2]+1, length=meanwidth/2)
                trace <- rbind(trace, getEIC(data, ms1Indexes[addScans], mzMin, mzMax))
                indexWindow[2] <- max(addScans)
            } else {
                addScans <- seq(to=indexWindow[1]-1, length=meanwidth/2)
                trace <- rbind(getEIC(data, ms1Indexes[addScans], mzMin, mzMax), trace)
                windowSeed <- windowSeed+(indexWindow-min(addScans))
                indexWindow[1] <- min(addScans)
            }
            scanWindow <- ms1Indexes[indexWindow[1]:indexWindow[2]]
        } else {
            scanWindow <- ms1Indexes[indexWindow[1]:indexWindow[2]]
            trace <- getEIC(data, scanWindow, mzMin, mzMax)
        }
        smooth <- predict(loess(trace[,1] ~ seq(1,nrow(trace)), span=0.25))
        differential <- sign(diff(smooth))
        middle <- windowSeed
        indexPos <- differential[middle]
        
        if (indexPos == 1) {
            distTop <- which(differential[middle:length(differential)] == -1)[1]
            if (is.null(distTop) || is.na(distTop)) {
                if (indexWindow[2] >= length(ms1Indexes)) {
                    indexWindow[2] <- length(ms1Indexes)
                    top <- length(smooth)
                    break
                }
                unfinished = 1
                iter <- iter+1
            } else {
                top = middle+distTop-1
                break
            }
        } else if (indexPos == -1) {
            distTop <- which(rev(differential[1:middle]) == 1)[1]
            if (is.null(distTop) || is.na(distTop)) {
                if (indexWindow[1] <= 1) {
                    indexWindow[1] <- 1
                    top <- 1
                    break
                }
                unfinished = -1
                iter <- iter+1
            } else {
                top <- middle-distTop
                break
            }
        } else {
            break
        }
    }
    max <- smooth[top]
    gauss <- function(p) {
        d=max*dnorm(seq_along(smooth), top, p)
        sum((d-smooth)^2)
    }
    
    bw <- 2.35*optimize(gauss, c(0.1, meanwidth*2))$minimum
    
    scans <- round(c(-bw*3, bw*3) + (indexWindow[1]:indexWindow[2])[top])
    if (scans[1] < 1) scans[1] <- 1
    if (scans[2] > length(ms1Indexes)) scans[2] <- length(ms1Indexes)
    if (scans[1] < indexWindow[1]) {
        addScans <- seq(from=scans[1], to=indexWindow[1]-1)
        trace <- rbind(getEIC(data, ms1Indexes[addScans], mzMin, mzMax), trace)
        top <- top+(indexWindow-min(addScans))
        indexWindow[1] <- scans[1]
    }
    if (scans[2] > indexWindow[2]) {
        addScans <- seq(from=indexWindow[2]+1, to=scans[2])
        trace <- rbind(trace, getEIC(data, ms1Indexes[addScans], mzMin, mzMax))
        indexWindow[2] <- scans[2]
    }
    trace <- cbind(trace, indexWindow[1]:indexWindow[2])
    scanRange <- scans[1]:scans[2]
    data.frame(intensity=trace[trace[,2] %in% scanRange, 1], retention=metaML$retentionTime[ms1Indexes[scanRange]], acquisitionNum=metaML$acquisitionNum[ms1Indexes[scanRange]])
}

#' Extract the trace of a parent ion
#' 
#' Given an MS2 scan find the parent ion scan and get the chromatographic trace
#' for that ion. Furthermore annotate the trace with which scan is the parent 
#' and possible additional ions selected for fragmentation
#' 
#' @param data An mzRamp object
#' 
#' @param scan An acquisition number to an MS2 scan
#' 
#' @param ppm The sensitivity used for the ion trace
#' 
#' @return A data.frame with columns: intensity, retention, acquisitionNum, 
#' parent and MS2scan
#' 
#' @noRd
#' 
traceParent <- function(data, metaML, scan, ppm=20) {
    scans <- metaML
    scanInfo <- header(data, which(scans$acquisitionNum == scan))
    parentInfo <- scans[scans$acquisitionNum == scanInfo$precursorScanNum, ]
    
    if(nrow(parentInfo) == 0) return(NULL)
    
    trace <- getIonTrace(data, metaML, which(scans$acquisitionNum == scanInfo$precursorScanNum), scanInfo$precursorMZ, ppm=ppm)
    
    trace$parent <- trace$retention == parentInfo$retentionTime
    
    trace$MS2scan <- NA
    
    mzWin <- c(scanInfo$precursorMZ-(scanInfo$precursorMZ/1000000)*ppm, scanInfo$precursorMZ+(scanInfo$precursorMZ/1000000)*ppm)
    for(i in 1:nrow(trace)) {
        if(trace$parent[i]) {
            trace$MS2scan[i] <- scanInfo$acquisitionNum
        } else {
            ms2 <- which(scans$precursorScanNum == trace$acquisitionNum[i] & scans$precursorMZ > mzWin[1] & scans$precursorMZ < mzWin[2])
            if (length(ms2) == 1) {
                trace$MS2scan[i] <- scans$acquisitionNum[ms2]
            } else if (length(ms2) > 1) {
                ms2 <- ms2[which.min(abs(scans$precursorMZ[ms2] - scanInfo$precursorMZ))]
                trace$MS2scan[i] <- scans$acquisitionNum[ms2]
            }
        }
    }
    if(nrow(trace) > 0) {
        return(trace)
    } else {
        return(NULL)
    }
}
thomasp85/MSGFgui-release documentation built on May 31, 2019, 11:09 a.m.