Module bioiain.utilities.sequences
Functions
def d3(resname)-
Expand source code
def d3(resname): try: ri = d3toint[resname] rn = d3to1[resname] except: log("warning", f"Unknown resname: {resname} (using UNK/X)") ri = 20 rn = "X" return rn, ri def intto1(i)-
Expand source code
def intto1(i): for d3, n in d3toint.items(): if i == n: return d3to1[d3] return "X"
Classes
class CLUSTAL (*args, verbose=False, run_msa=True, build_tree=False, matrix_path=None, **kwargs)-
Expand source code
class CLUSTAL(MSA): def __init__(self, *args, verbose=False, run_msa=True, build_tree=False, matrix_path=None, **kwargs): super().__init__(*args, **kwargs) kwargs.pop("name", None) if run_msa: self.msa_path = self._run_clustal_msa(name=self.name, verbose=verbose, matrix_path=matrix_path, **kwargs) self.msa_fasta = FASTA(self.msa_path) self.msa_fasta.rewrite() if build_tree: self.tree_path = self._build_tree(self.msa_path) def _run_clustal_msa(self, fasta_path=None, name="temp", out_folder=None, clustal_cmd="clustalw", matrix="BLOSUM", out_format="fasta", force=False, verbose=False, matrix_path=None, **kwargs): if fasta_path is None: fasta_path = self.fasta_path log(2, f"Calculating MSA ({matrix}) of: {fasta_path}") fname = f"{name}_{matrix}.ms.alignment.fasta" if matrix == "path": assert matrix_path is not None matrix = matrix_path if out_folder is None: out_folder = os.path.join(TEMP_FOLDER, "alignments") os.makedirs(out_folder, exist_ok=True) out_path = os.path.join(out_folder, fname) if os.path.exists(out_path) and not force: log(3, "Alignment already generated (CLUSTAL)") return out_path cmd = [ clustal_cmd, "-align", "-type=protein", f"-infile={fasta_path}", f"-matrix={matrix}", f"-pwmatrix={matrix}", f"-outfile={out_path}", f"-output={out_format}" f"-slow" ] if verbose: log(3, "$", " ".join(cmd)) subprocess.run(cmd) else: out_log = open("/dev/null", "w") subprocess.run(cmd, stdout=out_log) return out_path def _build_tree(self, align_path, force=False): log(2, f"Building tree for: {align_path}") out_path = align_path.replace(".fasta", ".nj") comp_file = out_path + ".list" if os.path.exists(out_path) and os.path.exists(comp_file) and not force: log(3, "Tree already generated") return out_path cmd = [ "clustalw", "-tree", "-type=protein", f"-infile={align_path}", "-outputtree=nj", ] #print("$", " ".join(cmd)) f = open(comp_file, "w") subprocess.run(cmd, stdout=f) return out_path def get_similar(self, target, name="temp", similarity=95): threshold = (100-similarity) / 100 log(2, f"Finding similar at {similarity}% for {target}") seq_num = self._get_seq_num(target) neighbour_nums = self._get_neighbours(seq_num, threshold=threshold) neighbour_names = [self._get_seq_name(n) for n in neighbour_nums] #print(neighbour_names) #exit() return neighbour_names def _get_seq_num(self, seq_name) -> int|None: #log(3, f"Finding seq_num for {seq_name}") comp_path = self.tree_path+".list" seq_num = None with open(comp_path, "r") as f: for line in f.readlines(): comps = line.split(" ") if len(comps) < 2: continue if seq_name in comps: seq_num = int(comps[1].replace(":", "")) break return seq_num def _get_seq_name(self, seq_num): #log(3, f"Finding seq_name for {seq_num}") comp_path = self.tree_path+".list" seq_name = None with open(comp_path, "r") as f: for line in f.readlines(): comps = line.split(" ") if len(comps) < 2: continue if f"{seq_num}:" == comps[1]: seq_name = comps[2] return seq_name def _get_neighbours(self, seq_num, threshold=0.05): log(3, f"Finding neighbours (seq. {seq_num}), threshold={threshold}") import re neighbours = [] with open(self.tree_path, "r") as f: for line in f.readlines(): if "DIST" in line and "length" in line: try: comps = [l for l in re.split(' |vs\.|;|=', line.strip()) if l != ""] num1 = int(comps[0]) num2 = int(comps[1]) dist = float(comps[3]) length = int(comps[5].replace("\n", "")) if dist > threshold: continue if seq_num == num1: neighbours.append(num2) elif seq_num == num2: neighbours.append(num1) except Exception as e: log("warning", f"Error reading tree file: {self.tree_path}") print(line) print(comps) raise e log(3, f"Found {len(neighbours)} neighbours") return neighboursAncestors
Methods
def get_similar(self, target, name='temp', similarity=95)-
Expand source code
def get_similar(self, target, name="temp", similarity=95): threshold = (100-similarity) / 100 log(2, f"Finding similar at {similarity}% for {target}") seq_num = self._get_seq_num(target) neighbour_nums = self._get_neighbours(seq_num, threshold=threshold) neighbour_names = [self._get_seq_name(n) for n in neighbour_nums] #print(neighbour_names) #exit() return neighbour_names
class FASTA (fasta_path)-
Expand source code
class FASTA(object): def __init__(self, fasta_path): self.fasta_path = fasta_path self.single_line = None def __repr__(self): return f"<bi.{self.__class__.__name__}: {self.fasta_path}>" def _parse_fasta(self, names=True, sequences=True, key=None): assert names or sequences if key is not None: if type(key) is str: key = [key] elif type is not list: key = list(key) fasta_dict = {} with open(self.fasta_path) as f: next_seq = False last_key = None wait_key = False for line in f.readlines(): line = line.replace("\n", "").strip() if line.startswith("#"): next_seq = True continue if line.startswith(">"): wait_key = False name = line[1:].strip() if key is not None: if len(key) == 0: break #print(name, key, name in key) if name in key: key.remove(name) else: wait_key = True continue if name not in fasta_dict: fasta_dict[name] = [] next_seq = True last_key = name continue elif wait_key: continue if not sequences: continue if line.strip() == "": next_seq = True continue else: if last_key is None: continue if next_seq: fasta_dict[last_key].append(line) next_seq = False else: fasta_dict[last_key][-1] += line if key is not None: #print(key) assert len(key) == 0 #print(names, sequences) if names and sequences: return fasta_dict elif names: return list(fasta_dict.keys()) elif sequences: seqs = [] [seqs.extend(seq) for seq in fasta_dict.values()] return seqs def rewrite(self, duplicates=False, empties=False, space_between=False, key_start="> "): log(3, "Rewriting FASTA:", self.fasta_path) data = self._parse_fasta() with open(self.fasta_path, "w") as f: for key, sequences in data.items(): if not empties: sequences = [s for s in sequences if len(s) > 0] n_seqs = len(sequences) if not duplicates: if n_seqs > 1: log("warning", f"{n_seqs} sequences for id: {key} (keeping only first)") sequences = sequences[:1] for seq in sequences: f.write(f"{key_start}{key}\n") f.write(f"{seq}\n") if space_between: f.write("\n") self.single_line = True return self.fasta_path def get_names(self, key=None): return self._parse_fasta(names=True, sequences=False, key=key) def get_sequences(self, key=None): return self._parse_fasta(names=False, sequences=True, key=key) def parse(self, key=None): return self._parse_fasta(key=key)Methods
def get_names(self, key=None)-
Expand source code
def get_names(self, key=None): return self._parse_fasta(names=True, sequences=False, key=key) def get_sequences(self, key=None)-
Expand source code
def get_sequences(self, key=None): return self._parse_fasta(names=False, sequences=True, key=key) def parse(self, key=None)-
Expand source code
def parse(self, key=None): return self._parse_fasta(key=key) def rewrite(self,
duplicates=False,
empties=False,
space_between=False,
key_start='> ')-
Expand source code
def rewrite(self, duplicates=False, empties=False, space_between=False, key_start="> "): log(3, "Rewriting FASTA:", self.fasta_path) data = self._parse_fasta() with open(self.fasta_path, "w") as f: for key, sequences in data.items(): if not empties: sequences = [s for s in sequences if len(s) > 0] n_seqs = len(sequences) if not duplicates: if n_seqs > 1: log("warning", f"{n_seqs} sequences for id: {key} (keeping only first)") sequences = sequences[:1] for seq in sequences: f.write(f"{key_start}{key}\n") f.write(f"{seq}\n") if space_between: f.write("\n") self.single_line = True return self.fasta_path
class MMSEQS2 (*args,
mmseqs_cmd='mmseqs',
db_name=None,
verbosity=1,
folder=None,
force=False,
**kwargs)-
Expand source code
class MMSEQS2(MSA): def __init__(self, *args, mmseqs_cmd="mmseqs", db_name=None, verbosity=1, folder=None, force=False, **kwargs): super().__init__(*args, **kwargs) self.fasta.rewrite(key_start=">") self.tmp_folder = os.path.join(TEMP_FOLDER, "mmseqs2") os.makedirs(self.tmp_folder, exist_ok=True) self.databases = {} self.mmseqs_cmd = mmseqs_cmd self.verbosity = verbosity self.name = self.name.replace(".dataset", "") if not self.name.endswith(".mmseqs"): self.name += ".mmseqs" if folder is None: folder = os.path.join(SUBDIR_NAME, "mmseqs") self.db_folder = os.path.join(folder, self.name) os.makedirs(self.db_folder, exist_ok=True) if force: self.delete() if db_name is None: db_name = self.name.split(".")[0] self.db_name = db_name if os.path.exists(self.db_path()): log(2, "Input is already DB, setup only") else: log(2, "Input is a file, creating DB...") self.create_db(db_name=db_name, **kwargs) def db_path(self, suffix="db"): return os.path.join(self.db_folder, f"{self.db_name}.{suffix}") def delete(self, db_name=None, make_dir=True): if db_name is None: shutil.rmtree(self.db_folder, ignore_errors=True) else: for file in self.db_folder: prefix = self.db_path(suffix=db_name) if file.startswith(prefix): os.remove(os.path.join(self.db_folder, file)) os.makedirs(self.db_folder, exist_ok=True) def _cmd(self, command, *args, **kwargs): if type(command) is str: command = [command] cmd = [self.mmseqs_cmd, *command] if "v" not in kwargs: kwargs["v"] = kwargs.pop("verbosity", self.verbosity) for kwarg, value in kwargs.items(): if not kwarg.startswith("--"): if len(kwarg) == 1: kwarg = f"-{kwarg}" else: kwarg = f"--{kwarg.replace('_', '-')}" cmd.extend([kwarg, str(value)]) cmd.extend([str(a) for a in args]) log(3, "$", " ".join(cmd)) subprocess.run(cmd) def create_db(self, force=False, fasta_path=None, **kwargs): if force: self.delete() if fasta_path is None: self.fasta.rewrite(key_start=">") fasta_path = self.fasta_path self.databases["sequence"] = self.db_path() self._cmd("createdb", fasta_path, self.databases["sequence"], createdb_mode=1, shuffle=0) return self def write(self, query, target=None, result=None, output_file=None, mode="tab", **kwargs): if mode.lower() == "tsv": cmd = ["createtsv"] extension = "tsv" elif mode.lower() == "fasta": cmd = ["result2flat"] extension = "fasta" elif mode.lower() == "tab" or mode.lower() == "alis": cmd = ["convertalis"] extension = "tab" else: raise NotImplementedError cmd.append(query) if output_file is None: if not output_file.endswith(f".{extension}"): output_file += f".{extension}" output_file = ".".join(query.split(".")[:-1]) if target is not None: cmd.append(target) if result is not None: cmd.append(result) cmd.append(output_file) try: self._cmd(cmd, **kwargs) except: raise TsvError() return output_file def cluster(self, reassign=False, force=False, linear=False, easy=False, **kwargs): self.databases["clustered"] = self.db_path("cluster") out_path = self.db_path("cluster")+ ".tsv" fasta_path = self.db_path("cluster")+ ".fasta" data_path = self.db_path("cluster") + ".json" if linear: cmd = ["linclust"] else: cmd = ["cluster"] if easy: cmd = ["easy-"+cmd[0]] cmd.extend([self.db_path(), self.db_path("cluster"), self.tmp_folder]) if reassign and not linear: cmd.append("--cluster-reassign") params = { "cmd": " ".join([str(c) for c in cmd]), "reassign":reassign, "linear":linear, "easy":easy, } if not os.path.exists(self.db_path("cluster")) or not os.path.exists(data_path): force=True if os.path.exists(data_path): if json.load(open(data_path))["params"] != params: log(3, "Different params detected") force = True if force: self.delete("cluster") try: self._cmd(*cmd) except: raise ClusteringError() else: log(3, "Cluster DB already clustered (mmseqs2)") if force or not os.path.exists(out_path): try: tsv = self.write(self.db_path(), self.db_path(), self.db_path("cluster"), out_path, mode="tsv") except: raise TsvError() try: clusters = {} print(out_path) with open(out_path) as f: for line in f: c, i = line.strip().split("\t") if c not in clusters: clusters[c] = {"name":c, "list": []} clusters[c]["list"].append(i) data = {"params": params, "clusters":{},} for c in clusters: data["clusters"][len(data["clusters"])] = {**clusters[c], "n": len(clusters[c]["list"])} json.dump(data, open(data_path, "w"), indent=4) except: raise ClusteringError() print("Cluster data:", data_path) return data_path def map(self, *args, **kwargs) -> pl.DataFrame: kwargs.pop("map", None) return self.search(*args, map=True, **kwargs) def search(self, query_db, exhaustive=True, map=False, dataset_name=None, **kwargs) -> pl.DataFrame: if map: cmd = ["map"] folder_name = "map" else: cmd = ["search"] folder_name = "search" if exhaustive: cmd.append("--exhaustive-search") cmd.extend(["--alignment-mode", "3"]) log(1, f"Searching({query_db}) in {self.db_path()} cmd={folder_name}") aligned_db = os.path.join(self.tmp_folder, folder_name, str(dataset_name)) shutil.rmtree(aligned_db, ignore_errors=True) os.makedirs(aligned_db, exist_ok=True) aligned_db = os.path.join(aligned_db, f"temp.{folder_name}") cmd.extend([query_db, self.db_path(), aligned_db, self.tmp_folder]) cmd.append("-a") try: self._cmd(*cmd, **kwargs) except: raise SearchError() columns = "query,target,evalue,raw,bits,fident,alnlen,pident,qcov,tcov,qlen,tlen,qstart,tstart,qaln,taln" tsv = self.write(query_db, self.db_path(), aligned_db, output_file=".".join(query_db.split(".")[:-1]) + f".{folder_name}.tab", mode="alis", format_mode=4, format_output=columns) df = pl.read_csv(tsv, separator="\t", schema_overrides={"query":pl.String, "target":pl.String}) log(df) return dfAncestors
Methods
def cluster(self, reassign=False, force=False, linear=False, easy=False, **kwargs)-
Expand source code
def cluster(self, reassign=False, force=False, linear=False, easy=False, **kwargs): self.databases["clustered"] = self.db_path("cluster") out_path = self.db_path("cluster")+ ".tsv" fasta_path = self.db_path("cluster")+ ".fasta" data_path = self.db_path("cluster") + ".json" if linear: cmd = ["linclust"] else: cmd = ["cluster"] if easy: cmd = ["easy-"+cmd[0]] cmd.extend([self.db_path(), self.db_path("cluster"), self.tmp_folder]) if reassign and not linear: cmd.append("--cluster-reassign") params = { "cmd": " ".join([str(c) for c in cmd]), "reassign":reassign, "linear":linear, "easy":easy, } if not os.path.exists(self.db_path("cluster")) or not os.path.exists(data_path): force=True if os.path.exists(data_path): if json.load(open(data_path))["params"] != params: log(3, "Different params detected") force = True if force: self.delete("cluster") try: self._cmd(*cmd) except: raise ClusteringError() else: log(3, "Cluster DB already clustered (mmseqs2)") if force or not os.path.exists(out_path): try: tsv = self.write(self.db_path(), self.db_path(), self.db_path("cluster"), out_path, mode="tsv") except: raise TsvError() try: clusters = {} print(out_path) with open(out_path) as f: for line in f: c, i = line.strip().split("\t") if c not in clusters: clusters[c] = {"name":c, "list": []} clusters[c]["list"].append(i) data = {"params": params, "clusters":{},} for c in clusters: data["clusters"][len(data["clusters"])] = {**clusters[c], "n": len(clusters[c]["list"])} json.dump(data, open(data_path, "w"), indent=4) except: raise ClusteringError() print("Cluster data:", data_path) return data_path def create_db(self, force=False, fasta_path=None, **kwargs)-
Expand source code
def create_db(self, force=False, fasta_path=None, **kwargs): if force: self.delete() if fasta_path is None: self.fasta.rewrite(key_start=">") fasta_path = self.fasta_path self.databases["sequence"] = self.db_path() self._cmd("createdb", fasta_path, self.databases["sequence"], createdb_mode=1, shuffle=0) return self def db_path(self, suffix='db')-
Expand source code
def db_path(self, suffix="db"): return os.path.join(self.db_folder, f"{self.db_name}.{suffix}") def delete(self, db_name=None, make_dir=True)-
Expand source code
def delete(self, db_name=None, make_dir=True): if db_name is None: shutil.rmtree(self.db_folder, ignore_errors=True) else: for file in self.db_folder: prefix = self.db_path(suffix=db_name) if file.startswith(prefix): os.remove(os.path.join(self.db_folder, file)) os.makedirs(self.db_folder, exist_ok=True) def map(self, *args, **kwargs) ‑> polars.dataframe.frame.DataFrame-
Expand source code
def map(self, *args, **kwargs) -> pl.DataFrame: kwargs.pop("map", None) return self.search(*args, map=True, **kwargs) def search(self, query_db, exhaustive=True, map=False, dataset_name=None, **kwargs) ‑> polars.dataframe.frame.DataFrame-
Expand source code
def search(self, query_db, exhaustive=True, map=False, dataset_name=None, **kwargs) -> pl.DataFrame: if map: cmd = ["map"] folder_name = "map" else: cmd = ["search"] folder_name = "search" if exhaustive: cmd.append("--exhaustive-search") cmd.extend(["--alignment-mode", "3"]) log(1, f"Searching({query_db}) in {self.db_path()} cmd={folder_name}") aligned_db = os.path.join(self.tmp_folder, folder_name, str(dataset_name)) shutil.rmtree(aligned_db, ignore_errors=True) os.makedirs(aligned_db, exist_ok=True) aligned_db = os.path.join(aligned_db, f"temp.{folder_name}") cmd.extend([query_db, self.db_path(), aligned_db, self.tmp_folder]) cmd.append("-a") try: self._cmd(*cmd, **kwargs) except: raise SearchError() columns = "query,target,evalue,raw,bits,fident,alnlen,pident,qcov,tcov,qlen,tlen,qstart,tstart,qaln,taln" tsv = self.write(query_db, self.db_path(), aligned_db, output_file=".".join(query_db.split(".")[:-1]) + f".{folder_name}.tab", mode="alis", format_mode=4, format_output=columns) df = pl.read_csv(tsv, separator="\t", schema_overrides={"query":pl.String, "target":pl.String}) log(df) return df def write(self, query, target=None, result=None, output_file=None, mode='tab', **kwargs)-
Expand source code
def write(self, query, target=None, result=None, output_file=None, mode="tab", **kwargs): if mode.lower() == "tsv": cmd = ["createtsv"] extension = "tsv" elif mode.lower() == "fasta": cmd = ["result2flat"] extension = "fasta" elif mode.lower() == "tab" or mode.lower() == "alis": cmd = ["convertalis"] extension = "tab" else: raise NotImplementedError cmd.append(query) if output_file is None: if not output_file.endswith(f".{extension}"): output_file += f".{extension}" output_file = ".".join(query.split(".")[:-1]) if target is not None: cmd.append(target) if result is not None: cmd.append(result) cmd.append(output_file) try: self._cmd(cmd, **kwargs) except: raise TsvError() return output_file
class MSA (fasta_path, name=None, **kwargs)-
Expand source code
class MSA(object): def __init__(self, fasta_path, name=None, **kwargs): self.fasta_path = fasta_path self.fasta = FASTA(fasta_path) if name is None: name = os.path.basename(fasta_path).replace(".fasta", "") self.name = name log(1, f"Initialising {self.__class__.__name__}...") log(2, "Fasta path:", self.fasta_path) def __repr__(self): return f"<bi.{self.__class__.__name__}:{self.name} ({len(self)} sequences)>" def __len__(self): return len(self.fasta.get_names())Subclasses