Exemplo n.º 1
0
    def plot_spectrum(self, data=None):
        """ Plot the spectrum. """

        if data is not None:
            self.data = data

        if self._data is None:
            return
        self.ax.clear()
        self.ax.set_xlabel('mass/charge')
        self.ax.set_ylabel('probability')
        self.ax.set_title('Mass interference spectrum')
        self.ax.minorticks_on()
        self.ax.grid(True)


        self.colours = [_redF if c else _blueF for c in self.data['target']]

        self.data_points = self.ax.scatter(self.x, self.y, marker='D', c=self.colours)

        self.data_lines = self.ax.vlines(self.x, [self.minimum] * self.data.shape[0],
                                    self.y, colors=self.colours, linewidth=3)

        self.renderer = self.fig.canvas.get_renderer()

        self.labels = []
        for molec, x, y in zip(self.data['molecule'].iloc[:self.MAX_LABELS], self.x, self.y):
            m = Molecule(molec)
            l = m.formula(all_isotopes=True, style='latex')

            label = self.ax.text(x, y, l, horizontalalignment='center')

            # Set initial offset
            self.shift_label(label)
            self.labels.append(label)

        # Iterate over labels until none overlap
        done = False
        while not done:
            done = True
            for label1, label2 in combinations(self.labels, 2):
                box1 = label1.get_window_extent(self.renderer)
                box2 = label2.get_window_extent(self.renderer)
                if box1.intersection(box1, box2):
                    self.shift_label(label2)
                    # There was an overlap, check again.
                    done = False

        # Draw thin lines from datapoints to labels
        for label, x, y in zip(self.labels, self.x, self.y):
            lx, ly = label.get_position()
            self.ax.plot((x,lx), (y, ly), linewidth=0.5, color='black')

        # For log plot
        self.minimum = self.data['probability'].min()/100
        self.ax.autoscale()
        self.ax.set_ybound(lower=self.minimum)
        xbound = massbound()
        self.ax.set_xbound(xbound[0],xbound[1])
        self.canvas.draw()
Exemplo n.º 2
0
    def show_standard_ratio(self):
        """ Show the standard ratios. """
        if not self.check_atoms_input():
            return

        data = standard_ratio(self.atoms)
        data['target'] = False
        if self.check_mz_input() and isinstance(self.mz, str):
            m = Molecule(self.mz)
            target_data = standard_ratio(m.elements)
            target_data['target'] = True
            data = data.append(target_data)
        data.index = range(1, data.shape[0] + 1)

        model = TableModel(data, table='std_ratios')
        self.table_output.setModel(model)
        self.table_output.setColumnHidden(5, False)
        self.table_output.setColumnHidden(6, True)
        try:
            # PyQt5
            self.table_output.horizontalHeader().setSectionResizeMode(
                widgets.QHeaderView.Stretch)
        except AttributeError:
            # PyQt4
            self.table_output.horizontalHeader().setResizeMode(
                widgets.QHeaderView.Stretch)
Exemplo n.º 3
0
def standard_ratio(atoms, style='plain'):
    """ Give the stable isotopes and their standard abundance for the given element(s). """
    data = periodic_table[periodic_table['element'].isin(atoms)].copy()
    data['ratio'] = 1.0
    data['inverse ratio'] = 1.0
    for a in atoms:
        abun = data.loc[data['element'] == a, 'abundance'].copy()
        ratio = abun / abun.max()
        inv_ratio = 1 / ratio
        data.loc[data['element'] == a, 'ratio'] = ratio
        data.loc[data['element'] == a, 'inverse ratio'] = inv_ratio

    if style != 'plain':
        pretty_isotopes = []
        for i in data['isotope'].values:
            m = Molecule(i)
            pretty_isotopes.append(
                m.formula(style=style, show_charge=False, all_isotopes=True))
        data['isotope'] = pretty_isotopes

    return data[[
        'isotope', 'mass', 'abundance', 'ratio', 'inverse ratio', 'standard'
    ]]
Exemplo n.º 4
0
 def data(self, index, role=QtCore.Qt.DisplayRole):
     if index.isValid():
         if role == QtCore.Qt.DisplayRole:
             if self.table == 'interference':
                 if index.column() == 0:
                     # formula
                     molec = self._data.iloc[index.row(), index.column()]
                     try:
                         m = Molecule(molec)
                     except ParseException:
                         return molec
                     else:
                         return m.formula(style='html', all_isotopes=True)
                 elif index.column() == 1:
                     # mass
                     return '{:.6f}'.format(self._data.iloc[index.row(), index.column()])
                 elif index.column() == 2:
                     # mass difference
                     return '{:.7f}'.format(self._data.iloc[index.row(), index.column()])
                 elif index.column() == 3:
                     # MRP
                     return '{:.2f}'.format(self._data.iloc[index.row(), index.column()])
                 elif index.column() == 4:
                     # probability
                     return '{:.5g}'.format(self._data.iloc[index.row(), index.column()])
                 else:
                     return '{}'.format(self._data.iloc[index.row(), index.column()])
             elif self.table == 'std_ratios':
                 if index.column() == 0:
                     # formula
                     m = Molecule(self._data.iloc[index.row(), index.column()])
                     return m.formula(style='html', all_isotopes=True)
                 elif index.column() == 1:
                     # mass
                     return '{:.6f}'.format(self._data.iloc[index.row(), index.column()])
                 elif index.column() in (2, 3):
                     # rel. abundance and ratio
                     return '{:.5g}'.format(self._data.iloc[index.row(), index.column()])
                 elif index.column() == 4:
                     # inverse ratio
                     return '{:.2f}'.format(self._data.iloc[index.row(), index.column()])
                 else:
                     return '{}'.format(self._data.iloc[index.row(), index.column()])
         elif role == QtCore.Qt.TextAlignmentRole:
             if index.column() == 0:
                 return QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter
             else:
                 return QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
         elif role == QtCore.Qt.EditRole:
             return self._data.iloc[index.row(), index.column()]
         elif role == QtCore.Qt.BackgroundRole:
             if self._data['target'].iloc[index.row()]:
                 return QtGui.QColor(*_red, alpha=32)
Exemplo n.º 5
0
 def check_mz_input(self):
     """ Validate input for mz_input.
         Returns True on correct input, False on error.
     """
     mz = str(self.mz_input.text())
     if mz == '':
         self.mz = None
     else:
         try:
             self.mz = float(mz)
         except ValueError:
             try:
                 m = Molecule(mz)
             except:
                 self.warn('Enter target as a number or as a molecular formula.')
                 return False
             else:
                 self.mz = mz
     return True
Exemplo n.º 6
0
def interference(atoms,
                 target,
                 targetrange=0.3,
                 maxsize=5,
                 charge=[1],
                 chargesign='-',
                 style='plain'):
    """ For a list of atoms (the composition of the sample),
        calculate all molecules that can be formed from a
        combination of those atoms (the interferences),
        including all stable isotopes, up to maxsize atoms,
        that have a mass-to-charge ratio within target ± targetrange.

        The target can be given as a mass-to-charge ratio or as a
        molecular formula. Molecular formulas are interpreted by Molecule().
        See Molecule() docstring for a detailed explanation on how to enter
        molecular formulas. If target is None, no filtering will be done and
        all possible combinations of all atoms and isotopes up to maxsize
        length will be calculated. Target information will be added to the
        output, unless target is None.

        Charge is usually 1, irrespective of sign. Give charge = [1, 2, 3]
        to also include interferences with higher charges. Masses are 
        adjusted for missing electrons (+ charge), extra electrons (- charge),
        or not adjusted (o charge, lower-case letter O). Setting charge=0
        has the same effect as setting chargesign='o'. The charge for the
        target ion, if target is specified as molecule instead of a number,
        can be different from the charge on the interferences. If no charge is
        specified for the target, the first charge and the chargesign of the
        interferences are used for the target.

        Molecular formulas are formatted in style (default is 'plain').
        See Molecule() for more options.

        Returns a pandas.DataFrame with a column 'molecule' with molecular formula,
        a column 'charge', a column 'mass/charge' for the mass-to-charge ratio, a
        column 'mass/charge diff' for the mass/charge difference between this ion
        and the target mass/charge, a column 'MRP' which gives the mass-resolving
        power (mz/Δmz) needed to resolve this ion from the target ion, a column
        'target', which indicates whether this row was specified as the target,
        and a column 'probability', which gives the combinatorial probability of
        encoutering this combination of isotopes, given the composition of the
        sample and the natural abundances of the isotopes.
    """
    if isinstance(charge, (int, float, str)):
        charge = tuple(int(charge))
    elif isinstance(charge, (tuple, list)):
        charge = tuple(int(c) for c in charge)
    else:
        raise ValueError(
            'charge must be given as a number or a list of numbers.')

    if chargesign not in ('+', '-', 'o', '0'):
        raise ValueError('chargesign must be either "+", "-", "o", or "0".')

    # How to handle charge?
    # 1. charge for interferences
    #       - can be multiple values
    #       - specified by parameter
    # 2. charge for target
    #       - only one value
    #       - can be different from 1
    #       - must be specified in target formula
    #       - if unspecified, take sign and first value from 1
    if target:
        try:
            target_mz = float(target)
            target = str(target)
            target_charge = 0
            target_chargesign = 'o'
            target_abun = 1
        except ValueError:
            m = Molecule(target)
            inferred_charge = False
            if m.chargesign:
                target_chargesign = m.chargesign
            else:
                target_chargesign = chargesign
                inferred_charge = True
            if m.charge:
                target_charge = m.charge
            else:
                target_charge = charge[0]
                inferred_charge = True
            # If no charge was specified on target,
            # push the inferred charge back to target
            if inferred_charge:
                if target_charge == 0:
                    pass
                elif target_charge == 1:
                    target += ' {}'.format(target_chargesign)
                else:
                    target += ' {}{}'.format(target_charge, target_chargesign)
            target_mz = m.mass
            target_abun = m.abundance
            if m.charge > 0:
                # mass correction done in Molecule.parse()
                target_mz /= m.charge
    else:
        target_mz = 0
        target_charge = 0
        target_chargesign = '0'
        target_abun = 0

    # Retrieve info from perioic table for all atoms in sample.
    # Create a list with all possible combinations up to maxsize atoms.
    # Create same list for masses, combos are created in same order.
    picked_atoms = periodic_table[periodic_table['element'].isin(atoms)]
    isotope_combos = []
    mass_combos = []
    for size in range(1, maxsize + 1):
        i = itertools.combinations_with_replacement(picked_atoms['isotope'],
                                                    size)
        m = itertools.combinations_with_replacement(picked_atoms['mass'], size)
        isotope_combos.extend(list(i))
        mass_combos.extend(list(m))

    masses = pd.DataFrame(mass_combos).sum(axis=1)
    molecules = [' '.join(m) for m in isotope_combos]
    data = pd.DataFrame({'molecule': molecules, 'mass/charge': masses})

    # ignore charge(s) for sign o
    if chargesign in ('o', '0'):
        data['charge'] = 0
    else:
        data_w_charge = []
        for ch in charge:
            d = data.copy()
            d['charge'] = ch
            if ch == 0:
                data_w_charge.append(d)
                continue
            elif ch == 1:
                charge_str = ' {}'.format(chargesign)
            else:
                charge_str = ' {}{}'.format(ch, chargesign)
            d['molecule'] += charge_str
            d['mass/charge'] /= ch
            if chargesign == '+':
                d['mass/charge'] -= mass_electron
            else:
                d['mass/charge'] += mass_electron
            data_w_charge.append(d)
        data = pd.concat(data_w_charge)

    if target:
        data = data.loc[(data['mass/charge'] >= target_mz -
                         round(targetrange * target_mz / 2000000, 3))
                        & (data['mass/charge'] <= target_mz +
                           round(targetrange * target_mz / 2000000, 3))]
        data['mass/charge diff'] = data['mass/charge'] - target_mz
        data['MRP'] = target_mz / data['mass/charge diff'].abs()
        global xmin
        xmin = target_mz - round(targetrange * target_mz / 2000000, 3)
        global xmax
        xmax = target_mz + round(targetrange * target_mz / 2000000, 3)

    else:
        data['mass/charge diff'] = 0.0
        data['MRP'] = pd.np.inf

    molec = []
    abun = []
    for molecule in data['molecule'].values:
        m = Molecule(molecule)
        abun.append(m.abundance)
        molec.append(m.formula(style=style))

    data['molecule'] = molec
    data['probability'] = abun
    data['target'] = False
    target_data = {
        'molecule': target,
        'charge': target_charge,
        'mass/charge': target_mz,
        'mass/charge diff': 0,
        'MRP': pd.np.inf,
        'probability': target_abun,
        'target': True
    }
    data = data.append(target_data, ignore_index=True)

    return data[[
        'molecule', 'charge', 'mass/charge', 'mass/charge diff', 'MRP',
        'probability', 'target'
    ]]