Source code for spike.plugins.MS.FTMS_calib

#!/usr/bin/env python 
# encoding: utf-8

"""A utility for calibration of MS experiments

based on a list of experimentally measured m/z and theoretical ones
The method will reduce the difference between lists.

adds ppm() ppm_error() and display_icalib() methods to the FTMSAxis object
     and imzmeas and mzref attributes

Adds the following methods():
to FTICR datasets
    set_calib(mzmeas, mzref, axis=1)
    calib(axis=1, method='l1', verbose=False)
    display_calib(axis=1, compare=False)

to FTICR axes
    display_icalib(xref, mzref, symbol='bo')
    ppm_error(xref, mzref)
    ppm(xref, mzref)
    display_icalib(xref, mzref, symbol='bo')

and the following attributes:
    RefAxis: a backup FTICRAxis, used to store the previous calibration

to FTICR axes
    mzref : list of m/z of reference values
    imzmeas : list of pea indices of reference peaks (to be match with mzref)

"""

from __future__ import print_function
import numpy as np
from numpy.linalg import norm
from scipy.optimize import curve_fit, least_squares, leastsq, minimize
from spike import NPKError
from spike.NPKData import NPKData_plugin
from spike.util.signal_tools import findnoiselevel
import spike.Display.testplot as testplot
plt = testplot.plot()
from spike.FTMS import FTMSAxis

########################################################################
# mathematics
########################################################################
[docs]def mzcalib(xind, ref, axis, method='l1'): """ fit axis parameters so that points located at xind be closest to ref values fits on two parameters if the 3rd (ML3 ie axis.calibC) is zero method = l2 uses levenberg-marqtart on l2 norm : classical method method = l1 uses powell on l1 norm : robust method """ axfit = axis.copy() # create copy if axfit.calibC == 0: Po = [axfit.calibA, axfit.calibB] else: Po = [axfit.calibA, axfit.calibB, axfit.calibC] if method == 'l2': Pn, info = leastsq(distcalib, Po, args=(xind, ref, axfit)) elif method == 'l1': res = minimize(l1calib, Po, args=(xind, ref, axfit), method='Powell') Pn = res.x else: raise Exception("method is either l1 or l2") if axfit.calibC == 0: axfit.calibA, axfit.calibB = Pn else: axfit.calibA, axfit.calibB, axfit.calibC = Pn return axfit
[docs]def l1calib(param, xref, mzref, axis): """ computes the sum of residual to minimize when calibrating basically a wrapper around axis.ppm """ axis.calibA = param[0] axis.calibB = param[1] try: axis.calibC = param[2] except IndexError: pass return axis.ppm(xref, mzref)
[docs]def distcalib(param, xref, mzref, axis): """ computes the residual to minimize when calibrating basically a wrapper around axis.ppm_error """ axis.calibA = param[0] axis.calibB = param[1] try: axis.calibC = param[2] except IndexError: pass return axis.ppm_error(xref, mzref)
[docs]def computes_index(meas, axis): "locates in index measured values" return np.array([axis.mztoi(r) for r in meas])
[docs]def icalib(npk, xind, ref, axis=1, method='l1', verbose=False): """ given a list of location in index 'xind' and of theoretical values 'ref' the current FTMS experiment is recalibrated optimatly along its axis 'axis' (usefull only in 2D) uses the current (2 or 3 parameters) calibration method is either 'l1' (robust) or 'l2' (classic) The current calibration is copied to a new unused axis called RefAxis """ # find axis todo = npk.test_axis(axis) npk.RefAxis = npk.axes(todo).copy() # for latter comparison ax2fit = npk.axes(todo).copy() # for action # fit if verbose: print( 'before - mean offset:', ax2fit.ppm(xind, ref), 'ppm') fitted = mzcalib(xind, ref, ax2fit, method=method) if verbose: print( ' after - mean offset:', fitted.ppm(xind, ref), 'ppm') # and assign npk.axes(todo).calibA = fitted.calibA npk.axes(todo).calibB = fitted.calibB npk.axes(todo).calibC = fitted.calibC if verbose: print( ' final - mean offset:', npk.axes(todo).ppm(xind, ref), 'ppm') print('**',npk.axis1.calibA, npk.axis1.calibB, npk.axis1.calibC) return npk
########################################################################################## ######### The following definitions are meant for being inserted into FTMS.FTMSAxis objects ##########################################################################################
[docs]def ppm_error(axis, xref, mzref): """ computes the error from a array of positions (xref) and the theoretical m/z (mzref) returns an array with errors in ppm xref : array of point coordinates of the reference points mzref: array of reference m/z """ return 1E6*(axis.itomz(xref)-mzref)/mzref
setattr(FTMSAxis, "ppm_error", ppm_error)
[docs]def ppm(axis, xref, mzref): """ computes the mean error in ppm from a array of positions (xref) and the theoretical m/z (mzref) uses l1 norm ! xref : array of point coordinates of the reference points mzref: array of reference m/z """ shift = axis.ppm_error(xref, mzref) return abs(shift).mean()
setattr(FTMSAxis, "ppm", ppm) def _display_icalib(axis, xref, mzref, symbol='bo'): """ generates a plot of the current calibration xref : list of point coordinates of the reference points mzref: list of reference m/z symbol: matplotlib notation for points (default is blue rounds) """ maxis = np.linspace(min(axis.mzref)-10, max(axis.mzref)+10,10) # sample mass axis plt.plot(maxis, np.zeros_like(maxis),'k--') # and draw a line at zero plt.plot(maxis, np.ones_like(maxis),'k:') plt.plot(maxis, -1*np.ones_like(maxis),'k:') plt.plot(mzref, axis.ppm_error(xref, mzref), symbol) # plt.plot(mzref,1E6*(axis.itomz(xref)-mzref)/mzref, symbol) # plot position of ref points plt.xlabel('$m/z$') plt.ylabel('$ppm$') setattr(FTMSAxis, "display_icalib", _display_icalib) setattr(FTMSAxis, "mzref", np.empty(10)) # template for reference m/z setattr(FTMSAxis, "imzmeas", np.empty(10)) # template for index of measured m/z ########################################################################################## ######### high level tools ##########################################################################################
[docs]def display_calib(npkd, axis=1, compare=False): """ generates a plot of the current calibration if compare is True, will try to draw the previous calibration curve along with the current one """ todo = npkd.test_axis(axis) axistodo = npkd.axes(todo) # xref = computes_index(meas, axistodo) plt.figure() axistodo.display_icalib(axistodo.imzmeas, axistodo.mzref) if compare: try: npkd.RefAxis.display_icalib(axistodo.imzmeas, axistodo.mzref, symbol='rx') maxis = np.linspace(min(axistodo.mzref)-10, max(axistodo.mzref)+10,1000) plt.plot(maxis, -axistodo.ppm_error( npkd.RefAxis.mztoi( maxis), maxis), 'r:' ) except AttributeError: raise Exception('No Reference Axis available') plt.title("Mean error %.3f ppm"%(axistodo.ppm(axistodo.imzmeas, axistodo.mzref))) return npkd
[docs]def set_calib(npkd, mzmeas, mzref, axis=1): """ preset parameters for mass calibration mzref : list of reference m/z of calibrants mzmeas : list of measured m/z of calibrants axis is the axis to be calibrated, defaut is 1 """ todo = npkd.test_axis(axis) npkd.axes(todo).mzref = np.array(mzref) npkd.axes(todo).imzmeas = npkd.axes(todo).mztoi( np.array(mzmeas) ) # convert to index (invariant !) return npkd
[docs]def calib(npk, axis=1, method='l1', verbose=False): """ the current FTMS experiment is recalibrated optimaly along its axis 'axis' (usefull only in 2D) using parameters provided with set_calib() uses the current (2 or 3 parameters) calibration method is either 'l1' (robust) or 'l2' (classic) The current calibration is copied to a new unused axis called RefAxis """ # find axis todo = npk.test_axis(axis) npk.RefAxis = npk.axes(todo).copy() # for latter comparison ax2fit = npk.axes(todo).copy() # for action # fit if verbose: print( 'before - mean offset:', ax2fit.ppm(ax2fit.imzmeas, ax2fit.mzref), 'ppm') fitted = mzcalib(ax2fit.imzmeas, ax2fit.mzref, ax2fit, method=method) if verbose: print( ' after - mean offset:', fitted.ppm(ax2fit.imzmeas, ax2fit.mzref), 'ppm') # and assign npk.axes(todo).calibA = fitted.calibA npk.axes(todo).calibB = fitted.calibB npk.axes(todo).calibC = fitted.calibC if verbose: print( ' final - mean offset:', npk.axes(todo).ppm(ax2fit.imzmeas, ax2fit.mzref), 'ppm') print('**',npk.axis1.calibA, npk.axis1.calibB, npk.axis1.calibC) return npk
[docs]def calib_loadref(npkd, fname, recalibrate=False, axis=1): """ Reads in a *.ref Bruker file holding a set of calibrating values for MS with the following format: # comments # TuneMixPos m/z charge C5H12O2N 118.086255 +1 C6H19O6N3P3 322.048121 +1 ... and associate the reference list to the experiment, preparing for recalibration if recalibrate is True, values will be recomputed, with values interpreted as formula or peptides (A letter code) """ pass # not finisehd
[docs]def read_ref(fname): """ Reads in a *.ref Bruker file holding a set of calibrating values for MS returns a referencing dictionary {'FORMULA':(value,charge), ... } """ import re ref = {} with open(fname,'r') as F: lines = F.readlines() for li in lines: if li.startswith('#'): continue # skip comments if not li.strip(): continue # and empty lines lis = li.split() try: val = float(lis[1]) except: print('m/z not valid - skipping line:',li.strip()) continue try: m = re.search('\d+',lis[2]) charge = float(m.group()) if '-' in lis[2]: charge *= -1 except: print('charge not valid - skipping line:',li.strip()) continue try: ref[lis[0]] = (val, charge) except: print('skipping line:',li.strip()) return ref
[docs]def recalibrate(calib): """ a referencing dictionary {'FORMULA':value, ... } """ "recalibrates calib list" import isotopes as iso diff = [] for mol,values in calib.items(): mass, charge = values peptide = False try: nmass = iso.parse_peptide(mol).monoisotop(charge) peptide = True except: pass if not peptide: try: nmass = iso.parse_formula(mol).monoisotop(charge) peptide = False except: print('could not understand %s'%mol) continue if nmass < 10.0: continue if peptide and charge >0: # then add H+ nmass += iso.parse_formula('H').monoisotop() diff.append(1E6*abs(mass-nmass)/mass) calib[mol] = (nmass, charge) print('recalibrate mean: %.3f ppm max: %.3f ppm'%(sum(diff)/len(diff), max(diff))) return calib
# and plug the whole stuf into NPKData NPKData_plugin("set_calib", set_calib) NPKData_plugin("calib", calib) NPKData_plugin("display_calib", display_calib)