# Copyright 2004 by Bob Bussell. All rights reserved. # This code is part of the Biopython distribution and governed by its # license. Please see the LICENSE file that should have been included # as part of this package. """Tools to manipulate data from nmrview .xpk peaklist files.""" from __future__ import print_function HEADERLEN = 6 class XpkEntry(object): """Provide dictonary access to single entry from nmrview .xpk file. This class is suited for handling single lines of non-header data from an nmrview .xpk file. This class provides methods for extracting data by the field name which is listed in the last line of the peaklist header. Parameters ---------- xpkentry : str The line from an nmrview .xpk file. xpkheadline : str The line from the header file that gives the names of the entries. This is typically the sixth line of the header, 1-origin. Attributes ---------- fields : dict Dictionary of fields where key is in header line, value is an entry. Variables are accessed by either their name in the header line as in self.field["H1.P"] will return the H1.P entry for example. self.field["entrynum"] returns the line number (1st field of line) """ def __init__(self, entry, headline): """Initialize the class.""" # Holds all fields from input line in a dictionary # keys are data labels from the .xpk header self.fields = {} datlist = entry.split() headlist = headline.split() i = 0 for i in range(len(datlist) - 1): self.fields[headlist[i]] = datlist[i + 1] i = i + 1 try: self.fields["entrynum"] = datlist[0] except IndexError as e: pass class Peaklist(object): """Provide access to header lines and data from a nmrview xpk file. Header file lines and file data are available as attributes. Parameters ---------- infn : str The input nmrview filename. Attributes ---------- firstline : str The first line in the header. axislabels : str The axis labels. dataset : str The label of the dataset. sw : str The sw coordinates. sf : str The sf coordinates. datalabels : str The labels of the entries. data : list File data after header lines. Examples -------- >>> from Bio.NMR.xpktools import Peaklist >>> peaklist = Peaklist('../Doc/examples/nmr/noed.xpk') >>> peaklist.firstline 'label dataset sw sf ' >>> peaklist.dataset 'test.nv' >>> peaklist.sf '{599.8230 } { 60.7860 } { 60.7860 }' >>> peaklist.datalabels ' H1.L H1.P H1.W H1.B H1.E H1.J 15N2.L 15N2.P 15N2.W 15N2.B 15N2.E 15N2.J N15.L N15.P N15.W N15.B N15.E N15.J vol int stat ' """ def __init__(self, infn): """Initialize the class.""" with open(infn, "r") as infile: # Read in the header lines self.firstline = infile.readline().split("\012")[0] self.axislabels = infile.readline().split("\012")[0] self.dataset = infile.readline().split("\012")[0] self.sw = infile.readline().split("\012")[0] self.sf = infile.readline().split("\012")[0] self.datalabels = infile.readline().split("\012")[0] # Read in the data lines to a list self.data = [line.split("\012")[0] for line in infile] def residue_dict(self, index): """Return a dict of lines in 'data' indexed by residue number or a nucleus. The nucleus should be given as the input argument in the same form as it appears in the xpk label line (H1, 15N for example) Parameters ---------- index : str The nucleus to index data by. Returns ------- resdict : dict Mappings of index nucleus to data line. Examples -------- >>> from Bio.NMR.xpktools import Peaklist >>> peaklist = Peaklist('../Doc/examples/nmr/noed.xpk') >>> residue_d = peaklist.residue_dict('H1') >>> sorted(residue_d.keys()) ['10', '3', '4', '5', '6', '7', '8', '9', 'maxres', 'minres'] >>> residue_d['10'] ['8 10.hn 7.663 0.021 0.010 ++ 0.000 10.n 118.341 0.324 0.010 +E 0.000 10.n 118.476 0.324 0.010 +E 0.000 0.49840 0.49840 0'] """ maxres = -1 minres = -1 # Cast the data lines into the xpentry class self.dict = {} for i in range(len(self.data)): line = self.data[i] ind = XpkEntry(line, self.datalabels).fields[index + ".L"] key = ind.split(".")[0] res = int(key) if maxres == -1: maxres = res if minres == -1: minres = res maxres = max([maxres, res]) minres = min([minres, res]) if str(res) in self.dict: # Append additional data to list under same key templst = self.dict[str(res)] templst.append(line) self.dict[str(res)] = templst else: # This is a new residue, start a new list self.dict[str(res)] = [line] # Use [] for list type self.dict["maxres"] = maxres self.dict["minres"] = minres return self.dict def write_header(self, outfn): """Write header lines from input file to handle ``outfn``.""" with open(outfn, "w") as outfile: outfile.write(self.firstline) outfile.write("\012") outfile.write(self.axislabels) outfile.write("\012") outfile.write(self.dataset) outfile.write("\012") outfile.write(self.sw) outfile.write("\012") outfile.write(self.sf) outfile.write("\012") outfile.write(self.datalabels) outfile.write("\012") def replace_entry(line, fieldn, newentry): """Replace an entry in a string by the field number. No padding is implemented currently. Spacing will change if the original field entry and the new field entry are of different lengths. """ # This method depends on xpktools._find_start_entry start = _find_start_entry(line, fieldn) leng = len(line[start:].split()[0]) newline = line[:start] + str(newentry) + line[(start + leng) :] return newline def _find_start_entry(line, n): """Find the starting character for entry ``n`` in a space delimited ``line`` (PRIVATE). n is counted starting with 1. The n=1 field by definition begins at the first character. Returns ------- starting character : str The starting character for entry ``n``. """ # This function is used by replace_entry infield = 0 # A flag that indicates that the counter is in a field if n == 1: return 0 # Special case # Count the number of fields by counting spaces c = 1 leng = len(line) # Initialize variables according to whether the first character # is a space or a character if line[0] == " ": infield = 0 field = 0 else: infield = 1 field = 1 while c < leng and field < n: if infield: if line[c] == " " and not (line[c - 1] == " "): infield = 0 else: if not line[c] == " ": infield = 1 field = field + 1 c = c + 1 return c - 1 def data_table(fn_list, datalabel, keyatom): """Generate a data table from a list of input xpk files. Parameters ---------- fn_list : list List of .xpk file names. datalabel : str The data element reported. keyatom : str The name of the nucleus used as an index for the data table. Returns ------- outlist : list List of table rows indexed by ``keyatom``. """ # TODO - Clarify this docstring, add an example? outlist = [] dict_list, label_line_list = _read_dicts(fn_list, keyatom) # Find global max and min residue numbers minr = dict_list[0]["minres"] maxr = dict_list[0]["maxres"] for dictionary in dict_list: if maxr < dictionary["maxres"]: maxr = dictionary["maxres"] if minr > dictionary["minres"]: minr = dictionary["minres"] res = minr while res <= maxr: # s.t. res numbers count = 0 line = str(res) for dictionary in dict_list: # s.t. dictionaries label = label_line_list[count] if str(res) in dictionary: line = ( line + "\t" + XpkEntry(dictionary[str(res)][0], label).fields[datalabel] ) else: line = line + "\t" + "*" count = count + 1 line = line + "\n" outlist.append(line) res = res + 1 return outlist def _read_dicts(fn_list, keyatom): """Read multiple files into a list of residue dictionaries (PRIVATE).""" dict_list = [] datalabel_list = [] for fn in fn_list: peaklist = Peaklist(fn) dict = peaklist.residue_dict(keyatom) dict_list.append(dict) datalabel_list.append(peaklist.datalabels) return [dict_list, datalabel_list] if __name__ == "__main__": from Bio._utils import run_doctest run_doctest()