From c254b4756cf83632af01e6c13665749236bc77f4 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sun, 12 May 2024 22:23:40 -0700 Subject: [PATCH 01/31] feat(pretty print): adding some experimental code for pretty printing hgvs variants. --- src/hgvs/easy.py | 2 + src/hgvs/parser.py | 2 +- src/hgvs/pretty/datacompiler.py | 326 +++++++++++++++++ src/hgvs/pretty/models.py | 89 +++++ src/hgvs/pretty_print.py | 629 ++++++++++++++++++++++++++++++++ src/hgvs/shell.py | 1 + tests/test_pretty_print.py | 534 +++++++++++++++++++++++++++ 7 files changed, 1582 insertions(+), 1 deletion(-) create mode 100644 src/hgvs/pretty/datacompiler.py create mode 100644 src/hgvs/pretty/models.py create mode 100644 src/hgvs/pretty_print.py create mode 100644 tests/test_pretty_print.py diff --git a/src/hgvs/easy.py b/src/hgvs/easy.py index 228dea06..f9bb8e15 100644 --- a/src/hgvs/easy.py +++ b/src/hgvs/easy.py @@ -37,6 +37,7 @@ from hgvs.dataproviders.uta import connect from hgvs.normalizer import Normalizer from hgvs.parser import Parser +from hgvs.pretty_print import PrettyPrint from hgvs.validator import Validator from hgvs.variantmapper import VariantMapper @@ -66,6 +67,7 @@ t_to_g = projector.t_to_g t_to_p = projector.t_to_p get_relevant_transcripts = am38.relevant_transcripts +pretty = PrettyPrint(hdp, useColor=True, showLegend=True) # # Copyright 2018 HGVS Contributors (https://github.com/biocommons/hgvs) diff --git a/src/hgvs/parser.py b/src/hgvs/parser.py index b60a1a09..1d584c45 100644 --- a/src/hgvs/parser.py +++ b/src/hgvs/parser.py @@ -99,7 +99,7 @@ def __init__(self, grammar_fn=None, expose_all_rules=False): self._logger = logging.getLogger(__name__) self._expose_rule_functions(expose_all_rules) - def parse(self, v): + def parse(self, v) -> hgvs.sequencevariant.SequenceVariant: """parse HGVS variant `v`, returning a SequenceVariant :param str v: an HGVS-formatted variant as a string diff --git a/src/hgvs/pretty/datacompiler.py b/src/hgvs/pretty/datacompiler.py new file mode 100644 index 00000000..3dc5ba07 --- /dev/null +++ b/src/hgvs/pretty/datacompiler.py @@ -0,0 +1,326 @@ +from typing import Tuple + +from bioutils.normalize import normalize +from bioutils.sequences import aa1_to_aa3_lut + +import hgvs +import hgvs.utils.altseq_to_hgvsp as altseq_to_hgvsp +import hgvs.utils.altseqbuilder as altseqbuilder +from hgvs.exceptions import HGVSInvalidIntervalError +from hgvs.pretty.models import ( + PositionDetail, + PrettyConfig, + ProteinData, + VariantCoords, + VariantData, +) +from hgvs.sequencevariant import SequenceVariant +from hgvs.utils.reftranscriptdata import RefTranscriptData + + +class DataCompiler: + + def __init__(self, config: PrettyConfig): + self.config = config + + def _get_shuffled_variant(self, var_g: SequenceVariant, direction: int) -> VariantCoords: + + # get shuffled representation: + if direction == 5: + shuffle_direction = "LEFTSHUFFLE" + elif direction == 3: + shuffle_direction = "RIGHTSHUFFLE" + else: + shuffle_direction = "EXPAND" + + chrom_seq = self.config.hdp.get_seq(var_g.ac) + + start, end, ref, alt = self.get_position_and_state(var_g) + + if var_g.posedit.edit.type == "identity": + return VariantCoords(start, end, ref, alt) + + shuffled_interval, shuffled_alleles = normalize( + chrom_seq, interval=(start, end), alleles=(None, alt), mode=shuffle_direction + ) + + return VariantCoords( + shuffled_interval[0], shuffled_interval[1], shuffled_alleles[0], shuffled_alleles[1] + ) + + def get_position_and_state( + self, sv: hgvs.sequencevariant.SequenceVariant + ) -> Tuple[int, int, str, str]: + """ + Get the details of a sequence variant. + + Args: + sv (hgvs.sequencevariant.SequenceVariant): The sequence variant object. + + Returns: + tuple: A tuple containing the start position, end position, and state of the variant. + + Raises: + ValueError: If the HGVS variant type is unsupported. + """ + + if sv.posedit.edit.type == "ins": + start = sv.posedit.pos.start.base + end = sv.posedit.pos.start.base + ref = sv.posedit.edit.ref + alt = sv.posedit.edit.alt + + elif sv.posedit.edit.type in ("sub", "del", "delins", "identity"): + start = sv.posedit.pos.start.base - 1 + end = sv.posedit.pos.end.base + if sv.posedit.edit.type == "identity": + alt = self.config.hdp.get_seq(sv.ac, start, end) + ref = alt + else: + alt = sv.posedit.edit.alt or "" + ref = sv.posedit.edit.ref + + elif sv.posedit.edit.type == "dup": + start = sv.posedit.pos.start.base - 1 + end = sv.posedit.pos.end.base + ref = self.config.hdp.get_seq(sv.ac, start, end) + alt = ref + ref + + else: + raise ValueError(f"HGVS variant type {sv.posedit.edit.type} is unsupported") + + return start, end, ref, alt + + def _get_prot_alt( + self, tx_ac: str, strand: int, reference_data, ref_base, c_interval + ) -> ProteinData: + edit = hgvs.edit.NARefAlt(ref=ref_base, alt=ref_base) + var_c = hgvs.sequencevariant.SequenceVariant( + ac=tx_ac, type="c", posedit=hgvs.posedit.PosEdit(c_interval, edit) + ) + builder = altseqbuilder.AltSeqBuilder(var_c, reference_data) + all_alt_data = builder.build_altseq() + + alt_data = all_alt_data[0] + + if not alt_data.variant_start_aa: + return None + + aa_index = alt_data.variant_start_aa - 1 + aa = reference_data.aa_sequence[aa_index] + + protaltseqbuilder = altseq_to_hgvsp.AltSeqToHgvsp(reference_data, alt_data) + var_p = protaltseqbuilder.build_hgvsp() + + is_init_met = False + if protaltseqbuilder._is_init_met: + is_init_met = True + + # convert to three letter code + tlc = aa1_to_aa3_lut[aa] + is_stop_codon = False + if tlc == "Ter": + is_stop_codon = True + + c_pos = int(c_interval.start.base) - 1 + c3 = (c_pos) % 3 + aa_char = tlc[c3] + if strand < 0: + aa_char = tlc[2 - c3] + + return ProteinData(c_pos, aa, tlc, aa_char, var_p, is_init_met, is_stop_codon) + + def data( + self, + var_g: SequenceVariant, + var_c: SequenceVariant = None, + display_start: int = None, + display_end: int = None, + ) -> VariantData: + """Takes a sequence variant and provides all the data we need for pretty printing.""" + + start, end, ref, alt = self.get_position_and_state(var_g) + + ls = self._get_shuffled_variant(var_g, 5) + rs = self._get_shuffled_variant(var_g, 3) + fs = self._get_shuffled_variant(var_g, 0) + + if ls.start < start: + start = ls.start + if rs.end > end: + end = rs.end + + if not display_start or display_start > start: + seq_start = start - self.config.padding_left + else: + seq_start = display_start + + if not display_end or display_end < end: + seq_end = end + self.config.padding_right + else: + seq_end = display_end + + chrom_seq = self.config.hdp.get_seq(var_g.ac) + disp_seq = chrom_seq[seq_start:seq_end] + + tx_ac = var_c.ac + tx_seq = self.config.hdp.get_seq(tx_ac) + #print(tx_seq) + + if self.config.default_assembly == "GRCh37": + am = self.config.am37 + else: + am = self.config.am38 + + mapper = am._fetch_AlignmentMapper(tx_ac=tx_ac, alt_ac=var_g.ac, alt_aln_method="splign") + + # we don't know the protein ac, get it looked up: + pro_ac = None + reference_data = RefTranscriptData(self.config.hdp, tx_ac, pro_ac) + + position_details = [] + prev_mapped_pos = None + + for chromosome_pos in range(seq_start + 1, seq_end + 1): + + pdata = PositionDetail(chromosome_pos=chromosome_pos) + position_details.append(pdata) + + pdata.ref = chrom_seq[chromosome_pos - 1] + + try: + gr = chromosome_pos - mapper.gc_offset - 1 + pdata.alignment_pos = gr + mapped_pos, mapped_pos_offset, cig = mapper.cigarmapper.map_ref_to_tgt( + gr, '"start"' + ) + except HGVSInvalidIntervalError: + continue + + pdata.mapped_pos = mapped_pos + pdata.mapped_pos_offset = mapped_pos_offset + pdata.cigar_ref = cig + + if prev_mapped_pos: + + while mapped_pos - prev_mapped_pos > 1: + + prev_mapped_pos = prev_mapped_pos + 1 + + pdata.mapped_pos = prev_mapped_pos + + # a region in ref that has been deleted. Fill in gaps. + self._backfill_gap_in_ref(tx_ac, tx_seq, mapper, reference_data, pdata, cig, prev_c_pos +1, prev_n_pos+1) + + print(f"pdata at end of backfill: {pdata}") + + #prev_mapped_pos += 1 + pdata = PositionDetail(chromosome_pos=chromosome_pos) + position_details.append(pdata) + + pdata.alignment_pos = gr + pdata.mapped_pos = prev_mapped_pos + pdata.mapped_pos_offset = mapped_pos_offset + pdata.cigar_ref = cig + pdata.ref = chrom_seq[chromosome_pos] + + if mapper.strand > 0: + prev_c_pos += 1 + prev_n_pos += 1 + else: + prev_c_pos -= 1 + prev_n_pos -= 1 + + + + prev_mapped_pos = mapped_pos + + g_interval = hgvs.location.Interval( + start=hgvs.location.SimplePosition(chromosome_pos), end=hgvs.location.SimplePosition(chromosome_pos) + ) + + try: + n_interval = mapper.g_to_n(g_interval) + c_interval = mapper.n_to_c(n_interval) + except hgvs.exceptions.HGVSInvalidIntervalError: + # we are beyond the transcript space, can't set any of the other values. + continue + + pdata.n_interval = n_interval + pdata.c_interval = c_interval + + n_pos = int(n_interval.start.base) - 1 + c_pos = int(c_interval.start.base) - 1 + prev_n_pos = n_pos + prev_c_pos = c_pos + + self._populate_with_n_c( + var_c.ac, tx_seq, mapper, reference_data, pdata, cig, n_interval, c_interval + ) + + for p in position_details: + print(f"{p}\t{p.protein_data}\t") + + vd = VariantData( + seq_start, seq_end, ls, rs, fs, disp_seq, tx_seq, mapper, var_g, var_c, position_details + ) + + return vd + + def _backfill_gap_in_ref(self, tx_ac, tx_seq, mapper, reference_data, pdata, cig, prev_c_pos, prev_n_pos ): + + pdata.chromosome_pos = None + pdata.ref = None + + if mapper.strand > 0: + pdata.c_pos = prev_c_pos + 1 + pdata.n_pos = prev_n_pos + 1 + else: + pdata.c_pos = prev_c_pos - 1 + pdata.n_pos = prev_n_pos - 1 + pdata.c_offset = 0 + pdata.cigar_ref = "D" + pdata.tx = tx_seq[pdata.n_pos] + + n_interval = hgvs.location.Interval( + start=hgvs.location.BaseOffsetPosition(base=pdata.n_pos, offset=0), + end=hgvs.location.BaseOffsetPosition(base=pdata.n_pos, offset=0), + ) + c_interval = mapper.n_to_c(n_interval, strict_bounds=False) + pdata.c_interval = c_interval + + self._populate_with_n_c( + tx_ac, tx_seq, mapper, reference_data, pdata, cig, n_interval, c_interval + ) + + + + def _populate_with_n_c( + self, tx_ac, tx_seq, mapper, reference_data, pdata, cig, n_interval, c_interval + ): + n_pos = int(n_interval.start.base) - 1 + c_pos = int(c_interval.start.base) - 1 + + # print(f"{n_interval} {c_pos} {c_interval.start.datum}") + + pdata.n_pos = n_pos + pdata.c_pos = c_pos + pdata.c_offset = c_interval.start.offset + pdata.tx = tx_seq[pdata.n_pos] + + coding = True + if cig == "N" or pdata.c_offset != 0: + coding = False + + if cig == "I": + coding = False + + ref_base = pdata.ref + if coding: + if not ref_base or pdata.tx: + ref_base = pdata.tx + + prot_data = self._get_prot_alt( + tx_ac, mapper.strand, reference_data, ref_base, c_interval + ) + pdata.protein_data = prot_data diff --git a/src/hgvs/pretty/models.py b/src/hgvs/pretty/models.py new file mode 100644 index 00000000..e1717a95 --- /dev/null +++ b/src/hgvs/pretty/models.py @@ -0,0 +1,89 @@ +from dataclasses import dataclass + +import hgvs +from hgvs.alignmentmapper import AlignmentMapper +from hgvs.assemblymapper import AssemblyMapper +from hgvs.location import Interval +from hgvs.sequencevariant import SequenceVariant + + +@dataclass(eq=True, repr=True, frozen=True, order=True) +class VariantCoords: + """Representation of a variant in one of the shuffled representation""" + + start: int + end: int + ref: str + alt: str + + +@dataclass(eq=True, frozen=True, order=True) +class ProteinData: + c_pos: int = None + aa: str = None # one letter AA code + tlc: str = None # three letter AA code + aa_char: str = None # the character from the tlc that maps to the c_pos + var_p: SequenceVariant = None + is_init_met: bool = False + is_stop_codon: bool = False + + def __repr__(self) -> str: + return f"{self.c_pos}\t{self.tlc}\t{self.aa_char}\t{self.c_pos %3}" + + +@dataclass(eq=True, frozen=False, order=True) +class PositionDetail: + + chromosome_pos: int = None + alignment_pos: int = None + mapped_pos: int = None + mapped_pos_offset: int = None + cigar_ref: str = None + n_pos: int = None + c_pos: int = None + c_offset: int = None + ref: str = None + tx: str = None + n_interval: Interval = None + c_interval: Interval = None + protein_data: ProteinData = None + + def __repr__(self) -> str: + return ( + f"{self.mapped_pos}\t{self.c_pos}\t{self.c_offset}\t{self.c_interval}\t" + + f"{self.cigar_ref}\t" + + f"{self.chromosome_pos}\t{self.ref}\t{self.alignment_pos}\t{self.mapped_pos_offset}\t" + + f"\t{self.n_pos}\t{self.tx}" + ) + + +@dataclass(eq=True, repr=True, frozen=True, order=True) +class VariantData: + """A data container for knowledge we have about a variant.""" + + display_start: int + display_end: int + left_shuffled: VariantCoords + right_shuffled: VariantCoords + fully_justified: VariantCoords + display_seq: str + tx_seq: str + alignmentmapper: AlignmentMapper + var_g: SequenceVariant + var_c: SequenceVariant = None + position_details: list[PositionDetail] = None + + +@dataclass +class PrettyConfig: + """acontainer for various configurations.""" + + hdp: hgvs.dataproviders.interface.Interface + am37: AssemblyMapper + am38: AssemblyMapper + padding_left: int + padding_right: int + default_assembly: str = "GRCh37" + useColor: bool = False + showLegend: bool = True + infer_hgvs_c: bool = True diff --git a/src/hgvs/pretty_print.py b/src/hgvs/pretty_print.py new file mode 100644 index 00000000..8ebfb50e --- /dev/null +++ b/src/hgvs/pretty_print.py @@ -0,0 +1,629 @@ +import math +from typing import Tuple + +import hgvs +from hgvs.assemblymapper import AssemblyMapper +from hgvs.enums import Datum +from hgvs.pretty.datacompiler import DataCompiler +from hgvs.pretty.models import PrettyConfig, VariantCoords, VariantData +from hgvs.sequencevariant import SequenceVariant +from hgvs.utils.reftranscriptdata import RefTranscriptData + +TGREEN = "\033[32m" # Green Text +TGREENBG = "\033[30;42m" +TRED = "\033[31m" # Red Text +TREDBG = "\033[30;41m" +TBLUE = "\033[34m" # Blue Text +TBLUEBG = "\033[30;44m" +TPURPLE = "\033[35m" # Purple Text +TPURPLEBG = "\033[30;45m" +TYELLOW = "\033[33m" # Yellow Text +TYELLOWBG = "\033[30;43m" + +ENDC = "\033[m" # reset to the defaults + + +class PrettyPrint: + """A class that provides a pretty display of the genomic context of a variant.""" + + def __init__( + self, + hdp: hgvs.dataproviders.interface.Interface, + default_assembly: str = "GRCh37", + padding_left: int = 20, + padding_right: int = 20, + useColor=False, + showLegend=True, + infer_hgvs_c=True, + ): + """ + :param hdp: HGVS Data Provider Interface-compliant instance + (see :class:`hgvs.dataproviders.interface.Interface`) + + :param padding: spacing left and right of the variant for display purposes. + """ + am37: AssemblyMapper = AssemblyMapper(hdp, assembly_name="GRCh37") + am38: AssemblyMapper = AssemblyMapper(hdp, assembly_name="GRCh38") + + self.config = PrettyConfig( + hdp, + am37, + am38, + padding_left, + padding_right, + default_assembly, + useColor, + showLegend, + infer_hgvs_c, + ) + + # def _display_position_info(self, data:VariantData)->str: + + # seq_start = data.display_start + # seq_end = data.display_end + + # line_start = f"{seq_start + 1:,} " + # line_end = f"{seq_end:,}" + # ls = len(line_start) + # le = len(line_end) + + # total_chars = seq_end - seq_start + 1 + + # gap = total_chars - ls - le -1 + + # line = line_start + # p = 0 + # while p < gap: + # line += ' ' + # p+=1 + + # line += line_end + # return line + + def _display_position_info(self, data: VariantData) -> str: + + count = -1 + var_seq = "" + for pdata in data.position_details: + count += 1 + g = pdata.chromosome_pos + + total_chars = len(var_seq) + + if total_chars > count: + continue + + if not g: + var_seq += " " + continue + + if g % 10 == 0: + var_seq += f"{g:,} " + + else: + var_seq += " " + + return var_seq.rstrip() + + def _display_ruler(self, data: VariantData) -> str: + """Draw a position indicator that diplays | every 10 bases and a . every 5 + + seq_start/end in interbase + """ + + ruler = "" + + for pd in data.position_details: + p = pd.chromosome_pos + if not p: + ruler += "_" + continue + + if p % 10 == 0: + ruler += "|" + elif p % 5 == 0: + ruler += "." + else: + ruler += " " + return ruler + + def _display_seq(self, data: VariantData) -> str: + """colors the ref sequences with adenine (A, green), thymine (T, red), cytosine (C, yellow), and guanine (G, blue)""" + + var_seq = "" + for p in data.position_details: + c = p.ref + + if not c: + var_seq += "." + continue + + if self.config.useColor: + if c == "A": + var_seq += TGREEN + elif c == "T": + var_seq += TRED + elif c == "C": + var_seq += TYELLOW + elif c == "G": + var_seq += TBLUE + + var_seq += c + + if self.config.useColor: + var_seq += ENDC + + return var_seq + + def _display_shuffled_range( + self, var_g: SequenceVariant, data: VariantData, vc: VariantCoords + ) -> str: + + seq_start = data.display_start + seq_end = data.display_end + + split_char = "|" + + start = vc.start + end = vc.end + + # map interbase coordinates to 1-based display coords: + start = start + 1 + + if var_g.posedit.edit.type == "sub": + end = start + if len(vc.alt) == 1: + split_char = vc.alt + + l = end - start + 1 + if var_g.posedit.edit.type == "ins" and l == 0: + start = start - 1 + end = end + 1 + split_char = "^" + + if var_g.posedit.edit.type == "del": + split_char = "" + if self.config.useColor: + split_char = TRED + split_char += "x" + if self.config.useColor: + split_char += ENDC + + elif var_g.posedit.edit.type == "identity": + split_char = "=" + # print(l,start, end , vc) + + if start < seq_start: + # raise ValueError(f"Can't create shuffled representation, since start {start} < seq_start {seq_start} ") + return "" + if end > seq_end: + return "" + # raise ValueError(f"Can't create shuffled representation, since end {end} > seq_end {seq_end} ") + + var_str = "" + in_range = False + for pdata in data.position_details: + p = pdata.chromosome_pos + + if not p: + if in_range: + var_str += "-" + else: + var_str += " " + continue + + if p == start: + var_str += split_char + in_range = True + elif p == end: + var_str += split_char + in_range = False + elif p > end and in_range: + in_range = False + var_str += " " + elif in_range: + var_str += "-" + else: + var_str += " " + + return var_str + + def _infer_hgvs_c(self, var_g: SequenceVariant) -> SequenceVariant: + if self.config.default_assembly == "GRCh37": + am = self.config.am37 + else: + am = self.config.am38 + + transcripts = am.relevant_transcripts(var_g) + if transcripts: + tx_ac = transcripts[0] + else: + return None + + var_c = am.g_to_c(var_g, tx_ac) + return var_c + + def _display_tx_ref_disagree_str(self, data: VariantData) -> str: + """show differences between tx and ref genome, if there are any""" + + if not data.var_c: + return "" + + var_str = "" + + counter = -1 + for p in range(data.display_start + 1, data.display_end + 1): + counter += 1 + pdata = data.position_details[counter] + c_offset = pdata.c_offset + if not pdata.mapped_pos: + var_str += " " + continue + + cig = pdata.cigar_ref + if cig == "=": + var_str += " " + elif cig == "N" or c_offset != 0: + var_str += " " + continue + else: + # an alignment issue, show cigar string + if self.config.useColor: + var_str += TRED + cig + ENDC + else: + var_str += cig + + if var_str.isspace(): + return "" + + if self.config.showLegend: + return f"tx ref dif: " + var_str + + return var_str + + def _display_transcript_str(self, data: VariantData) -> str: + """If transcript info is available show the details of the tx for the region.""" + if not data.var_c: + return "" + + mapper = data.alignmentmapper + + orientation = "->" + if mapper.strand < 0: + orientation = "<-" + + var_str = "" + if self.config.showLegend: + var_str = f"tx seq {orientation} : " + + first_c = None + last_c = None + + tx_seq = data.tx_seq + + coding = False + c_pos = None + + counter = -1 + for pdata in data.position_details: + p = pdata.chromosome_pos + counter += 1 + + if not pdata.mapped_pos: + var_str += " " + continue + + n_pos = pdata.n_pos + c_pos = pdata.c_pos + cig = pdata.cigar_ref + c_offset = pdata.c_offset + + if cig == "N" or c_offset != 0: + coding = False + var_str += " " + continue + + # if we get here we are coding... + last_c = f"c.{c_pos}" + if not coding: + + if not first_c: + first_c = last_c + + coding = True + + if cig == "=": + c3 = (c_pos - 1) % 3 + bg_col = math.ceil(c_pos / 3) % 2 + # print(p, c_pos, c3, bg_col ) + if n_pos >= 0: + base = pdata.tx + if self.config.useColor and c_pos > 0: + if bg_col: + var_str += TPURPLE + base + ENDC + else: + var_str += TYELLOW + base + ENDC + else: + if c_pos < 0: + var_str += base.lower() + else: + var_str += base + continue + + elif cig == "X" or cig == "D": + # for mismatches and tx-insertions show sequence + var_str += pdata.tx + continue + + else: + var_str += "-" + + return var_str + + def _display_protein_str(self, data: VariantData) -> str: + if not data.var_c: + return "" + + mapper = data.alignmentmapper + + var_str = "" + for pdata in data.position_details: + + p = pdata.chromosome_pos + + if not pdata.mapped_pos: + var_str += " " + continue + + ref_base = pdata.ref + + c_interval = pdata.c_interval + if not c_interval: + var_str += " " + continue + + cig = pdata.cigar_ref + c_offset = pdata.c_offset + if cig == "N" or c_offset != 0: + var_str += " " + continue + + if cig == "I": + var_str += "-" + continue + + if not ref_base or pdata.tx: + ref_base = pdata.tx + + protein_data = pdata.protein_data + + if not protein_data: + var_str += " " + continue + + aa_char = protein_data.aa_char + + # color init met and stop codon if using color: + if protein_data.is_init_met: + if self.config.useColor: + aa_char = TGREEN + aa_char + ENDC + var_str += aa_char + continue + if protein_data.is_stop_codon: + if self.config.useColor: + aa_char = TRED + aa_char + ENDC + var_str += aa_char + continue + + if not protein_data.var_p.posedit: + var_str += " " + continue + + var_str += aa_char + continue + + if self.config.showLegend: + legend = "aa seq -> : " + if mapper.strand < 0: + legend = "aa seq <- : " + return legend + var_str + + return var_str + + def _display_c_pos(self, data: VariantData) -> str: + """show the position of the transcript seq""" + var_str = "" + + count = -1 + for pdata in data.position_details: + count += 1 + if not pdata.mapped_pos: + var_str += " " + continue + + c_pos = pdata.c_pos + + if c_pos is None: + var_str += " " + continue + + if len(var_str) > count: + continue + + if (c_pos + 1) % 10 == 0: + # if pdata.c_interval.start.datum == Datum.CDS_END: + # var_str += "*" + var_str += f"{pdata.c_interval} " + continue + + elif c_pos == 0: + var_str += f"{pdata.c_interval} " + continue + var_str += " " + + if self.config.showLegend: + return " : " + var_str.rstrip() + return var_str.rstrip() + + def _display_c_pos_ruler(self, data: VariantData) -> str: + """show the position of the transcript seq""" + + var_str = "" + + count = -1 + for pdata in data.position_details: + count += 1 + if not pdata.mapped_pos: + var_str += " " + continue + + c_pos = pdata.c_pos + + if c_pos is None: + var_str += " " + continue + + + if len(var_str) > count: + continue + + if (c_pos + 1) % 10 == 0: + var_str += "|" + continue + + elif (c_pos + 1) % 5 == 0: + var_str += "." + continue + elif c_pos == 0: + var_str += "|" + + var_str += " " + + if self.config.showLegend: + return "tx pos : " + var_str + return var_str + + def _map_to_chrom(self, sv: SequenceVariant) -> SequenceVariant: + """maps a variant to chromosomal coords, if needed.""" + if self.config.default_assembly == "GRCh37": + am = self.config.am37 + else: + am = self.config.am38 + + if sv.type == "c": + return am.c_to_g(sv) + elif sv.type == "n": + return am.n_to_g(sv) + elif sv.type == "t": + return am.t_to_g(sv) + + def _colorize_hgvs(self, hgvs_str: str) -> str: + if not self.config.useColor: + return hgvs_str + + spl = hgvs_str.split(":") + var_str = TPURPLE + spl[0] + ENDC + var_str += ":" + + sec = spl[1].split(".") + var_str += TYELLOW + sec[0] + ENDC + var_str += "." + var_str += sec[1] + + return var_str + + def display( + self, sv: SequenceVariant, display_start: int = None, display_end: int = None + ) -> str: + """Takes a variant and prints the genomic context around it.""" + + var_c = None + if sv.type == "g": + var_g = sv + elif sv.type == "c": + var_g = self._map_to_chrom(sv) + var_c = sv + elif sv.type in ["c", "n"]: + # map back to genome + var_g = self._map_to_chrom(sv) + + if not var_c: + if self.config.infer_hgvs_c: + var_c = self._infer_hgvs_c(var_g) + + dc = DataCompiler(config=self.config) + + data = dc.data(var_g, var_c, display_start, display_end) + + pos_info = self._display_position_info(data) + + ruler = self._display_ruler(data) + + seq = self._display_seq(data) + + left_shuffled_var = data.left_shuffled + right_shuffled_var = data.right_shuffled + fully_justified_var = data.fully_justified + + if self.config.showLegend: + head = "hgvs : " + posh = " : " + rule = "chrom pos : " + seqe = "seq -> : " + regi = "region : " + refa = "ref>alt : " + else: + head = posh = rule = seqe = regi = refa = "" + + if self.config.useColor: + var_g_print = self._colorize_hgvs(str(var_g)) + else: + var_g_print = str(var_g) + + var_str = head + var_g_print + "\n" + if data.var_c: + if self.config.useColor: + var_c_print = self._colorize_hgvs(str(var_c)) + else: + var_c_print = str(var_c) + var_str += head + var_c_print + "\n" + + var_str += posh + pos_info + "\n" + rule + ruler + "\n" + seqe + seq + "\n" + + tx_ref_disagree_str = self._display_tx_ref_disagree_str(data) + if tx_ref_disagree_str: + var_str += tx_ref_disagree_str + "\n" + + left_shuffled_str = self._display_shuffled_range(sv, data, left_shuffled_var) + right_shuffled_str = self._display_shuffled_range(sv, data, right_shuffled_var) + + if left_shuffled_str != right_shuffled_str: + + fully_justified_str = self._display_shuffled_range(sv, data, fully_justified_var) + var_str += regi + fully_justified_str + "\n" + + else: + var_str += regi + left_shuffled_str + "\n" + + protein_str = self._display_protein_str(data) + if protein_str: + var_str += protein_str + "\n" + + transcript_str = self._display_transcript_str(data) + if transcript_str: + var_str += transcript_str + "\n" + + c_pos_count_str = self._display_c_pos(data) + c_pos_str = self._display_c_pos_ruler(data) + if c_pos_str: + var_str += c_pos_str + "\n" + if c_pos_count_str: + var_str += c_pos_count_str + "\n" + + if left_shuffled_str != right_shuffled_str: + # TODO: detect repeats? + fully_justified_ref = fully_justified_var.ref + fully_justified_alt = fully_justified_var.alt + var_str += refa + f"{fully_justified_ref}>{fully_justified_alt}" + + return var_str diff --git a/src/hgvs/shell.py b/src/hgvs/shell.py index 234219f4..96b430a1 100755 --- a/src/hgvs/shell.py +++ b/src/hgvs/shell.py @@ -24,6 +24,7 @@ * am38, projector, hgvs_assembly_mapper_38 -- GRCh38 Assembly Mapper instances * hn, normalizer, hgvs_normalizer -- Normalizer instance * hv, validator, hgvs_validator) -- Validator instance +* pretty -- PrettyPrint instance The following functions are available: * parse, normalize, validate diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py new file mode 100644 index 00000000..1c1f7761 --- /dev/null +++ b/tests/test_pretty_print.py @@ -0,0 +1,534 @@ +# -*- coding: utf-8 -*- +import os +import unittest + +import pytest +from support import CACHE + +import hgvs +from hgvs.pretty_print import PrettyPrint + + +@pytest.mark.quick +@pytest.mark.models +class Test_SimplePosition(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.hp = hgvs.parser.Parser() + cls.hdp = hgvs.dataproviders.uta.connect( + mode=os.environ.get("HGVS_CACHE_MODE", "run"), cache=CACHE + ) + cls.pp = PrettyPrint( + cls.hdp, + ) + cls.pp.useColor = False + + cls.atta_expected_results = ( + " : 123,346,500 123,346,520 123,346,540\n" + + "chrom pos : | . | . | . | . | .\n" + + "seq -> : ATAAAGCTTTTCCAAATGTTATTAATTACTGGCATTGCTTTTTGCCAA\n" + + "region : |------| \n" + + "aa seq <- : TerAsnSerAlaAsnSerLysAlaLeu\n" + + "tx seq <- : TATTTCGAAAAGGTTTACAATAATTAATGACCGTAACGAAAAACGGTT\n" + + "tx pos : | . | . | | . | . | \n" + + " : *20 *10 *1 2880 2870 2860\n" + + "ref>alt : ATTAATTA>ATTAATTAATTA\n" + ) + + def test_var_c1_forward(self): + """test c1 on -> strand""" + + hgvs_c = "NM_198689.2:c.1=" + var_c = self.hp.parse(hgvs_c) + result = self.pp.display(var_c) + print(result) + result = result.split("\n") + expected_str = ( + "hgvs : NC_000021.8:g.46020522=\n" + + "hgvs : NM_198689.2:c.1=\n" + + " : 46,020,510 46,020,530\n" + + "chrom pos : . | . | . | . | \n" + + "seq -> : CCTCCAGTTCAATCCCCAGCATGGCCGCGTCCACTATGTCT\n" + + "tx ref dif: X \n" + + "region : = \n" + + "aa seq -> : MetAlaAlaSerThrMetSer\n" + + "tx seq -> : cctccagttcaatccccagcATGGCTGCGTCCACTATGTCT\n" + + "tx pos : | . | . | . | . | \n" + + " : -20 -10 1 10 20\n" + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_var_c1_reverse(self): + """test c1 on <- strand""" + + hgvs_c = "NM_001177507.2:c.1=" + var_c = self.hp.parse(hgvs_c) + result = self.pp.display(var_c) + print(result) + result = result.split("\n") + expected_str = ( + "hgvs : NC_000007.13:g.36763753=\n" + + "hgvs : NM_001177507.2:c.1=\n" + + " : 36,763,740 36,763,760\n" + + "chrom pos : . | . | . | . | \n" + + "seq -> : GATTTTCCAGGGGGACTGCATCTCCGAGCTATGCACCCCAA\n" + + "region : = \n" + + "aa seq <- : IleLysTrpProSerGlnMet \n" + + "tx seq <- : CTAAAAGGTCCCCCTGACGTAgaggctcgatacgtggggtt\n" + + "tx pos : | . | . | . | . |\n" + + " : 20 10 1 -10 -20\n" + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_var_g_substitution(self): + hgvs_g = "NC_000007.13:g.36561662C>T" + var_g = self.hp.parse(hgvs_g) + + result = self.pp.display(var_g) + print(result) + result = result.split("\n") + expected_str = ( + "hgvs : NC_000007.13:g.36561662C>T\n" + + "hgvs : NM_001177507.2:c.1486G>A\n" + + " : 36,561,650 36,561,670\n" + + "chrom pos : . | . | . | . | \n" + + "seq -> : TACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" + + "region : T \n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + + "tx pos : | . | . | . | \n" + + " : 1500 1490 1480 1470\n" + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_var_g_ins(self): + """[ATTA]x2 -> x3""" + hgvs_g = "NC_000005.10:g.123346517_123346518insATTA" + var_g = self.hp.parse(hgvs_g) + + result = self.pp.display(var_g) + print(result) + result = result.split("\n") + expected_str = ( + "hgvs : NC_000005.10:g.123346517_123346518insATTA\n" + + "hgvs : NM_001166226.1:c.*1_*2insTAAT\n" + + self.atta_expected_results + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_var_g_dup(self): + hgvs_g = "NC_000005.10:g.123346522_123346525dup" + var_g = self.hp.parse(hgvs_g) + + result = self.pp.display(var_g) + print(result) + result = result.split("\n") + expected_str = ( + "hgvs : NC_000005.10:g.123346522_123346525dup\n" + + "hgvs : NM_001166226.1:c.2880_2883dup\n" + + self.atta_expected_results + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_insertion(self): + "A shuffleable insertion, shuffleable unit: TCGTCATC additional residues: G" + hgvs_g = "NC_000004.11:g.1643284_1643285insTCGTCATCG" + var_g = self.hp.parse(hgvs_g) + + result = self.pp.display(var_g) + print(result) + result = result.split("\n") + expected_str = ( + "hgvs : NC_000004.11:g.1643284_1643285insTCGTCATCG\n" + + "hgvs : NM_001174070.2:c.932_933insCGATGACGA\n" + + " : 1,643,270 1,643,280 1,643,290 1,643,300 1,643,310\n" + + "chrom pos : . | . | . | . | . | \n" + + "seq -> : TCACTGGGGTGTCATCCTCATCGTCATCTTCGTAATTGAGGGAGCAAA\n" + + "region : |------| \n" + + "aa seq <- : sValProThrAspAspGluAspAspAspGluTyrAsnLeuSerCysLe\n" + + "tx seq <- : AGTGACCCCACAGTAGGAGTAGCAGTAGAAGCATTAACTCCCTCGTTT\n" + + "tx pos : | . | . | . | . | .\n" + + " : 950 940 930 920 910\n" + + "ref>alt : TCGTCATC>TCGTCATCGTCGTCATC\n" + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_insertion_size_1(self): + hgvs_g = "NC_000007.13:g.36561662_36561663insT" + var_g = self.hp.parse(hgvs_g) + + result = self.pp.display(var_g) + print(result) + + result = result.split("\n") + expected_str = ( + "hgvs : NC_000007.13:g.36561662_36561663insT\n" + + "hgvs : NM_001177507.2:c.1485_1486insA\n" + + " : 36,561,650 36,561,670\n" + + "chrom pos : . | . | . | . | \n" + + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" + + "region : ^^ \n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + + "tx pos : | . | . | . | \n" + + " : 1500 1490 1480 1470\n" + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_del_2bp(self): + hgvs_g = "NC_000007.13:g.36561662_36561663del" + var_g = self.hp.parse(hgvs_g) + + result = self.pp.display(var_g) + print(result) + + result = result.split("\n") + expected_str = ( + "hgvs : NC_000007.13:g.36561662_36561663del\n" + + "hgvs : NM_001177507.2:c.1485_1486del\n" + + " : 36,561,650 36,561,670\n" + + "chrom pos : . | . | . | . | \n" + + "seq -> : TACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCTG\n" + + "region : xx \n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGln\n" + + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGAC\n" + + "tx pos : | . | . | . | .\n" + + " : 1500 1490 1480 1470\n" + + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_del_1bp_shuffleable(self): + hgvs_g = "NC_000007.13:g.36561662del" + var_g = self.hp.parse(hgvs_g) + + result = self.pp.display(var_g) + print(result) + + result = result.split("\n") + expected_str = ( + "hgvs : NC_000007.13:g.36561662del\n" + + "hgvs : NM_001177507.2:c.1487del\n" + + " : 36,561,650 36,561,670\n" + + "chrom pos : . | . | . | . | \n" + + "seq -> : TTACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" + + "region : xx \n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + + "tx pos : | . | . | . | \n" + + " : 1500 1490 1480 1470\n" + + + "ref>alt : CC>C\n" + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_del_1bp(self): + hgvs_g = "NC_000007.13:g.36561663del" + var_g = self.hp.parse(hgvs_g) + + result = self.pp.display(var_g) + print(result) + + result = result.split("\n") + expected_str = ( + "hgvs : NC_000007.13:g.36561663del\n" + + "hgvs : NM_001177507.2:c.1485del\n" + + " : 36,561,650 36,561,670\n" + + "chrom pos : . | . | . | . | \n" + + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCTG\n" + + "region : x \n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGln\n" + + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGAC\n" + + "tx pos : | . | . | . | .\n" + + " : 1500 1490 1480 1470\n" + + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_dup_1bp_shuffleable(self): + hgvs_g = "NC_000007.13:g.36561662dup" + var_g = self.hp.parse(hgvs_g) + + result = self.pp.display(var_g) + print(result) + + result = result.split("\n") + expected_str = ( + "hgvs : NC_000007.13:g.36561662dup\n" + + "hgvs : NM_001177507.2:c.1487dup\n" + + " : 36,561,650 36,561,670\n" + + "chrom pos : . | . | . | . | \n" + + "seq -> : TTACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" + + "region : || \n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + + "tx pos : | . | . | . | \n" + + " : 1500 1490 1480 1470\n" + + "ref>alt : CC>CCC\n" + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_dup_1bp(self): + hgvs_g = "NC_000007.13:g.36561663dup" + var_g = self.hp.parse(hgvs_g) + + result = self.pp.display(var_g) + print(result) + + result = result.split("\n") + expected_str = ( + "hgvs : NC_000007.13:g.36561663dup\n" + + "hgvs : NM_001177507.2:c.1485dup\n" + + " : 36,561,650 36,561,670\n" + + "chrom pos : . | . | . | . | \n" + + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCTG\n" + + "region : | \n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGln\n" + + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGAC\n" + + "tx pos : | . | . | . | .\n" + + " : 1500 1490 1480 1470\n" + + "ref>alt : A>AA\n" + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_identity(self): + + hgvs_g = "NC_000007.13:g.36561663=" + var_g = self.hp.parse(hgvs_g) + + result = self.pp.display(var_g) + print(result) + + result = result.split("\n") + expected_str = ( + "hgvs : NC_000007.13:g.36561663=\n" + + "hgvs : NM_001177507.2:c.1485=\n" + + " : 36,561,650 36,561,670\n" + + "chrom pos : . | . | . | . | \n" + + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCTG\n" + + "region : = \n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGln\n" + + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGAC\n" + + "tx pos : | . | . | . | .\n" + + " : 1500 1490 1480 1470\n" + + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_tiny(self): + """Test a variant with bad input.""" + hgvs_g = "NC_000005.10:g.123346517_123346518insATTA" + var_g = self.hp.parse(hgvs_g) + + tiny_pp = PrettyPrint(self.hdp, padding_left=0, padding_right=0) + + result = tiny_pp.display(var_g) + print(result) + + result = result.split("\n") + + expected_str = ( + "hgvs : NC_000005.10:g.123346517_123346518insATTA\n" + + "hgvs : NM_001166226.1:c.*1_*2insTAAT\n" + + " : 123,346,520\n" + + "chrom pos : | .\n" + + "seq -> : ATTAATTA\n" + + "region : |------|\n" + + "aa seq <- : TerAsnS\n" + + "tx seq <- : TAATTAAT\n" + + "tx pos : | | \n" + + " : *1 2880\n" + + "ref>alt : ATTAATTA>ATTAATTAATTA" + ).split("\n") + + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + @pytest.mark.skip(reason="CNVs not implemented yet") + def test_cnv(self): + """Test a CNV variant. TODO: make display compact""" + hgvs_g = "NC_000005.10:g.123345517_123346518del" + var_g = self.hp.parse(hgvs_g) + + result = self.pp.display(var_g) + + print(result) + + def test_hgvs_c(self): + """Test a hgvs_c variant overlapping start codon on reverse strand.""" + hgvs_c = "NM_004572.3:c.-9_12dup" + var_c = self.hp.parse(hgvs_c) + pp = PrettyPrint( + self.hdp, padding_left=10, padding_right=110, useColor=False, showLegend=False + ) + result = pp.display(var_c) + + print(result) + + result = result.split("\n") + expected_str = ( + "NC_000012.11:g.33049660_33049680dup\n" + + "NM_004572.3:c.-9_12dup\n" + + " 33,049,650 33,049,670 33,049,690 33,049,710 33,049,730 33,049,750 33,049,770 33,049,790\n" + + " . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |\n" + + "CTGGGGCGCCGGGGGCTGCCATGGGGCCGGTGGGGGCGACCGAGCTGCTCGCCTGCCTCTGGACTCGCGGGCGAAGCCGCCACGGAGCTGGGGGCGCTGGCGCGAGCCCCGCCCCGCTCGAGTCCGGCCCCGCCCCTGGCCCGCCCC\n" + + " |-------------------------| \n" + + "aProAlaGlyProAlaAlaMet \n" + + "GACCCCGCGGCCCCCGACGGTAccccggccacccccgctggctcgacgagcggacggagacctgagcgcccgcttcggcggtgcctcgacccccgcgaccgcgctcggggcggggcgagctcaggccggggcgggga \n" + + " | . | . | . | . | . | . | . | . | . | . | . | . | . | . \n" + + " 20 10 1 -10 -20 -30 -40 -50 -60 -70 -80 -90 -100 -110\n" + + "GGGGGCTGCCATGGGGCCGGTGGGGGC>GGGGGCTGCCATGGGGCCGGTGGGGGCTGCCATGGGGCCGGTGGGGGC" + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_ref_disagree(self): + """Test a tx ref disagree variant.""" + hgvs_g = "NM_001111.4:c.298G>A" + var_g = self.hp.parse(hgvs_g) + result = self.pp.display(var_g) + + print(result) + + result = result.split("\n") + + # note the X in the transscript sequence + expected_str = ( + "hgvs : NC_000001.10:g.154574820=\n" + + "hgvs : NM_001111.4:c.298G>A\n" + + " : 154,574,800 154,574,820 154,574,840\n" + + "chrom pos : | . | . | . | . |\n" + + "seq -> : TCTCTGGAGCCCCTGACTTCTGAGATGCACGCCCCTGGGGA\n" + + "tx ref dif: X \n" + + "region : T \n" + + "aa seq <- : ArgGlnLeuGlyGlnSerGlyLeuHisValGlyArgProVa\n" + + "tx seq <- : AGAGACCTCGGGGACTGAAGGCTCTACGTGCGGGGACCCCT\n" + + "tx pos : . | . | . | . | \n" + + " : 310 300 290 280\n" + + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_ref_disagree_ref_ins(self): + """A ref disagree with a region inserted inref, that is missing in transcript""" + # hgvs_g = "NC_000001.10:g.154574820_154574821delinsCA" + # var_g = self.hp.parse(hgvs_g) + # hgvs_c = "NM_020469.2:c.188_189=" + # hgvs_c = "NM_003777.3:c.5475dup" # an I variant + # hgvs_c = + + hgvs_c = "NM_198689.2:c.124_135=" + # this would match chromosome: "NM_198689.2:c.124_135insCTGCTGCGCCCCCAG" + var_c = self.hp.parse(hgvs_c) + pp = PrettyPrint(self.hdp, infer_hgvs_c=True, padding_left=30, padding_right=40) + result = pp.display(var_c) + print(result) + result = result.split("\n") + expected_str = ( + "hgvs : NC_000021.8:g.46020668_46020682del\n" + "hgvs : NM_198689.2:c.124_135=\n" + " : 46,020,630 46,020,650 46,020,670 46,020,690 46,020,710\n" + "chrom pos : | . | . | . | . | . | . | . | . | . | \n" + "seq -> : CGACTGCCCAGAGAGCTGCTGCGAGCCCCCCTGCTGCGCCCCCAGCTGCTGCGCCCCGGCCCCCTGCCTGAGCCTGGTCTGCACCCCAGTGAGCCGT\n" + "tx ref dif: IIIIIIIIIIIIIII XX \n" + "region : =-------------------------= \n" + "aa seq -> : pAspCysProGluSerCysCysGluProPr---------------oCysCysAlaProAlaProCysLeuSerLeuValCysThrProValSerTyr\n" + "tx seq -> : CGACTGCCCAGAGAGCTGCTGCGAGCCCCC---------------CTGCTGCGCCCCGGCCCCCTGCCTGAGCCTGGTCTGCACCCCAGTGAGCTAT\n" + "tx pos : . | . | . | . | . | . | . | . | . \n" + " : 110 120 130 140 150 160 170 180\n" + "ref>alt : CTGCTGCGCCCCCAGCTGCTGCGCCCC>CTGCTGCGCCCC\n" + ).split("\n") + + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_ref_disagree_del(self): + # hgvs_g = "NC_000001.10:g.154574820_154574821delinsCA" - one base is a svn relative to the tx and part of the variant -> NM_001025107.2:c.-589C>T + # var_g = self.hp.parse(hgvs_g) + # hgvs_c = "NM_020469.2:c.188_189=" is NC_000009.11:g.136135237_136135238delinsGC in ref + + + hgvs_c = "NM_000682.6:c.901_911del" # a del variant + + var_c = self.hp.parse(hgvs_c) + pp = PrettyPrint(self.hdp, infer_hgvs_c=True, padding_left=30, padding_right=40) + result = pp.display(var_c) + print(result) + result = result.split("\n") + + expected_str = ( + "hgvs : NC_000002.11:g.96780987_96780997del\n" + + "hgvs : NM_000682.6:c.901_911del\n" + + " : 96,780,960 96,780,980 96,781,000 96,781,020\n" + + "chrom pos : | . | . | . | . _________ | . | . | . | . \n" + + "seq -> : TGCCTGGGGTTCACACTCTTCCTCCTCCTCCTCCTCCTCTTC.........GGCTTCATCCTCTGGAGATGCCCCACAAACACCCTCCTTC\n" + + "tx ref dif: DDDDDDDDD \n" + + "region : x----------x \n" + + "aa seq <- : AlaGlnProGluCysGluGluGluGluGluGluGluGluGluGluGluGluAlaGluAspGluProSerAlaGlyCysValGlyGluLysG\n" + + "tx seq <- : ACGGACCCCAAGTGTGAGAAGGAGGAGGAGGAGGAGGAGAAGGAGGAGAAGTCGAAGTAGGAGACCTCTACGGGGTGTTTGTGGGAGGAAG\n" + + "tx pos : | . | . | . | . | . | . | . | . | . \n" + + " : 940 930 920 910 900 890 880 870 860\n" + + "ref>alt : CTCCTCCTCTTC>C\n" + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_ref_disagree_tx_ins(self): + """ A variant that is a tx ref disagree with an insertion in the ref genome""" + aa! + + + @pytest.mark.skip(reason="actually not that special, but still a nice variant.") + def test_exon_boundary_overlap_forward_strand(self): + hgvs_c = "NM_001283009.2:c.1228_1266+39del" + var_c = self.hp.parse(hgvs_c) + pp = PrettyPrint(self.hdp, showLegend=True, useColor=False) + + result = pp.display(var_c) + + print(result) + + assert False + + def test_ruler(self): + """Test the ruler display option turned on.""" + hgvs_c = "NM_001111.4:c.298G>A" + var_c = self.hp.parse(hgvs_c) + pp = PrettyPrint(self.hdp, showLegend=False) + + result = pp.display(var_c) + + print(result) + + result = result.split("\n") + + # note the X in the transscript ref-disagree row + expected_str = ( + "NC_000001.10:g.154574820=\n" + + "NM_001111.4:c.298G>A\n" + + "154,574,800 154,574,820 154,574,840\n" + + "| . | . | . | . |\n" + + "TCTCTGGAGCCCCTGACTTCTGAGATGCACGCCCCTGGGGA\n" + + " X \n" + + " T \n" + + "ArgGlnLeuGlyGlnSerGlyLeuHisValGlyArgProVa\n" + + "AGAGACCTCGGGGACTGAAGGCTCTACGTGCGGGGACCCCT\n" + + " . | . | . | . | \n" + + " 310 300 290 280\n" + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) From 8986d206e39dd0f06604de3cd5b62c559478e2e5 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sun, 12 May 2024 22:38:04 -0700 Subject: [PATCH 02/31] feat(pretty print): making sure we don't bloat the test cache. --- tests/test_pretty_print.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index 1c1f7761..e5d5bae5 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -16,7 +16,7 @@ class Test_SimplePosition(unittest.TestCase): def setUpClass(cls): cls.hp = hgvs.parser.Parser() cls.hdp = hgvs.dataproviders.uta.connect( - mode=os.environ.get("HGVS_CACHE_MODE", "run"), cache=CACHE + mode=None, cache=None ) cls.pp = PrettyPrint( cls.hdp, @@ -487,10 +487,6 @@ def test_ref_disagree_del(self): for r, e in zip(result, expected_str): self.assertEqual(e, r) - def test_ref_disagree_tx_ins(self): - """ A variant that is a tx ref disagree with an insertion in the ref genome""" - aa! - @pytest.mark.skip(reason="actually not that special, but still a nice variant.") def test_exon_boundary_overlap_forward_strand(self): From e9fde79f98eac282be9f93ea24ff2e16577dfb2b Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sun, 12 May 2024 22:46:00 -0700 Subject: [PATCH 03/31] feat(pretty print): making type py3.8 compatible --- src/hgvs/pretty/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hgvs/pretty/models.py b/src/hgvs/pretty/models.py index e1717a95..e2439ca7 100644 --- a/src/hgvs/pretty/models.py +++ b/src/hgvs/pretty/models.py @@ -5,7 +5,7 @@ from hgvs.assemblymapper import AssemblyMapper from hgvs.location import Interval from hgvs.sequencevariant import SequenceVariant - +from typing import List @dataclass(eq=True, repr=True, frozen=True, order=True) class VariantCoords: @@ -71,7 +71,7 @@ class VariantData: alignmentmapper: AlignmentMapper var_g: SequenceVariant var_c: SequenceVariant = None - position_details: list[PositionDetail] = None + position_details: List[PositionDetail] = None @dataclass From d3a5e6e194c0c3003d23e79a53ee7c7affb66d00 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Mon, 27 May 2024 22:58:02 -0700 Subject: [PATCH 04/31] feat(pretty print): breaking up creation of each line in display into subclasses --- .../pretty/renderer/chrom_seq_renderer.py | 37 +++++++ src/hgvs/pretty/renderer/pos_info.py | 33 +++++++ src/hgvs/pretty/renderer/prot_seq_renderer.py | 75 +++++++++++++++ src/hgvs/pretty/renderer/renderer.py | 14 +++ src/hgvs/pretty/renderer/ruler.py | 31 ++++++ src/hgvs/pretty/renderer/shuffled_variant.py | 93 ++++++++++++++++++ src/hgvs/pretty/renderer/tx_alig_renderer.py | 96 +++++++++++++++++++ .../pretty/renderer/tx_mapping_renderer.py | 67 +++++++++++++ src/hgvs/pretty/renderer/tx_pos.py | 43 +++++++++ .../renderer/tx_ref_disagree_renderer.py | 47 +++++++++ 10 files changed, 536 insertions(+) create mode 100644 src/hgvs/pretty/renderer/chrom_seq_renderer.py create mode 100644 src/hgvs/pretty/renderer/pos_info.py create mode 100644 src/hgvs/pretty/renderer/prot_seq_renderer.py create mode 100644 src/hgvs/pretty/renderer/renderer.py create mode 100644 src/hgvs/pretty/renderer/ruler.py create mode 100644 src/hgvs/pretty/renderer/shuffled_variant.py create mode 100644 src/hgvs/pretty/renderer/tx_alig_renderer.py create mode 100644 src/hgvs/pretty/renderer/tx_mapping_renderer.py create mode 100644 src/hgvs/pretty/renderer/tx_pos.py create mode 100644 src/hgvs/pretty/renderer/tx_ref_disagree_renderer.py diff --git a/src/hgvs/pretty/renderer/chrom_seq_renderer.py b/src/hgvs/pretty/renderer/chrom_seq_renderer.py new file mode 100644 index 00000000..dfd888ed --- /dev/null +++ b/src/hgvs/pretty/renderer/chrom_seq_renderer.py @@ -0,0 +1,37 @@ +from hgvs.pretty.models import VariantData +from hgvs.pretty.renderer.renderer import BasicRenderer + + +class ChromSeqRendered(BasicRenderer): + + def legend(self)->str: + return "seq -> : " + + def display(self, data:VariantData)->str: + """colors the ref sequences with adenine (A, green), thymine (T, red), cytosine (C, yellow), and guanine (G, blue)""" + from hgvs.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW + + var_seq = "" + for p in data.position_details: + c = p.ref + + if not c: + var_seq += "." + continue + + if self.config.useColor: + if c == "A": + var_seq += TGREEN + elif c == "T": + var_seq += TRED + elif c == "C": + var_seq += TYELLOW + elif c == "G": + var_seq += TBLUE + + var_seq += c + + if self.config.useColor: + var_seq += ENDC + + return var_seq diff --git a/src/hgvs/pretty/renderer/pos_info.py b/src/hgvs/pretty/renderer/pos_info.py new file mode 100644 index 00000000..85395c3e --- /dev/null +++ b/src/hgvs/pretty/renderer/pos_info.py @@ -0,0 +1,33 @@ +from hgvs.pretty.models import VariantData +from hgvs.pretty.renderer.renderer import BasicRenderer + + +class ChrPositionInfo(BasicRenderer): + + def legend(self): + return " : " + + def display(self, data: VariantData) -> str: + + count = -1 + var_seq = "" + for pdata in data.position_details: + count += 1 + g = pdata.chromosome_pos + + total_chars = len(var_seq) + + if total_chars > count: + continue + + if not g: + var_seq += " " + continue + + if g % 10 == 0: + var_seq += f"{g:,} " + + else: + var_seq += " " + + return var_seq.rstrip() \ No newline at end of file diff --git a/src/hgvs/pretty/renderer/prot_seq_renderer.py b/src/hgvs/pretty/renderer/prot_seq_renderer.py new file mode 100644 index 00000000..82dabddd --- /dev/null +++ b/src/hgvs/pretty/renderer/prot_seq_renderer.py @@ -0,0 +1,75 @@ +from hgvs.pretty.models import VariantData +from hgvs.pretty.renderer.renderer import BasicRenderer + + +class ProtSeqRenderer(BasicRenderer): + + def legend(self): + legend = "aa seq -> : " + if self.orientation < 0: + legend = "aa seq <- : " + return legend + + def display(self, data: VariantData) -> str: + if not data.var_c_or_n: + return "" + + from hgvs.pretty_print import ENDC, TGREEN, TRED + + var_str = "" + for pdata in data.position_details: + + p = pdata.chromosome_pos + + if not pdata.mapped_pos: + var_str += " " + continue + + ref_base = pdata.ref + + c_interval = pdata.c_interval + if not c_interval: + var_str += " " + continue + + cig = pdata.cigar_ref + c_offset = pdata.c_offset + if cig == "N" or c_offset != 0: + var_str += " " + continue + + if cig == "I": + var_str += "-" + continue + + if not ref_base or pdata.tx: + ref_base = pdata.tx + + protein_data = pdata.protein_data + + if not protein_data: + var_str += " " + continue + + aa_char = protein_data.aa_char + + # color init met and stop codon if using color: + if protein_data.is_init_met: + if self.config.useColor: + aa_char = TGREEN + aa_char + ENDC + var_str += aa_char + continue + if protein_data.is_stop_codon: + if self.config.useColor: + aa_char = TRED + aa_char + ENDC + var_str += aa_char + continue + + if not protein_data.var_p.posedit: + var_str += " " + continue + + var_str += aa_char + continue + + return var_str \ No newline at end of file diff --git a/src/hgvs/pretty/renderer/renderer.py b/src/hgvs/pretty/renderer/renderer.py new file mode 100644 index 00000000..a0d33d5e --- /dev/null +++ b/src/hgvs/pretty/renderer/renderer.py @@ -0,0 +1,14 @@ +from abc import ABC, abstractmethod + +class BasicRenderer(ABC): + def __init__(self, config, orientation:int): + self.config = config + self.orientation = orientation + + @abstractmethod + def legend(self): + pass + + @abstractmethod + def display(self): + pass diff --git a/src/hgvs/pretty/renderer/ruler.py b/src/hgvs/pretty/renderer/ruler.py new file mode 100644 index 00000000..e8d3f3cb --- /dev/null +++ b/src/hgvs/pretty/renderer/ruler.py @@ -0,0 +1,31 @@ +from hgvs.pretty.models import VariantData +from hgvs.pretty.renderer.renderer import BasicRenderer + + +class ChrRuler(BasicRenderer): + + def legend(self): + """ returns the legend for this category of display""" + return "chrom pos : " + + def display(self, data: VariantData) -> str: + """Draw a position indicator that diplays | every 10 bases and a . every 5 + + seq_start/end in interbase + """ + + ruler = "" + + for pd in data.position_details: + p = pd.chromosome_pos + if not p: + ruler += "_" + continue + + if p % 10 == 0: + ruler += "|" + elif p % 5 == 0: + ruler += "." + else: + ruler += " " + return ruler \ No newline at end of file diff --git a/src/hgvs/pretty/renderer/shuffled_variant.py b/src/hgvs/pretty/renderer/shuffled_variant.py new file mode 100644 index 00000000..74b0d735 --- /dev/null +++ b/src/hgvs/pretty/renderer/shuffled_variant.py @@ -0,0 +1,93 @@ + + +from hgvs.pretty.models import VariantCoords, VariantData +from hgvs.pretty.renderer.renderer import BasicRenderer +from hgvs.sequencevariant import SequenceVariant + + +class ShuffledVariant(BasicRenderer): + + def __init__(self, config, orientation:int, var_g: SequenceVariant, vc: VariantCoords)->None: + super().__init__(config, orientation) + + self.var_g = var_g + self.vc = vc + + def legend(self + ) ->str: + + return "region : " + + def display( + self, data: VariantData + ) -> str: + + from hgvs.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW + + seq_start = data.display_start + seq_end = data.display_end + + split_char = "|" + + start = self.vc.start + end = self.vc.end + + # map interbase coordinates to 1-based display coords: + start = start + 1 + + if self.var_g.posedit.edit.type == "sub": + end = start + if len(self.vc.alt) == 1: + split_char = self.vc.alt + l = end - start + 1 + if self.var_g.posedit.edit.type == "ins" and l == 0: + start = start - 1 + end = end + 1 + split_char = "^" + + if self.var_g.posedit.edit.type == "del": + split_char = "" + if self.config.useColor: + split_char = TRED + split_char += "x" + if self.config.useColor: + split_char += ENDC + + elif self.var_g.posedit.edit.type == "identity": + split_char = "=" + # print(l,start, end , vc) + + if start < seq_start: + # raise ValueError(f"Can't create shuffled representation, since start {start} < seq_start {seq_start} ") + return "" + if end > seq_end: + return "" + # raise ValueError(f"Can't create shuffled representation, since end {end} > seq_end {seq_end} ") + + var_str = "" + in_range = False + for pdata in data.position_details: + p = pdata.chromosome_pos + + if not p: + if in_range: + var_str += "-" + else: + var_str += " " + continue + + if p == start: + var_str += split_char + in_range = True + elif p == end: + var_str += split_char + in_range = False + elif p > end and in_range: + in_range = False + var_str += " " + elif in_range: + var_str += "-" + else: + var_str += " " + + return var_str \ No newline at end of file diff --git a/src/hgvs/pretty/renderer/tx_alig_renderer.py b/src/hgvs/pretty/renderer/tx_alig_renderer.py new file mode 100644 index 00000000..eeb84b34 --- /dev/null +++ b/src/hgvs/pretty/renderer/tx_alig_renderer.py @@ -0,0 +1,96 @@ +import math +from hgvs.pretty.models import VariantData +from hgvs.pretty.renderer.renderer import BasicRenderer + + +class TxAligRenderer(BasicRenderer): + + def legend(self)->str: + orientation = "->" + if self.orientation < 0: + orientation = "<-" + return f"tx seq {orientation} : " + + + def display(self, data: VariantData) -> str: + """If transcript info is available show the details of the tx for the region.""" + if not data.var_c_or_n: + return "" + + from hgvs.pretty_print import ENDC, TYELLOW, TPURPLE + var_str = "" + + first_c = None + last_c = None + + coding = False + c_pos = None + + counter = -1 + for pdata in data.position_details: + + counter += 1 + + if not pdata.mapped_pos: + var_str += " " + continue + + + n_pos = pdata.n_pos + c_pos = pdata.c_pos + cig = pdata.cigar_ref + c_offset = pdata.c_offset + + if cig == "N" or (c_offset is not None and c_offset != 0): + coding = False + var_str += " " + continue + + + if c_pos: + # if we get here we are coding... + last_c = f"c.{c_pos}" + else: + last_c = f"n.{n_pos}" + c_pos = n_pos + if not coding: + + if not first_c: + first_c = last_c + + coding = True + + if cig == "=": + #c3 = (c_pos - 1) % 3 + if c_pos: + bg_col = math.ceil(c_pos / 3) % 2 + elif n_pos: + bg_col = math.ceil(n_pos / 3) % 2 + else: + var_str += " " + continue + + # print(p, c_pos, c3, bg_col ) + if n_pos >= 0: + base = pdata.tx + if self.config.useColor and c_pos > 0: + if bg_col: + var_str += TPURPLE + base + ENDC + else: + var_str += TYELLOW + base + ENDC + else: + if c_pos is None or c_pos < 0: + var_str += base.lower() + else: + var_str += base + continue + + elif cig == "X" or cig == "D": + # for mismatches and tx-insertions show sequence + var_str += pdata.tx + continue + + else: + var_str += "-" + + return var_str \ No newline at end of file diff --git a/src/hgvs/pretty/renderer/tx_mapping_renderer.py b/src/hgvs/pretty/renderer/tx_mapping_renderer.py new file mode 100644 index 00000000..52d21e70 --- /dev/null +++ b/src/hgvs/pretty/renderer/tx_mapping_renderer.py @@ -0,0 +1,67 @@ +from hgvs.pretty.models import VariantData +from hgvs.pretty.renderer.renderer import BasicRenderer + + +class TxMappingRenderer(BasicRenderer): + """ prints the position in c/n coordinates. """ + + def legend(self): + return "tx pos : " + + def display(self, data: VariantData) -> str: + """show the position of the transcript seq""" + + var_str = "" + + count = -1 + prev_c_pos = '' + for pdata in data.position_details: + count += 1 + if not pdata.mapped_pos: + var_str += " " + prev_c_pos = '' + continue + + c_pos = pdata.c_pos + if c_pos is None and pdata.n_pos: + c_pos = pdata.n_pos + + if c_pos is None: + var_str += " " + prev_c_pos = c_pos + continue + + if pdata.cigar_ref == 'N': + var_str += " " + prev_c_pos = c_pos + continue + + if len(var_str) > count: + prev_c_pos = c_pos + continue + + if (c_pos + 1) % 10 == 0: + var_str += "|" + prev_c_pos = c_pos + continue + + elif (c_pos + 1) % 5 == 0: + var_str += "." + prev_c_pos = c_pos + continue + + elif prev_c_pos and prev_c_pos == c_pos and pdata.c_offset > 0 and (pdata.c_offset % 10) == 0: + var_str += "^" + prev_c_pos = c_pos + continue + elif prev_c_pos and prev_c_pos == c_pos and pdata.c_offset > 0 and (pdata.c_offset % 5) == 0: + var_str += "." + prev_c_pos = c_pos + continue + + elif c_pos == 0: + var_str += "|" + + var_str += " " + + return var_str \ No newline at end of file diff --git a/src/hgvs/pretty/renderer/tx_pos.py b/src/hgvs/pretty/renderer/tx_pos.py new file mode 100644 index 00000000..9bbe19d0 --- /dev/null +++ b/src/hgvs/pretty/renderer/tx_pos.py @@ -0,0 +1,43 @@ +from hgvs.pretty.models import VariantData +from hgvs.pretty.renderer.renderer import BasicRenderer + + + + +class TxRulerRenderer(BasicRenderer): + + def legend(self)->str: + return " : " + + def display(self, data: VariantData) -> str: + """show the position of the transcript seq""" + var_str = "" + + count = -1 + for pdata in data.position_details: + count += 1 + if not pdata.mapped_pos: + var_str += " " + continue + + c_pos = pdata.c_pos + + if c_pos is None: + var_str += " " + continue + + if len(var_str) > count: + continue + + if (c_pos + 1) % 10 == 0: + # if pdata.c_interval.start.datum == Datum.CDS_END: + # var_str += "*" + var_str += f"{pdata.c_interval} " + continue + + elif c_pos == 0: + var_str += f"{pdata.c_interval} " + continue + var_str += " " + + return var_str.rstrip() \ No newline at end of file diff --git a/src/hgvs/pretty/renderer/tx_ref_disagree_renderer.py b/src/hgvs/pretty/renderer/tx_ref_disagree_renderer.py new file mode 100644 index 00000000..30ca5287 --- /dev/null +++ b/src/hgvs/pretty/renderer/tx_ref_disagree_renderer.py @@ -0,0 +1,47 @@ +from hgvs.pretty.models import VariantData +from hgvs.pretty.renderer.renderer import BasicRenderer + + +class TxRefDisagreeRenderer(BasicRenderer): + """ Display tx-ref-disagree positions""" + def legend(self)->str: + + return f"tx ref dif: " + + def display(self, data: VariantData) -> str: + """show differences between tx and ref genome, if there are any""" + + if not data.var_c_or_n: + return "" + + + from hgvs.pretty_print import ENDC, TRED + + var_str = "" + counter = -1 + for p in range(data.display_start + 1, data.display_end + 1): + counter += 1 + pdata = data.position_details[counter] + c_offset = pdata.c_offset + if not pdata.mapped_pos: + var_str += " " + continue + + cig = pdata.cigar_ref + if cig == "=": + var_str += " " + elif cig == "N" or c_offset != 0: + var_str += " " + continue + else: + # an alignment issue, show cigar string + if self.config.useColor: + var_str += TRED + cig + ENDC + else: + var_str += cig + + if var_str.isspace(): + return "" + + + return var_str \ No newline at end of file From f2018dfc66bb8d096b718a87f5153bdc61d78a33 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Tue, 28 May 2024 19:47:27 -0700 Subject: [PATCH 05/31] feat(pretty print): breaking up creation of each line in display into subclasses --- src/hgvs/pretty/datacompiler.py | 130 +++++-- src/hgvs/pretty/models.py | 20 +- src/hgvs/pretty_print.py | 588 ++++++-------------------------- src/hgvs/shell.py | 1 + tests/data/cache-py3.hdp | Bin 924046 -> 725622 bytes tests/test_pretty_print.py | 24 +- 6 files changed, 241 insertions(+), 522 deletions(-) diff --git a/src/hgvs/pretty/datacompiler.py b/src/hgvs/pretty/datacompiler.py index 3dc5ba07..ce03facc 100644 --- a/src/hgvs/pretty/datacompiler.py +++ b/src/hgvs/pretty/datacompiler.py @@ -6,7 +6,7 @@ import hgvs import hgvs.utils.altseq_to_hgvsp as altseq_to_hgvsp import hgvs.utils.altseqbuilder as altseqbuilder -from hgvs.exceptions import HGVSInvalidIntervalError +from hgvs.exceptions import HGVSDataNotAvailableError, HGVSInvalidIntervalError from hgvs.pretty.models import ( PositionDetail, PrettyConfig, @@ -16,7 +16,7 @@ ) from hgvs.sequencevariant import SequenceVariant from hgvs.utils.reftranscriptdata import RefTranscriptData - +from typing import Optional class DataCompiler: @@ -91,6 +91,26 @@ def get_position_and_state( return start, end, ref, alt + def _get_exon_nr(self, tx_exons, genomic_pos)-> Tuple[int, str]: + + i = -1 + for ex in tx_exons: + i+=1 + exon_nr = ex['ord'] + 1 + + if ex ['alt_start_i'] < genomic_pos and ex['alt_end_i'] >= genomic_pos: + return (exon_nr, 'exon') + + if i > 0: + if ex['alt_strand'] > 0: + if tx_exons[i - 1]["alt_end_i"] < genomic_pos and tx_exons[i]["alt_start_i"] >= genomic_pos: + return (exon_nr, 'intron') + else: + if tx_exons[i]["alt_start_i"] < genomic_pos and tx_exons[i-1]["alt_end_i"] >= genomic_pos: + return (i, 'intron') + + return (-1, 'no-overlap') + def _get_prot_alt( self, tx_ac: str, strand: int, reference_data, ref_base, c_interval ) -> ProteinData: @@ -133,7 +153,7 @@ def _get_prot_alt( def data( self, var_g: SequenceVariant, - var_c: SequenceVariant = None, + var_c_or_n: SequenceVariant = None, display_start: int = None, display_end: int = None, ) -> VariantData: @@ -160,34 +180,60 @@ def data( else: seq_end = display_end + if var_c_or_n is not None: + tx_ac = var_c_or_n.ac + else: + tx_ac = '' # can't show transcript , since there is none. + + alt_ac = var_g.ac + alt_aln_method = 'splign' + + if tx_ac: + tx_exons = self.config.hdp.get_tx_exons(tx_ac, alt_ac, alt_aln_method) + tx_exons = sorted(tx_exons, key=lambda e: e["ord"]) + else: + tx_exons = [] + chrom_seq = self.config.hdp.get_seq(var_g.ac) disp_seq = chrom_seq[seq_start:seq_end] + + if tx_ac : + tx_seq = self.config.hdp.get_seq(tx_ac) + if self.config.default_assembly == "GRCh37": + am = self.config.am37 + else: + am = self.config.am38 - tx_ac = var_c.ac - tx_seq = self.config.hdp.get_seq(tx_ac) - #print(tx_seq) + mapper = am._fetch_AlignmentMapper(tx_ac=tx_ac, alt_ac=var_g.ac, alt_aln_method="splign") - if self.config.default_assembly == "GRCh37": - am = self.config.am37 else: - am = self.config.am38 - - mapper = am._fetch_AlignmentMapper(tx_ac=tx_ac, alt_ac=var_g.ac, alt_aln_method="splign") + tx_seq = "" + mapper = None + #print(tx_seq) + # we don't know the protein ac, get it looked up: pro_ac = None - reference_data = RefTranscriptData(self.config.hdp, tx_ac, pro_ac) + if var_c_or_n and var_c_or_n.type == 'c': + reference_data = RefTranscriptData(self.config.hdp, tx_ac, pro_ac) + else: + reference_data = None position_details = [] prev_mapped_pos = None + prev_c_pos = -1 for chromosome_pos in range(seq_start + 1, seq_end + 1): - pdata = PositionDetail(chromosome_pos=chromosome_pos) + exon_nr, feat = self._get_exon_nr(tx_exons, chromosome_pos) + + pdata = PositionDetail(chromosome_pos=chromosome_pos, exon_nr=exon_nr, variant_feature=feat) position_details.append(pdata) pdata.ref = chrom_seq[chromosome_pos - 1] + if not mapper: + continue try: gr = chromosome_pos - mapper.gc_offset - 1 pdata.alignment_pos = gr @@ -210,12 +256,12 @@ def data( pdata.mapped_pos = prev_mapped_pos # a region in ref that has been deleted. Fill in gaps. - self._backfill_gap_in_ref(tx_ac, tx_seq, mapper, reference_data, pdata, cig, prev_c_pos +1, prev_n_pos+1) + self._backfill_gap_in_ref(var_c_or_n, tx_seq, tx_exons, mapper, reference_data, pdata, cig, prev_c_pos +1, prev_n_pos+1) - print(f"pdata at end of backfill: {pdata}") - + + exon_nr, feat = self._get_exon_nr(tx_exons, chromosome_pos) #prev_mapped_pos += 1 - pdata = PositionDetail(chromosome_pos=chromosome_pos) + pdata = PositionDetail(chromosome_pos=chromosome_pos, exon_nr=exon_nr, variant_feature=feat) position_details.append(pdata) pdata.alignment_pos = gr @@ -241,33 +287,40 @@ def data( try: n_interval = mapper.g_to_n(g_interval) - c_interval = mapper.n_to_c(n_interval) + if var_c_or_n.type == 'c': + c_interval = mapper.n_to_c(n_interval) + else: + c_interval = None except hgvs.exceptions.HGVSInvalidIntervalError: # we are beyond the transcript space, can't set any of the other values. continue pdata.n_interval = n_interval - pdata.c_interval = c_interval - - n_pos = int(n_interval.start.base) - 1 - c_pos = int(c_interval.start.base) - 1 + if c_interval is not None: + pdata.c_interval = c_interval + c_pos = int(c_interval.start.base) - 1 + prev_c_pos = c_pos + else: + prev_c_pos = -1 + + n_pos = int(n_interval.start.base) - 1 prev_n_pos = n_pos - prev_c_pos = c_pos - + self._populate_with_n_c( - var_c.ac, tx_seq, mapper, reference_data, pdata, cig, n_interval, c_interval + var_c_or_n, tx_seq, tx_exons, mapper, reference_data, pdata, cig, n_interval, c_interval ) - for p in position_details: - print(f"{p}\t{p.protein_data}\t") + # print(position_details[0].get_header()) + # for p in position_details: + # print(f"{p}\t{p.protein_data}\t") vd = VariantData( - seq_start, seq_end, ls, rs, fs, disp_seq, tx_seq, mapper, var_g, var_c, position_details + seq_start, seq_end, ls, rs, fs, disp_seq, tx_seq, mapper, var_g, mapper.strand, var_c_or_n, position_details ) return vd - def _backfill_gap_in_ref(self, tx_ac, tx_seq, mapper, reference_data, pdata, cig, prev_c_pos, prev_n_pos ): + def _backfill_gap_in_ref(self, var_c_or_n, tx_seq, tx_exons, mapper, reference_data, pdata, cig, prev_c_pos, prev_n_pos ): pdata.chromosome_pos = None pdata.ref = None @@ -290,25 +343,33 @@ def _backfill_gap_in_ref(self, tx_ac, tx_seq, mapper, reference_data, pdata, cig pdata.c_interval = c_interval self._populate_with_n_c( - tx_ac, tx_seq, mapper, reference_data, pdata, cig, n_interval, c_interval + var_c_or_n, tx_seq, tx_exons, mapper, reference_data, pdata, cig, n_interval, c_interval ) def _populate_with_n_c( - self, tx_ac, tx_seq, mapper, reference_data, pdata, cig, n_interval, c_interval + self, var_c_or_n, tx_seq, tx_exons, mapper, reference_data, pdata, cig, n_interval, c_interval ): n_pos = int(n_interval.start.base) - 1 - c_pos = int(c_interval.start.base) - 1 + if c_interval: + c_pos = int(c_interval.start.base) - 1 + pdata.c_pos = c_pos + pdata.c_offset = c_interval.start.offset + else: + c_pos = None # print(f"{n_interval} {c_pos} {c_interval.start.datum}") + # set transcript_feature: + tx_ac = var_c_or_n.ac pdata.n_pos = n_pos - pdata.c_pos = c_pos - pdata.c_offset = c_interval.start.offset + pdata.tx = tx_seq[pdata.n_pos] coding = True + if var_c_or_n.type == 'n': # rna coding can't be in protein space + coding = False if cig == "N" or pdata.c_offset != 0: coding = False @@ -319,7 +380,6 @@ def _populate_with_n_c( if coding: if not ref_base or pdata.tx: ref_base = pdata.tx - prot_data = self._get_prot_alt( tx_ac, mapper.strand, reference_data, ref_base, c_interval ) diff --git a/src/hgvs/pretty/models.py b/src/hgvs/pretty/models.py index e2439ca7..f283978e 100644 --- a/src/hgvs/pretty/models.py +++ b/src/hgvs/pretty/models.py @@ -47,13 +47,23 @@ class PositionDetail: n_interval: Interval = None c_interval: Interval = None protein_data: ProteinData = None + exon_nr: int = None + variant_feature: str = None + + @classmethod + def get_header(cls) -> str: + return ( + "alignment_pos\tc_pos\tc_offset\tc_interval\tcigar_ref\tchromosome_pos\tref\tmapped_pos\t" + "mapped_pos_offset\tn_pos\ttx\tvariant_feature\texon_nr" + ) def __repr__(self) -> str: return ( - f"{self.mapped_pos}\t{self.c_pos}\t{self.c_offset}\t{self.c_interval}\t" + f"{self.alignment_pos}\t{self.c_pos}\t{self.c_offset}\t{self.c_interval}\t" + f"{self.cigar_ref}\t" - + f"{self.chromosome_pos}\t{self.ref}\t{self.alignment_pos}\t{self.mapped_pos_offset}\t" + + f"{self.chromosome_pos}\t{self.ref}\t{self.mapped_pos}\t{self.mapped_pos_offset}\t" + f"\t{self.n_pos}\t{self.tx}" + + f"\t{self.variant_feature} {self.exon_nr}" ) @@ -70,8 +80,11 @@ class VariantData: tx_seq: str alignmentmapper: AlignmentMapper var_g: SequenceVariant - var_c: SequenceVariant = None + strand:int + var_c_or_n: SequenceVariant = None position_details: List[PositionDetail] = None + all: bool = False + @dataclass @@ -87,3 +100,4 @@ class PrettyConfig: useColor: bool = False showLegend: bool = True infer_hgvs_c: bool = True + all:bool = False diff --git a/src/hgvs/pretty_print.py b/src/hgvs/pretty_print.py index 8ebfb50e..d4e46292 100644 --- a/src/hgvs/pretty_print.py +++ b/src/hgvs/pretty_print.py @@ -1,13 +1,20 @@ -import math -from typing import Tuple + +from typing import List import hgvs from hgvs.assemblymapper import AssemblyMapper -from hgvs.enums import Datum from hgvs.pretty.datacompiler import DataCompiler -from hgvs.pretty.models import PrettyConfig, VariantCoords, VariantData +from hgvs.pretty.models import PrettyConfig +from hgvs.pretty.renderer.chrom_seq_renderer import ChromSeqRendered +from hgvs.pretty.renderer.pos_info import ChrPositionInfo +from hgvs.pretty.renderer.prot_seq_renderer import ProtSeqRenderer +from hgvs.pretty.renderer.ruler import ChrRuler +from hgvs.pretty.renderer.shuffled_variant import ShuffledVariant +from hgvs.pretty.renderer.tx_alig_renderer import TxAligRenderer +from hgvs.pretty.renderer.tx_mapping_renderer import TxMappingRenderer +from hgvs.pretty.renderer.tx_pos import TxRulerRenderer +from hgvs.pretty.renderer.tx_ref_disagree_renderer import TxRefDisagreeRenderer from hgvs.sequencevariant import SequenceVariant -from hgvs.utils.reftranscriptdata import RefTranscriptData TGREEN = "\033[32m" # Green Text TGREENBG = "\033[30;42m" @@ -35,6 +42,7 @@ def __init__( useColor=False, showLegend=True, infer_hgvs_c=True, + all=False ): """ :param hdp: HGVS Data Provider Interface-compliant instance @@ -55,453 +63,45 @@ def __init__( useColor, showLegend, infer_hgvs_c, + all ) - # def _display_position_info(self, data:VariantData)->str: - - # seq_start = data.display_start - # seq_end = data.display_end - - # line_start = f"{seq_start + 1:,} " - # line_end = f"{seq_end:,}" - # ls = len(line_start) - # le = len(line_end) - - # total_chars = seq_end - seq_start + 1 - - # gap = total_chars - ls - le -1 - - # line = line_start - # p = 0 - # while p < gap: - # line += ' ' - # p+=1 - - # line += line_end - # return line - - def _display_position_info(self, data: VariantData) -> str: - - count = -1 - var_seq = "" - for pdata in data.position_details: - count += 1 - g = pdata.chromosome_pos - - total_chars = len(var_seq) - - if total_chars > count: - continue - - if not g: - var_seq += " " - continue - - if g % 10 == 0: - var_seq += f"{g:,} " - - else: - var_seq += " " - - return var_seq.rstrip() - - def _display_ruler(self, data: VariantData) -> str: - """Draw a position indicator that diplays | every 10 bases and a . every 5 - - seq_start/end in interbase - """ - - ruler = "" - - for pd in data.position_details: - p = pd.chromosome_pos - if not p: - ruler += "_" - continue - - if p % 10 == 0: - ruler += "|" - elif p % 5 == 0: - ruler += "." - else: - ruler += " " - return ruler - - def _display_seq(self, data: VariantData) -> str: - """colors the ref sequences with adenine (A, green), thymine (T, red), cytosine (C, yellow), and guanine (G, blue)""" - - var_seq = "" - for p in data.position_details: - c = p.ref - - if not c: - var_seq += "." - continue - - if self.config.useColor: - if c == "A": - var_seq += TGREEN - elif c == "T": - var_seq += TRED - elif c == "C": - var_seq += TYELLOW - elif c == "G": - var_seq += TBLUE - - var_seq += c - - if self.config.useColor: - var_seq += ENDC - - return var_seq - - def _display_shuffled_range( - self, var_g: SequenceVariant, data: VariantData, vc: VariantCoords - ) -> str: - - seq_start = data.display_start - seq_end = data.display_end - - split_char = "|" - - start = vc.start - end = vc.end - - # map interbase coordinates to 1-based display coords: - start = start + 1 - - if var_g.posedit.edit.type == "sub": - end = start - if len(vc.alt) == 1: - split_char = vc.alt - - l = end - start + 1 - if var_g.posedit.edit.type == "ins" and l == 0: - start = start - 1 - end = end + 1 - split_char = "^" - - if var_g.posedit.edit.type == "del": - split_char = "" - if self.config.useColor: - split_char = TRED - split_char += "x" - if self.config.useColor: - split_char += ENDC - - elif var_g.posedit.edit.type == "identity": - split_char = "=" - # print(l,start, end , vc) - - if start < seq_start: - # raise ValueError(f"Can't create shuffled representation, since start {start} < seq_start {seq_start} ") - return "" - if end > seq_end: - return "" - # raise ValueError(f"Can't create shuffled representation, since end {end} > seq_end {seq_end} ") - - var_str = "" - in_range = False - for pdata in data.position_details: - p = pdata.chromosome_pos - - if not p: - if in_range: - var_str += "-" - else: - var_str += " " - continue - - if p == start: - var_str += split_char - in_range = True - elif p == end: - var_str += split_char - in_range = False - elif p > end and in_range: - in_range = False - var_str += " " - elif in_range: - var_str += "-" - else: - var_str += " " - - return var_str - def _infer_hgvs_c(self, var_g: SequenceVariant) -> SequenceVariant: + def _get_assembly_mapper(self)->AssemblyMapper: if self.config.default_assembly == "GRCh37": am = self.config.am37 else: am = self.config.am38 + + return am - transcripts = am.relevant_transcripts(var_g) - if transcripts: - tx_ac = transcripts[0] - else: - return None - - var_c = am.g_to_c(var_g, tx_ac) - return var_c - - def _display_tx_ref_disagree_str(self, data: VariantData) -> str: - """show differences between tx and ref genome, if there are any""" - - if not data.var_c: - return "" - - var_str = "" - - counter = -1 - for p in range(data.display_start + 1, data.display_end + 1): - counter += 1 - pdata = data.position_details[counter] - c_offset = pdata.c_offset - if not pdata.mapped_pos: - var_str += " " - continue - - cig = pdata.cigar_ref - if cig == "=": - var_str += " " - elif cig == "N" or c_offset != 0: - var_str += " " - continue - else: - # an alignment issue, show cigar string - if self.config.useColor: - var_str += TRED + cig + ENDC - else: - var_str += cig - - if var_str.isspace(): - return "" - - if self.config.showLegend: - return f"tx ref dif: " + var_str - - return var_str + def _get_all_transcripts(self, var_g) ->List[str]: + am = self._get_assembly_mapper() - def _display_transcript_str(self, data: VariantData) -> str: - """If transcript info is available show the details of the tx for the region.""" - if not data.var_c: - return "" + transcripts = am.relevant_transcripts(var_g) - mapper = data.alignmentmapper + return transcripts - orientation = "->" - if mapper.strand < 0: - orientation = "<-" - var_str = "" - if self.config.showLegend: - var_str = f"tx seq {orientation} : " - - first_c = None - last_c = None - - tx_seq = data.tx_seq - - coding = False - c_pos = None - - counter = -1 - for pdata in data.position_details: - p = pdata.chromosome_pos - counter += 1 - - if not pdata.mapped_pos: - var_str += " " - continue - - n_pos = pdata.n_pos - c_pos = pdata.c_pos - cig = pdata.cigar_ref - c_offset = pdata.c_offset - - if cig == "N" or c_offset != 0: - coding = False - var_str += " " - continue - - # if we get here we are coding... - last_c = f"c.{c_pos}" - if not coding: - - if not first_c: - first_c = last_c - - coding = True - - if cig == "=": - c3 = (c_pos - 1) % 3 - bg_col = math.ceil(c_pos / 3) % 2 - # print(p, c_pos, c3, bg_col ) - if n_pos >= 0: - base = pdata.tx - if self.config.useColor and c_pos > 0: - if bg_col: - var_str += TPURPLE + base + ENDC - else: - var_str += TYELLOW + base + ENDC - else: - if c_pos < 0: - var_str += base.lower() - else: - var_str += base - continue - - elif cig == "X" or cig == "D": - # for mismatches and tx-insertions show sequence - var_str += pdata.tx - continue + def _infer_hgvs_c(self, var_g: SequenceVariant, tx_ac:str=None) -> SequenceVariant: + + if not tx_ac: + transcripts = self._get_all_transcripts(var_g) + if transcripts: + tx_ac = transcripts[0] else: - var_str += "-" - - return var_str - - def _display_protein_str(self, data: VariantData) -> str: - if not data.var_c: - return "" - - mapper = data.alignmentmapper - - var_str = "" - for pdata in data.position_details: - - p = pdata.chromosome_pos - - if not pdata.mapped_pos: - var_str += " " - continue - - ref_base = pdata.ref - - c_interval = pdata.c_interval - if not c_interval: - var_str += " " - continue - - cig = pdata.cigar_ref - c_offset = pdata.c_offset - if cig == "N" or c_offset != 0: - var_str += " " - continue - - if cig == "I": - var_str += "-" - continue - - if not ref_base or pdata.tx: - ref_base = pdata.tx - - protein_data = pdata.protein_data - - if not protein_data: - var_str += " " - continue - - aa_char = protein_data.aa_char - - # color init met and stop codon if using color: - if protein_data.is_init_met: - if self.config.useColor: - aa_char = TGREEN + aa_char + ENDC - var_str += aa_char - continue - if protein_data.is_stop_codon: - if self.config.useColor: - aa_char = TRED + aa_char + ENDC - var_str += aa_char - continue - - if not protein_data.var_p.posedit: - var_str += " " - continue - - var_str += aa_char - continue - - if self.config.showLegend: - legend = "aa seq -> : " - if mapper.strand < 0: - legend = "aa seq <- : " - return legend + var_str - - return var_str - - def _display_c_pos(self, data: VariantData) -> str: - """show the position of the transcript seq""" - var_str = "" - - count = -1 - for pdata in data.position_details: - count += 1 - if not pdata.mapped_pos: - var_str += " " - continue + return None - c_pos = pdata.c_pos + am = self._get_assembly_mapper() - if c_pos is None: - var_str += " " - continue - - if len(var_str) > count: - continue - - if (c_pos + 1) % 10 == 0: - # if pdata.c_interval.start.datum == Datum.CDS_END: - # var_str += "*" - var_str += f"{pdata.c_interval} " - continue - - elif c_pos == 0: - var_str += f"{pdata.c_interval} " - continue - var_str += " " - - if self.config.showLegend: - return " : " + var_str.rstrip() - return var_str.rstrip() - - def _display_c_pos_ruler(self, data: VariantData) -> str: - """show the position of the transcript seq""" - - var_str = "" - - count = -1 - for pdata in data.position_details: - count += 1 - if not pdata.mapped_pos: - var_str += " " - continue - - c_pos = pdata.c_pos - - if c_pos is None: - var_str += " " - continue - - - if len(var_str) > count: - continue - - if (c_pos + 1) % 10 == 0: - var_str += "|" - continue - - elif (c_pos + 1) % 5 == 0: - var_str += "." - continue - elif c_pos == 0: - var_str += "|" - - var_str += " " + if tx_ac.startswith('NR_'): + var_n = am.g_to_n(var_g, tx_ac) + return var_n + var_c = am.g_to_c(var_g, tx_ac) + return var_c - if self.config.showLegend: - return "tx pos : " + var_str - return var_str + def _map_to_chrom(self, sv: SequenceVariant) -> SequenceVariant: """maps a variant to chromosomal coords, if needed.""" @@ -533,33 +133,47 @@ def _colorize_hgvs(self, hgvs_str: str) -> str: return var_str def display( - self, sv: SequenceVariant, display_start: int = None, display_end: int = None + self, sv: SequenceVariant, tx_ac: str = None, display_start: int = None, display_end: int = None ) -> str: """Takes a variant and prints the genomic context around it.""" - var_c = None + var_c_or_n = None if sv.type == "g": var_g = sv + if tx_ac is not None: + var_c_or_n = self._infer_hgvs_c(var_g, tx_ac=tx_ac) elif sv.type == "c": var_g = self._map_to_chrom(sv) - var_c = sv - elif sv.type in ["c", "n"]: + var_c_or_n = sv + elif sv.type == "n": # map back to genome var_g = self._map_to_chrom(sv) + var_c_or_n = sv - if not var_c: - if self.config.infer_hgvs_c: - var_c = self._infer_hgvs_c(var_g) + data_compiler = DataCompiler(config=self.config) - dc = DataCompiler(config=self.config) + if self.config.all: + # get all overlapping transcripts - data = dc.data(var_g, var_c, display_start, display_end) + response = "" + tx_acs = self._get_all_transcripts(var_g) + print(f"displaying {len(tx_acs)} alternative transcripts") + for tx_ac in tx_acs: + var_c_or_n = self._infer_hgvs_c(var_g, tx_ac) + response += self.create_repre(var_g, var_c_or_n, display_start, display_end, data_compiler) + response += "\n---\n" + return response + else: - pos_info = self._display_position_info(data) + if not var_c_or_n: + if self.config.infer_hgvs_c: + var_c_or_n = self._infer_hgvs_c(var_g) - ruler = self._display_ruler(data) + + return self.create_repre(var_g, var_c_or_n, display_start, display_end, data_compiler) - seq = self._display_seq(data) + def create_repre(self, var_g:SequenceVariant, var_c_or_n:SequenceVariant, display_start:int, display_end:int, data_compiler:DataCompiler): + data = data_compiler.data(var_g, var_c_or_n, display_start, display_end) left_shuffled_var = data.left_shuffled right_shuffled_var = data.right_shuffled @@ -567,13 +181,9 @@ def display( if self.config.showLegend: head = "hgvs : " - posh = " : " - rule = "chrom pos : " - seqe = "seq -> : " - regi = "region : " refa = "ref>alt : " else: - head = posh = rule = seqe = regi = refa = "" + head = refa = "" if self.config.useColor: var_g_print = self._colorize_hgvs(str(var_g)) @@ -581,44 +191,64 @@ def display( var_g_print = str(var_g) var_str = head + var_g_print + "\n" - if data.var_c: + if data.var_c_or_n: if self.config.useColor: - var_c_print = self._colorize_hgvs(str(var_c)) + var_c_print = self._colorize_hgvs(str(var_c_or_n)) else: - var_c_print = str(var_c) + var_c_print = str(var_c_or_n) var_str += head + var_c_print + "\n" - var_str += posh + pos_info + "\n" + rule + ruler + "\n" + seqe + seq + "\n" + renderer_cls= [ChrPositionInfo,ChrRuler , ChromSeqRendered, TxRefDisagreeRenderer] + + renderers = [] + for cls in renderer_cls: + r = cls(self.config, data.strand) + renderers.append(r) + - tx_ref_disagree_str = self._display_tx_ref_disagree_str(data) - if tx_ref_disagree_str: - var_str += tx_ref_disagree_str + "\n" + for renderer in renderers: + d = '' + if self.config.showLegend: + d += renderer.legend() + str_results = renderer.display(data) - left_shuffled_str = self._display_shuffled_range(sv, data, left_shuffled_var) - right_shuffled_str = self._display_shuffled_range(sv, data, right_shuffled_var) + if str_results: + var_str += d + str_results + "\n" + + left_shuffled_renderer = ShuffledVariant(self.config, data.strand, var_g, left_shuffled_var) + left_shuffled_str = left_shuffled_renderer.display(data) + + right_shuffled_renderer = ShuffledVariant(self.config, data.strand, var_g, right_shuffled_var) + right_shuffled_str = right_shuffled_renderer.display(data) + if self.config.showLegend: + shuffled_seq_header = left_shuffled_renderer.legend() + else: + shuffled_seq_header = '' + if left_shuffled_str != right_shuffled_str: + fully_justified_renderer = ShuffledVariant(self.config, data.strand, var_g, fully_justified_var) + fully_justified_str = fully_justified_renderer.display(data) - fully_justified_str = self._display_shuffled_range(sv, data, fully_justified_var) - var_str += regi + fully_justified_str + "\n" + #var_str += shuffled_seq_header + left_shuffled_str + "\n" + #var_str += shuffled_seq_header + right_shuffled_str + "\n" + var_str += shuffled_seq_header + fully_justified_str + "\n" else: - var_str += regi + left_shuffled_str + "\n" - - protein_str = self._display_protein_str(data) - if protein_str: - var_str += protein_str + "\n" - - transcript_str = self._display_transcript_str(data) - if transcript_str: - var_str += transcript_str + "\n" - - c_pos_count_str = self._display_c_pos(data) - c_pos_str = self._display_c_pos_ruler(data) - if c_pos_str: - var_str += c_pos_str + "\n" - if c_pos_count_str: - var_str += c_pos_count_str + "\n" + var_str += shuffled_seq_header + left_shuffled_str + "\n" + + renderers_cls = [ProtSeqRenderer, TxAligRenderer ,TxMappingRenderer, TxRulerRenderer] + for cls in renderers_cls: + renderer = cls(self.config, data.strand) + + d = '' + if self.config.showLegend: + d += renderer.legend() + str_results = renderer.display(data) + + if str_results: + var_str += d + str_results + "\n" + if left_shuffled_str != right_shuffled_str: # TODO: detect repeats? diff --git a/src/hgvs/shell.py b/src/hgvs/shell.py index 96b430a1..c81ea02d 100755 --- a/src/hgvs/shell.py +++ b/src/hgvs/shell.py @@ -79,6 +79,7 @@ def shell(): validator, variant_mapper, vm, + pretty, ) from hgvs.utils.context import variant_context_w_alignment # noqa diff --git a/tests/data/cache-py3.hdp b/tests/data/cache-py3.hdp index 54d2f9a632400c42075d85d222126ba6b92ebc72..f087b249457769b59a8e3267b90432297b5d1763 100644 GIT binary patch literal 725622 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≶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 qrRziSzfr$xEFJBLu@c#kV>wj_p literal 924046 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�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%0A|-{DYM3NjXEM%qCzC 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 DJF x3""" @@ -413,7 +417,7 @@ def test_ref_disagree(self): + "chrom pos : | . | . | . | . |\n" + "seq -> : TCTCTGGAGCCCCTGACTTCTGAGATGCACGCCCCTGGGGA\n" + "tx ref dif: X \n" - + "region : T \n" + + "region : = \n" + "aa seq <- : ArgGlnLeuGlyGlnSerGlyLeuHisValGlyArgProVa\n" + "tx seq <- : AGAGACCTCGGGGACTGAAGGCTCTACGTGCGGGGACCCCT\n" + "tx pos : . | . | . | . | \n" @@ -445,7 +449,7 @@ def test_ref_disagree_ref_ins(self): "chrom pos : | . | . | . | . | . | . | . | . | . | \n" "seq -> : CGACTGCCCAGAGAGCTGCTGCGAGCCCCCCTGCTGCGCCCCCAGCTGCTGCGCCCCGGCCCCCTGCCTGAGCCTGGTCTGCACCCCAGTGAGCCGT\n" "tx ref dif: IIIIIIIIIIIIIII XX \n" - "region : =-------------------------= \n" + "region : x-------------------------x \n" "aa seq -> : pAspCysProGluSerCysCysGluProPr---------------oCysCysAlaProAlaProCysLeuSerLeuValCysThrProValSerTyr\n" "tx seq -> : CGACTGCCCAGAGAGCTGCTGCGAGCCCCC---------------CTGCTGCGCCCCGGCCCCCTGCCTGAGCCTGGTCTGCACCCCAGTGAGCTAT\n" "tx pos : . | . | . | . | . | . | . | . | . \n" @@ -498,8 +502,6 @@ def test_exon_boundary_overlap_forward_strand(self): print(result) - assert False - def test_ruler(self): """Test the ruler display option turned on.""" hgvs_c = "NM_001111.4:c.298G>A" @@ -520,7 +522,7 @@ def test_ruler(self): + "| . | . | . | . |\n" + "TCTCTGGAGCCCCTGACTTCTGAGATGCACGCCCCTGGGGA\n" + " X \n" - + " T \n" + + " = \n" + "ArgGlnLeuGlyGlnSerGlyLeuHisValGlyArgProVa\n" + "AGAGACCTCGGGGACTGAAGGCTCTACGTGCGGGGACCCCT\n" + " . | . | . | . | \n" @@ -528,3 +530,15 @@ def test_ruler(self): ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) + + def test_rna_coding(self): + """ a rna coding transcript.""" + hgvs_n = 'NR_146230.2:n.10G>A' + var_n = self.hp.parse(hgvs_n) + result = self.pp.display(var_n) + print(result) + expected_str = ( + + ) + for r, e in zip(result, expected_str): + self.assertEqual(e, r) From 2f7069b9404bb0dcd42e1ee2e94132dd735a08b8 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Fri, 31 May 2024 23:36:21 -0700 Subject: [PATCH 06/31] feat(pretty print): feat(pretty print): improvements for RNA coding transcripts, new option to show reverse-chrom strand sequence. --- src/hgvs/pretty/models.py | 3 +- .../renderer/chrom_seq_reverse_renderer.py | 38 ++++++++++++++++++ src/hgvs/pretty/renderer/tx_pos.py | 10 ++++- src/hgvs/pretty_print.py | 14 +++++-- tests/data/cache-py3.hdp | Bin 725622 -> 738928 bytes tests/test_pretty_print.py | 22 ++++++++-- 6 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 src/hgvs/pretty/renderer/chrom_seq_reverse_renderer.py diff --git a/src/hgvs/pretty/models.py b/src/hgvs/pretty/models.py index f283978e..621f0755 100644 --- a/src/hgvs/pretty/models.py +++ b/src/hgvs/pretty/models.py @@ -100,4 +100,5 @@ class PrettyConfig: useColor: bool = False showLegend: bool = True infer_hgvs_c: bool = True - all:bool = False + all:bool = False # print all possible hgvs_c (for all UTA transcripts) + show_reverse_strand:bool = False # show the reverse strand sequence for the chromosome diff --git a/src/hgvs/pretty/renderer/chrom_seq_reverse_renderer.py b/src/hgvs/pretty/renderer/chrom_seq_reverse_renderer.py new file mode 100644 index 00000000..6de33b99 --- /dev/null +++ b/src/hgvs/pretty/renderer/chrom_seq_reverse_renderer.py @@ -0,0 +1,38 @@ +from hgvs.pretty.models import VariantData +from hgvs.pretty.renderer.renderer import BasicRenderer +from bioutils.sequences import reverse_complement + + +class ChromReverseSeqRendered(BasicRenderer): + + def legend(self)->str: + return "seq <- : " + + def display(self, data:VariantData)->str: + """colors the ref sequences with adenine (A, green), thymine (T, red), cytosine (C, yellow), and guanine (G, blue)""" + from hgvs.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW + + var_seq = "" + for p in data.position_details: + c = reverse_complement(p.ref) + + if not c: + var_seq += "." + continue + + if self.config.useColor: + if c == "A": + var_seq += TGREEN + elif c == "T": + var_seq += TRED + elif c == "C": + var_seq += TYELLOW + elif c == "G": + var_seq += TBLUE + + var_seq += c + + if self.config.useColor: + var_seq += ENDC + + return var_seq diff --git a/src/hgvs/pretty/renderer/tx_pos.py b/src/hgvs/pretty/renderer/tx_pos.py index 9bbe19d0..6ff222c8 100644 --- a/src/hgvs/pretty/renderer/tx_pos.py +++ b/src/hgvs/pretty/renderer/tx_pos.py @@ -13,6 +13,8 @@ def display(self, data: VariantData) -> str: """show the position of the transcript seq""" var_str = "" + rna_coding = data.var_c_or_n.ac.startswith("NR_") + count = -1 for pdata in data.position_details: count += 1 @@ -21,6 +23,10 @@ def display(self, data: VariantData) -> str: continue c_pos = pdata.c_pos + interval = pdata.c_interval + if rna_coding: + c_pos = pdata.n_pos + interval = pdata.n_interval if c_pos is None: var_str += " " @@ -32,11 +38,11 @@ def display(self, data: VariantData) -> str: if (c_pos + 1) % 10 == 0: # if pdata.c_interval.start.datum == Datum.CDS_END: # var_str += "*" - var_str += f"{pdata.c_interval} " + var_str += f"{interval} " continue elif c_pos == 0: - var_str += f"{pdata.c_interval} " + var_str += f"{interval} " continue var_str += " " diff --git a/src/hgvs/pretty_print.py b/src/hgvs/pretty_print.py index d4e46292..90be7bb8 100644 --- a/src/hgvs/pretty_print.py +++ b/src/hgvs/pretty_print.py @@ -6,6 +6,7 @@ from hgvs.pretty.datacompiler import DataCompiler from hgvs.pretty.models import PrettyConfig from hgvs.pretty.renderer.chrom_seq_renderer import ChromSeqRendered +from hgvs.pretty.renderer.chrom_seq_reverse_renderer import ChromReverseSeqRendered from hgvs.pretty.renderer.pos_info import ChrPositionInfo from hgvs.pretty.renderer.prot_seq_renderer import ProtSeqRenderer from hgvs.pretty.renderer.ruler import ChrRuler @@ -42,7 +43,8 @@ def __init__( useColor=False, showLegend=True, infer_hgvs_c=True, - all=False + all=False, + show_reverse_strand = False ): """ :param hdp: HGVS Data Provider Interface-compliant instance @@ -63,7 +65,8 @@ def __init__( useColor, showLegend, infer_hgvs_c, - all + all, + show_reverse_strand ) @@ -198,7 +201,12 @@ def create_repre(self, var_g:SequenceVariant, var_c_or_n:SequenceVariant, displ var_c_print = str(var_c_or_n) var_str += head + var_c_print + "\n" - renderer_cls= [ChrPositionInfo,ChrRuler , ChromSeqRendered, TxRefDisagreeRenderer] + renderer_cls= [ChrPositionInfo,ChrRuler , ChromSeqRendered] + + if self.config.show_reverse_strand: + renderer_cls.append(ChromReverseSeqRendered) + + renderer_cls.append(TxRefDisagreeRenderer) renderers = [] for cls in renderer_cls: diff --git a/tests/data/cache-py3.hdp b/tests/data/cache-py3.hdp index f087b249457769b59a8e3267b90432297b5d1763..517a834baa20920b0b23399ebe0864f566d5d6b9 100644 GIT binary patch delta 12320 zcmZ{qU5s626^74Dr!Ae9#L^#-8YmJH5pCLmR!Af`nV6Hz$utGd#YBU(G!>Wx6k0?< zb2N@olhLRQ?r>pHBNrtaL#h-njF=cq{D~C9g%|!*5jFT514K;F=XuvY)0s})bIv~B z{?=OW`>yx<_U_Dqm%sj&e=NN9WAh{Pqw{O#$L810ubW>#f8P9tZ;#LJSl8){bUK}8 z&l4Lyd-BB{lF{{7QGe9zj>^p%%x>P#QMVP9u@<4d=7rmy(X4V@#OTD)oTmc=*CeOSEv zJw4#*Bb=uH%hBufIy1{L=RQj4|0A9}{q%Y3N1E3j8C&0+JU)K@j)}$TU%&a)(dplC zLU5Q6q#||Dna`$5M&l^TBx@6z3J;R*e4s+%j5|4cP)`ih#9_wKTgAs*x?_}nKn1VS}jj|=A3B@+ovWc z`(2L7sj12C| zpO&FFKRCMP;tOk4PAm9#%Y$FowC3V=2PF9WWx?e;pk3DNq<^i-p=0f*Z%-)Xvb(g> zPufnpesxvl_OoiNoa*+6nZSHz({0)P-5a`JC zs?L|MNYaA!$||f2&(XSCR_I9k|315Rz!qESSv9euH227A=h&Rf%|4fL5$v+A*lD*P zY`6ZCvs+o1B)zmOsk!s`_~jcHHtwG}xM%Ox6BAcAr>-B_H>mNhs~49=yc$1t&!M}H z9=_*}4;)+U9h+IZiKkhw-@CE*<=)ZW=XwwIp6)&0`(^K2y)X4n_73$P?!BvbOYdvF z2YQElU!0j)e`I0Z)H@ICnb^I!^eFr}`(^GKPp3EqbMD!7UjJhD!OfRmvf-iiSMe<8 zc<}D;-Z=OPk(tp=GwU|Z{gmOE$D7ZuF-%(8O=c!%?ldF9vmd&k)9Ls7Y}qcaKCkXn zcdDN=Q#@SyQ#@PvVN)~ia>cvP4=;n=0cok8j9PL@~_03(f%AUNyUv=nd(E~{Wn^wh zxb@*hhkdC>1>s~;eh^|$uwAdf#Rb`j66d8o`U=Vv{a3U0SBNI6NigtD0ElMYGiTDG zSS?u}@x?oY!Z`&-lt%#|aG#Ly$^YMHJb2m)Guf!WH3&kLg5B9DLXu`gDL^bitO)Pl;>K zE0;wkwC3_Lh)-N5Da2M2$|)$!1{RLkAiCZuPF+yy87zD#h4MBbOfi}y+i`;CDR3pH z^88Cpo%A84@u$4S5A8sqIq;F8J${%TI=d6nl{8<|w9=@mQ=`U+1;acP4;Y!`i=ycD zS*_Qhs~Krz0?n-;da%R=iE{*$JT0(i=dy&BmMpa>$a=)P{z`EpnGBY^%%jC(kV&9A z_pvnD7=pM01fr5*WHD>KUic99(2Jx=NYn;o_F)fY?!TI(%#a3?fRcDM)J3l1C|9H* zx&)s$lR=fWEVM%?>@AICKS-aB44Uc%|Nb}zRc^=dGhjwW&gzuL6XygNknNevDDg96 zb7VrQT`&+#AYaz;nRyiQ{38LONRiJA^7%23qg}`<7e#CFj(L*AbWC!%iQ2VUob11r ziwQ&04;QvF~?H3Fw7t|oOsM)44*zgIqy2zjQNpUq6uQSM|dME&tt0$Sd!Z}qO-*PfqljO*6?65 z$()OTAP$IOgpbOYqK-mw&R4Gah++hp8EZrB8o6lMIKfmb1MdG2JLe3amWind#zH$m zZ_G0AMWPbQ;fgt&*QZ`wF6}&R3M2NuVaQ~^47a4L#ff#@aRJ%B`HP2bGOs*bt zk$r}mVge*MEK@NnkeN^sVp2^>_TN7b&frByoK}KM5b+UZ7ToI3bI^fCxEWgYdg>S| zSaJ{9t9uJ{_?50DP$lZ`AIf6RtsB9vQo5v4QNDeJm7r`~)nRCc|$&C}mK3OH(X>$da&#@Vn8i^yZaDaKlVg+Mtl!dQ2KCItRl> zbzOhzu#l*r+G6YOUWH|q)RcJ?8RTSFidA?{q`Ko6^ZfUssDxCU2#zqD(wZZSs6@JF zk%BYRe!2z@t&do9ixW5^76!G}$o1y;&(Oz@+Hq+_w;X6wMo0=m^wey~G?qj-_0HWP zi~STCzDZNTRt_I&OhbJBf+=4LL3bTDKxAw?rLq*0%YBB89j798XF{X2by z2I&P$1h^o(Rt%z_9@SEzb|^}loc07D9RMZfkkVbk%Ne8MIqduIpH@I^8^*k>N!P$C zXK*at1so1NBp5l$byakgL{}+yTe)nxHe|_|r;Y@(oMZn(R?iyAxd6p8dE{y%sR1|S zcqyli=TVaexT}d0SI4kPJ|PlRIH#4b2>h4{k&#H_BXxo}R~UDqtu*I7TCZ61GZ zY}=uBa%IZOZ|I(yd1cgZ=)Tjtzjvbdm6I@o?;9cEQq&`)C;TpJ=wi(xqG4z2@5a zq~O92?+3hh5&Xi1y+$GLV3H1ni*`l;Il*y;Es#V3Lq=8ht3k?A>6#V>+lv)qxs>Hy zzy|c=NBHyNYBbL5+Nsg zghd)(rmMwF;J8Q!0$J~LigThuLSWR+z@e{p09QDC;7ei-sQC;9S6=8XhYi?{A-SWl zkQ2`gIc{7KMM}p65jq^lOMyMy46Z+z;Odd~XF;c6Y9obFFj6vITSQ>t zl0oYp6sVe(&4Opihv5Uilx01Q!|DdBVxdc;NJt?%l$8N|Ob68-vS>1BvC5^Zz8JV2 zQK&&O`3sLNs*jKcTM(Y&62qM#IR=E>xmKROg)4ErsqGh3dNALV_N8FCY{{yxgHt0g=~h4a+?d)2T|uV5oEaFEQ|ev+jLwLQ&+W&ImXg>A!f;!$5%iU zU>0cs9yWmrG2t-?1Q%roL(NerxN(pE+hQ+GuNHNiG>EJXq5aa2S^8*??zwnDp=uOl z1KBR7ljC%@q@rsJX$gty<_}(yIq6lla6BeY)-+;K#w6-U(}BQ_5GSCARnEIrPG? z^Gc0lJbx9}a`8zg1Z1#!cu>N89Q0HOf-@;&357G{Ql3{lXUm zYtI?C;gltrYYB@hbs8sT=Ji0hs5LJ!*ScEN?zE>ywR;nkG|KqG!Bk!bg<+6}z;Vu2 zGE2~9@XEEcowUJx$V?i}z3)oLoa3ygr4(76#vRi1V ze77)y_+*$$|G4)h5y-s$~W*#zm-fnNh z!!4l^8qE&i?V7ovbVq8zMC5asqR01VBk3XI*QAh}lGA$^>Lh}y@0mUK&TU4}0 z0AZqhG|`$7NI@f(X9k!O4Eu#7o=3S#N${6q+H#e-)(AB@Wo%e0jfu(l< zu*4HLc-*(UEK#~jr;$@owvGlcB^6t96eX5jXc83zq1#dAze(NEyp3df>13Hq4GFG_uh>pp^6`( zZb`7v6c3=t?WXF00M0;w5ut^l&ZOQ7(|lmMoQc1;6HhDuaWuIWn7(#sPu3t_C%z{gTji z*Hen4q@0JL8aESpK$bUNb_J;jvYp;vh|=0ONAF= zQKU%Mz_xR4udQ}-1k7!08_p3oP=iePKWUh(0MDewU=oF-LaM^e7$|0wrdZ5Ch+@T# z8M4^2|ELr9o@n`wja@ B{>lIV delta 50 zcmey+qx)@%PD2Z03sVbo3rh=Y3tJ0&3r7p*7On_sHnHyvKrr2K8)xtK*V0^_- : TACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" + + "seq <- : ATGGAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + "region : T \n" + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" @@ -535,10 +538,23 @@ def test_rna_coding(self): """ a rna coding transcript.""" hgvs_n = 'NR_146230.2:n.10G>A' var_n = self.hp.parse(hgvs_n) - result = self.pp.display(var_n) + + pp = PrettyPrint(self.hdp, show_reverse_strand=True) + result = pp.display(var_n) print(result) expected_str = ( - + "hgvs : NC_000001.10:g.167905930G>A\n" + + "hgvs : NR_146230.2:n.10G>A\n" + + " : 167,905,910 167,905,930 167,905,950\n" + + "chrom pos : | . | . | . | . |\n" + + "seq -> : TGCTGATCTTTGGATGTTCTGGTTAGTCTAAGAAGGAGAGT\n" + + "seq <- : ACGACTAGAAACCTACAAGACCAATCAGATTCTTCCTCTCA\n" + + "region : A \n" + + "aa seq -> : \n" + + "tx seq -> : GATGTTCTGGTTAGTCTAAGAAGGAGAGT\n" + + "tx pos : . | . | . |\n" + + " : 10 20 30\n" ) for r, e in zip(result, expected_str): self.assertEqual(e, r) + From 27ae2cd3218d04aaddb1049eb7ff0dee1a2c7aec Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sun, 2 Jun 2024 23:54:48 -0700 Subject: [PATCH 07/31] feat(repeat-detection): using fully justified representation. --- src/hgvs/repeats.py | 67 +++++++++++++++++++++++++++++++++++++++ tests/test_repeats.py | 73 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 src/hgvs/repeats.py create mode 100644 tests/test_repeats.py diff --git a/src/hgvs/repeats.py b/src/hgvs/repeats.py new file mode 100644 index 00000000..3c4379eb --- /dev/null +++ b/src/hgvs/repeats.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +""" A class to manage conversion of SequenceVariants to repeat representation""" + +import re +from typing import Optional + +from hgvs.dataproviders.interface import Interface +from hgvs.pretty.datacompiler import DataCompiler +from hgvs.pretty.models import PrettyConfig, VariantCoords +from hgvs.sequencevariant import SequenceVariant + + +def count_pattern_occurence(pattern, string): + """Counts how often a pattern can be found in the string.""" + matches = re.findall(pattern, string) + return len(matches) + + +class RepeatAnalyser: + + def __init__(self, hdp: Interface, var_g: SequenceVariant) -> None: + + config = PrettyConfig(hdp, None, None) + dc = DataCompiler(config) + + fs = dc.get_shuffled_variant(var_g, 0) + + self.is_repeat = False + self.ref_count = 0 + self.alt_count = 0 + self.ref_str = fs.ref + self.alt_str = fs.alt + + self.repeat_unit = self._get_repeat_unit(fs) + if self.repeat_unit is None: + return + + self.is_repeat = True + + self.ref_count = count_pattern_occurence(self.repeat_unit, fs.ref) + self.alt_count = count_pattern_occurence(self.repeat_unit, fs.alt) + self.ref_str = f"{self.repeat_unit}[{self.ref_count}]" + self.alt_str = f"{self.repeat_unit}[{self.alt_count}]" + + def __repr__(self): + return f"{self.ref_str}>{self.alt_str}" + + def _get_repeat_unit(self, fs: VariantCoords) -> Optional[str]: + """Takes fully justified coordiantes and tries to detect a repeat in them.""" + # analyze for repeat: + if len(fs.ref) == len(fs.alt): + # seems we cant shuffle. is an SVN or delins + return None + + if len(fs.alt) > 0: + if fs.alt in fs.ref: + if fs.ref.startswith(fs.alt): + d = fs.ref[len(fs.alt) :] + if count_pattern_occurence(d, fs.ref) > 1: + return d + elif fs.ref in fs.alt: + if fs.alt.startswith(fs.ref): + d = fs.alt[len(fs.ref) :] + if count_pattern_occurence(d, fs.ref) > 1: + return d + + return None diff --git a/tests/test_repeats.py b/tests/test_repeats.py new file mode 100644 index 00000000..62bbeb4f --- /dev/null +++ b/tests/test_repeats.py @@ -0,0 +1,73 @@ +import os +from unittest import TestCase + +import pytest +from parameterized import parameterized +from support import CACHE + +import hgvs +from hgvs.parser import Parser +from hgvs.repeats import RepeatAnalyser +from hgvs.variantmapper import VariantMapper + + +class TestRepeats(TestCase): + @classmethod + def setUpClass(cls): + cls.hdp = hgvs.dataproviders.uta.connect( + mode=os.environ.get("HGVS_CACHE_MODE", "run"), cache=CACHE + ) + cls.vm = hgvs.variantmapper.VariantMapper(cls.hdp) + cls.hp = hgvs.parser.Parser() + + @parameterized.expand( + [ + ("NC_000019.10:g.45770205del", True, "C", 6, 5, "C[6]>C[5]"), + ("NC_000019.10:g.45770205insC", True, "C", 6, 7, "C[6]>C[7]"), + ("NC_000019.10:g.45770206del", False, None, 0, 0, "A>"), + ("NC_000007.13:g.36561662dup", True, "C", 2, 3, "C[2]>C[3]"), + ] + ) + def test_homopolymer(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, s): + # hgvs_g = "NC_000019.10:g.45770205del" + + var_g = self.hp.parse_hgvs_variant(hgvs_g) + ra = RepeatAnalyser(self.hdp, var_g) + self.assertEqual(is_repeat, ra.is_repeat) + self.assertEqual(repeat_unit, ra.repeat_unit) + self.assertEqual(ref_count, ra.ref_count) + self.assertEqual(alt_count, ra.alt_count) + self.assertEqual(s, str(ra)) + + @parameterized.expand( + [ + ( + "NC_000021.8:g.46020668_46020682del", + False, + None, + 0, + 0, + "CTGCTGCGCCCCCAGCTGCTGCGCCCC>CTGCTGCGCCCC", + ), + ( + "NC_000012.11:g.33049660_33049680dup", + False, + None, + 0, + 0, + "GGGGGCTGCCATGGGGCCGGTGGGGGC>GGGGGCTGCCATGGGGCCGGTGGGGGCTGCCATGGGGCCGGTGGGGGC", + ), + ("NC_000005.10:g.123346517_123346518insATTA", True, "ATTA", 2, 3, "ATTA[2]>ATTA[3]"), + ("NC_000005.10:g.123346522_123346525dup", True, "ATTA", 2, 3, "ATTA[2]>ATTA[3]"), + ] + ) + def test_repeats(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, s): + # hgvs_g = "NC_000019.10:g.45770205del" + + var_g = self.hp.parse_hgvs_variant(hgvs_g) + ra = RepeatAnalyser(self.hdp, var_g) + self.assertEqual(is_repeat, ra.is_repeat) + self.assertEqual(repeat_unit, ra.repeat_unit) + self.assertEqual(ref_count, ra.ref_count) + self.assertEqual(alt_count, ra.alt_count) + self.assertEqual(s, str(ra)) From b5b3d64a9fb21da1ba6aa6aeb484c6249955c96c Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sun, 2 Jun 2024 23:55:28 -0700 Subject: [PATCH 08/31] feat(repeat-detection): using fully justified representation. --- pyproject.toml | 7 +- src/hgvs/pretty/datacompiler.py | 178 +++++++++++++++++++++----------- src/hgvs/pretty/models.py | 14 +-- src/hgvs/pretty_print.py | 85 ++++++++------- 4 files changed, 176 insertions(+), 108 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 840870aa..9fd56f8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ keywords = [ "hgvs", "variation" ] -dynamic = ["version", "optional-dependencies"] +dynamic = ["version"] dependencies=[ "attrs >= 17.4.0", # https://github.com/biocommons/hgvs/issues/473 @@ -44,6 +44,11 @@ dependencies=[ "six", ] +[project.optional-dependencies] +dev = [ + "parameterized" +] + [project.urls] "Homepage" = "https://github.com/biocommons/hgvs" "Bug Tracker" = "https://github.com/biocommons/hgvs/issues" diff --git a/src/hgvs/pretty/datacompiler.py b/src/hgvs/pretty/datacompiler.py index ce03facc..58083b8d 100644 --- a/src/hgvs/pretty/datacompiler.py +++ b/src/hgvs/pretty/datacompiler.py @@ -1,4 +1,4 @@ -from typing import Tuple +from typing import Optional, Tuple from bioutils.normalize import normalize from bioutils.sequences import aa1_to_aa3_lut @@ -16,14 +16,14 @@ ) from hgvs.sequencevariant import SequenceVariant from hgvs.utils.reftranscriptdata import RefTranscriptData -from typing import Optional + class DataCompiler: def __init__(self, config: PrettyConfig): self.config = config - def _get_shuffled_variant(self, var_g: SequenceVariant, direction: int) -> VariantCoords: + def get_shuffled_variant(self, var_g: SequenceVariant, direction: int) -> VariantCoords: # get shuffled representation: if direction == 5: @@ -91,26 +91,32 @@ def get_position_and_state( return start, end, ref, alt - def _get_exon_nr(self, tx_exons, genomic_pos)-> Tuple[int, str]: + def _get_exon_nr(self, tx_exons, genomic_pos) -> Tuple[int, str]: i = -1 for ex in tx_exons: - i+=1 - exon_nr = ex['ord'] + 1 - - if ex ['alt_start_i'] < genomic_pos and ex['alt_end_i'] >= genomic_pos: - return (exon_nr, 'exon') - + i += 1 + exon_nr = ex["ord"] + 1 + + if ex["alt_start_i"] < genomic_pos and ex["alt_end_i"] >= genomic_pos: + return (exon_nr, "exon") + if i > 0: - if ex['alt_strand'] > 0: - if tx_exons[i - 1]["alt_end_i"] < genomic_pos and tx_exons[i]["alt_start_i"] >= genomic_pos: - return (exon_nr, 'intron') + if ex["alt_strand"] > 0: + if ( + tx_exons[i - 1]["alt_end_i"] < genomic_pos + and tx_exons[i]["alt_start_i"] >= genomic_pos + ): + return (exon_nr, "intron") else: - if tx_exons[i]["alt_start_i"] < genomic_pos and tx_exons[i-1]["alt_end_i"] >= genomic_pos: - return (i, 'intron') + if ( + tx_exons[i]["alt_start_i"] < genomic_pos + and tx_exons[i - 1]["alt_end_i"] >= genomic_pos + ): + return (i, "intron") + + return (-1, "no-overlap") - return (-1, 'no-overlap') - def _get_prot_alt( self, tx_ac: str, strand: int, reference_data, ref_base, c_interval ) -> ProteinData: @@ -161,9 +167,9 @@ def data( start, end, ref, alt = self.get_position_and_state(var_g) - ls = self._get_shuffled_variant(var_g, 5) - rs = self._get_shuffled_variant(var_g, 3) - fs = self._get_shuffled_variant(var_g, 0) + ls = self.get_shuffled_variant(var_g, 5) + rs = self.get_shuffled_variant(var_g, 3) + fs = self.get_shuffled_variant(var_g, 0) if ls.start < start: start = ls.start @@ -183,38 +189,39 @@ def data( if var_c_or_n is not None: tx_ac = var_c_or_n.ac else: - tx_ac = '' # can't show transcript , since there is none. - + tx_ac = "" # can't show transcript , since there is none. + alt_ac = var_g.ac - alt_aln_method = 'splign' - + alt_aln_method = "splign" + if tx_ac: tx_exons = self.config.hdp.get_tx_exons(tx_ac, alt_ac, alt_aln_method) tx_exons = sorted(tx_exons, key=lambda e: e["ord"]) else: tx_exons = [] - + chrom_seq = self.config.hdp.get_seq(var_g.ac) disp_seq = chrom_seq[seq_start:seq_end] - - if tx_ac : + + if tx_ac: tx_seq = self.config.hdp.get_seq(tx_ac) if self.config.default_assembly == "GRCh37": am = self.config.am37 else: am = self.config.am38 - mapper = am._fetch_AlignmentMapper(tx_ac=tx_ac, alt_ac=var_g.ac, alt_aln_method="splign") + mapper = am._fetch_AlignmentMapper( + tx_ac=tx_ac, alt_ac=var_g.ac, alt_aln_method="splign" + ) else: tx_seq = "" mapper = None - #print(tx_seq) + # print(tx_seq) - # we don't know the protein ac, get it looked up: pro_ac = None - if var_c_or_n and var_c_or_n.type == 'c': + if var_c_or_n and var_c_or_n.type == "c": reference_data = RefTranscriptData(self.config.hdp, tx_ac, pro_ac) else: reference_data = None @@ -227,9 +234,11 @@ def data( exon_nr, feat = self._get_exon_nr(tx_exons, chromosome_pos) - pdata = PositionDetail(chromosome_pos=chromosome_pos, exon_nr=exon_nr, variant_feature=feat) + pdata = PositionDetail( + chromosome_pos=chromosome_pos, exon_nr=exon_nr, variant_feature=feat + ) position_details.append(pdata) - + pdata.ref = chrom_seq[chromosome_pos - 1] if not mapper: @@ -250,18 +259,29 @@ def data( if prev_mapped_pos: while mapped_pos - prev_mapped_pos > 1: - + prev_mapped_pos = prev_mapped_pos + 1 - + pdata.mapped_pos = prev_mapped_pos # a region in ref that has been deleted. Fill in gaps. - self._backfill_gap_in_ref(var_c_or_n, tx_seq, tx_exons, mapper, reference_data, pdata, cig, prev_c_pos +1, prev_n_pos+1) + self._backfill_gap_in_ref( + var_c_or_n, + tx_seq, + tx_exons, + mapper, + reference_data, + pdata, + cig, + prev_c_pos + 1, + prev_n_pos + 1, + ) - exon_nr, feat = self._get_exon_nr(tx_exons, chromosome_pos) - #prev_mapped_pos += 1 - pdata = PositionDetail(chromosome_pos=chromosome_pos, exon_nr=exon_nr, variant_feature=feat) + # prev_mapped_pos += 1 + pdata = PositionDetail( + chromosome_pos=chromosome_pos, exon_nr=exon_nr, variant_feature=feat + ) position_details.append(pdata) pdata.alignment_pos = gr @@ -277,17 +297,16 @@ def data( prev_c_pos -= 1 prev_n_pos -= 1 - - prev_mapped_pos = mapped_pos g_interval = hgvs.location.Interval( - start=hgvs.location.SimplePosition(chromosome_pos), end=hgvs.location.SimplePosition(chromosome_pos) + start=hgvs.location.SimplePosition(chromosome_pos), + end=hgvs.location.SimplePosition(chromosome_pos), ) try: n_interval = mapper.g_to_n(g_interval) - if var_c_or_n.type == 'c': + if var_c_or_n.type == "c": c_interval = mapper.n_to_c(n_interval) else: c_interval = None @@ -302,12 +321,20 @@ def data( prev_c_pos = c_pos else: prev_c_pos = -1 - - n_pos = int(n_interval.start.base) - 1 + + n_pos = int(n_interval.start.base) - 1 prev_n_pos = n_pos - + self._populate_with_n_c( - var_c_or_n, tx_seq, tx_exons, mapper, reference_data, pdata, cig, n_interval, c_interval + var_c_or_n, + tx_seq, + tx_exons, + mapper, + reference_data, + pdata, + cig, + n_interval, + c_interval, ) # print(position_details[0].get_header()) @@ -315,16 +342,38 @@ def data( # print(f"{p}\t{p.protein_data}\t") vd = VariantData( - seq_start, seq_end, ls, rs, fs, disp_seq, tx_seq, mapper, var_g, mapper.strand, var_c_or_n, position_details + seq_start, + seq_end, + ls, + rs, + fs, + disp_seq, + tx_seq, + mapper, + var_g, + mapper.strand, + var_c_or_n, + position_details, ) return vd - def _backfill_gap_in_ref(self, var_c_or_n, tx_seq, tx_exons, mapper, reference_data, pdata, cig, prev_c_pos, prev_n_pos ): - + def _backfill_gap_in_ref( + self, + var_c_or_n, + tx_seq, + tx_exons, + mapper, + reference_data, + pdata, + cig, + prev_c_pos, + prev_n_pos, + ): + pdata.chromosome_pos = None pdata.ref = None - + if mapper.strand > 0: pdata.c_pos = prev_c_pos + 1 pdata.n_pos = prev_n_pos + 1 @@ -336,20 +385,27 @@ def _backfill_gap_in_ref(self, var_c_or_n, tx_seq, tx_exons, mapper, reference_d pdata.tx = tx_seq[pdata.n_pos] n_interval = hgvs.location.Interval( - start=hgvs.location.BaseOffsetPosition(base=pdata.n_pos, offset=0), - end=hgvs.location.BaseOffsetPosition(base=pdata.n_pos, offset=0), - ) + start=hgvs.location.BaseOffsetPosition(base=pdata.n_pos, offset=0), + end=hgvs.location.BaseOffsetPosition(base=pdata.n_pos, offset=0), + ) c_interval = mapper.n_to_c(n_interval, strict_bounds=False) pdata.c_interval = c_interval self._populate_with_n_c( - var_c_or_n, tx_seq, tx_exons, mapper, reference_data, pdata, cig, n_interval, c_interval - ) - - + var_c_or_n, tx_seq, tx_exons, mapper, reference_data, pdata, cig, n_interval, c_interval + ) def _populate_with_n_c( - self, var_c_or_n, tx_seq, tx_exons, mapper, reference_data, pdata, cig, n_interval, c_interval + self, + var_c_or_n, + tx_seq, + tx_exons, + mapper, + reference_data, + pdata, + cig, + n_interval, + c_interval, ): n_pos = int(n_interval.start.base) - 1 @@ -364,11 +420,11 @@ def _populate_with_n_c( tx_ac = var_c_or_n.ac pdata.n_pos = n_pos - + pdata.tx = tx_seq[pdata.n_pos] coding = True - if var_c_or_n.type == 'n': # rna coding can't be in protein space + if var_c_or_n.type == "n": # rna coding can't be in protein space coding = False if cig == "N" or pdata.c_offset != 0: coding = False diff --git a/src/hgvs/pretty/models.py b/src/hgvs/pretty/models.py index 621f0755..67b7f80f 100644 --- a/src/hgvs/pretty/models.py +++ b/src/hgvs/pretty/models.py @@ -1,11 +1,12 @@ from dataclasses import dataclass +from typing import List import hgvs from hgvs.alignmentmapper import AlignmentMapper from hgvs.assemblymapper import AssemblyMapper from hgvs.location import Interval from hgvs.sequencevariant import SequenceVariant -from typing import List + @dataclass(eq=True, repr=True, frozen=True, order=True) class VariantCoords: @@ -80,11 +81,10 @@ class VariantData: tx_seq: str alignmentmapper: AlignmentMapper var_g: SequenceVariant - strand:int + strand: int var_c_or_n: SequenceVariant = None position_details: List[PositionDetail] = None all: bool = False - @dataclass @@ -94,11 +94,11 @@ class PrettyConfig: hdp: hgvs.dataproviders.interface.Interface am37: AssemblyMapper am38: AssemblyMapper - padding_left: int - padding_right: int + padding_left: int = 20 + padding_right: int = 20 default_assembly: str = "GRCh37" useColor: bool = False showLegend: bool = True infer_hgvs_c: bool = True - all:bool = False # print all possible hgvs_c (for all UTA transcripts) - show_reverse_strand:bool = False # show the reverse strand sequence for the chromosome + all: bool = False # print all possible hgvs_c (for all UTA transcripts) + show_reverse_strand: bool = False # show the reverse strand sequence for the chromosome diff --git a/src/hgvs/pretty_print.py b/src/hgvs/pretty_print.py index 90be7bb8..69b46fa9 100644 --- a/src/hgvs/pretty_print.py +++ b/src/hgvs/pretty_print.py @@ -1,6 +1,5 @@ - - from typing import List + import hgvs from hgvs.assemblymapper import AssemblyMapper from hgvs.pretty.datacompiler import DataCompiler @@ -44,7 +43,7 @@ def __init__( showLegend=True, infer_hgvs_c=True, all=False, - show_reverse_strand = False + show_reverse_strand=False, ): """ :param hdp: HGVS Data Provider Interface-compliant instance @@ -66,28 +65,25 @@ def __init__( showLegend, infer_hgvs_c, all, - show_reverse_strand + show_reverse_strand, ) - - def _get_assembly_mapper(self)->AssemblyMapper: + def _get_assembly_mapper(self) -> AssemblyMapper: if self.config.default_assembly == "GRCh37": am = self.config.am37 else: am = self.config.am38 - + return am - def _get_all_transcripts(self, var_g) ->List[str]: + def _get_all_transcripts(self, var_g) -> List[str]: am = self._get_assembly_mapper() transcripts = am.relevant_transcripts(var_g) return transcripts - - def _infer_hgvs_c(self, var_g: SequenceVariant, tx_ac:str=None) -> SequenceVariant: - + def _infer_hgvs_c(self, var_g: SequenceVariant, tx_ac: str = None) -> SequenceVariant: if not tx_ac: transcripts = self._get_all_transcripts(var_g) @@ -98,14 +94,12 @@ def _infer_hgvs_c(self, var_g: SequenceVariant, tx_ac:str=None) -> SequenceVaria am = self._get_assembly_mapper() - if tx_ac.startswith('NR_'): - var_n = am.g_to_n(var_g, tx_ac) + if tx_ac.startswith("NR_"): + var_n = am.g_to_n(var_g, tx_ac) return var_n var_c = am.g_to_c(var_g, tx_ac) return var_c - - def _map_to_chrom(self, sv: SequenceVariant) -> SequenceVariant: """maps a variant to chromosomal coords, if needed.""" if self.config.default_assembly == "GRCh37": @@ -136,7 +130,11 @@ def _colorize_hgvs(self, hgvs_str: str) -> str: return var_str def display( - self, sv: SequenceVariant, tx_ac: str = None, display_start: int = None, display_end: int = None + self, + sv: SequenceVariant, + tx_ac: str = None, + display_start: int = None, + display_end: int = None, ) -> str: """Takes a variant and prints the genomic context around it.""" @@ -163,19 +161,27 @@ def display( print(f"displaying {len(tx_acs)} alternative transcripts") for tx_ac in tx_acs: var_c_or_n = self._infer_hgvs_c(var_g, tx_ac) - response += self.create_repre(var_g, var_c_or_n, display_start, display_end, data_compiler) + response += self.create_repre( + var_g, var_c_or_n, display_start, display_end, data_compiler + ) response += "\n---\n" return response - else: + else: if not var_c_or_n: if self.config.infer_hgvs_c: var_c_or_n = self._infer_hgvs_c(var_g) - return self.create_repre(var_g, var_c_or_n, display_start, display_end, data_compiler) - def create_repre(self, var_g:SequenceVariant, var_c_or_n:SequenceVariant, display_start:int, display_end:int, data_compiler:DataCompiler): + def create_repre( + self, + var_g: SequenceVariant, + var_c_or_n: SequenceVariant, + display_start: int, + display_end: int, + data_compiler: DataCompiler, + ): data = data_compiler.data(var_g, var_c_or_n, display_start, display_end) left_shuffled_var = data.left_shuffled @@ -201,8 +207,8 @@ def create_repre(self, var_g:SequenceVariant, var_c_or_n:SequenceVariant, displ var_c_print = str(var_c_or_n) var_str += head + var_c_print + "\n" - renderer_cls= [ChrPositionInfo,ChrRuler , ChromSeqRendered] - + renderer_cls = [ChrPositionInfo, ChrRuler, ChromSeqRendered] + if self.config.show_reverse_strand: renderer_cls.append(ChromReverseSeqRendered) @@ -213,51 +219,52 @@ def create_repre(self, var_g:SequenceVariant, var_c_or_n:SequenceVariant, displ r = cls(self.config, data.strand) renderers.append(r) - for renderer in renderers: - d = '' + d = "" if self.config.showLegend: - d += renderer.legend() - str_results = renderer.display(data) + d += renderer.legend() + str_results = renderer.display(data) if str_results: var_str += d + str_results + "\n" - left_shuffled_renderer = ShuffledVariant(self.config, data.strand, var_g, left_shuffled_var) left_shuffled_str = left_shuffled_renderer.display(data) - right_shuffled_renderer = ShuffledVariant(self.config, data.strand, var_g, right_shuffled_var) + right_shuffled_renderer = ShuffledVariant( + self.config, data.strand, var_g, right_shuffled_var + ) right_shuffled_str = right_shuffled_renderer.display(data) if self.config.showLegend: - shuffled_seq_header = left_shuffled_renderer.legend() + shuffled_seq_header = left_shuffled_renderer.legend() else: - shuffled_seq_header = '' - + shuffled_seq_header = "" + if left_shuffled_str != right_shuffled_str: - fully_justified_renderer = ShuffledVariant(self.config, data.strand, var_g, fully_justified_var) + fully_justified_renderer = ShuffledVariant( + self.config, data.strand, var_g, fully_justified_var + ) fully_justified_str = fully_justified_renderer.display(data) - #var_str += shuffled_seq_header + left_shuffled_str + "\n" - #var_str += shuffled_seq_header + right_shuffled_str + "\n" + # var_str += shuffled_seq_header + left_shuffled_str + "\n" + # var_str += shuffled_seq_header + right_shuffled_str + "\n" var_str += shuffled_seq_header + fully_justified_str + "\n" else: var_str += shuffled_seq_header + left_shuffled_str + "\n" - renderers_cls = [ProtSeqRenderer, TxAligRenderer ,TxMappingRenderer, TxRulerRenderer] + renderers_cls = [ProtSeqRenderer, TxAligRenderer, TxMappingRenderer, TxRulerRenderer] for cls in renderers_cls: renderer = cls(self.config, data.strand) - d = '' + d = "" if self.config.showLegend: - d += renderer.legend() - str_results = renderer.display(data) + d += renderer.legend() + str_results = renderer.display(data) if str_results: var_str += d + str_results + "\n" - if left_shuffled_str != right_shuffled_str: # TODO: detect repeats? fully_justified_ref = fully_justified_var.ref From 2a72299c9c3a0d74c41c298203690c21a0c6f050 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sun, 2 Jun 2024 23:58:11 -0700 Subject: [PATCH 09/31] formatting --- .../pretty/renderer/chrom_seq_renderer.py | 4 +- .../renderer/chrom_seq_reverse_renderer.py | 7 +- src/hgvs/pretty/renderer/pos_info.py | 2 +- src/hgvs/pretty/renderer/prot_seq_renderer.py | 2 +- src/hgvs/pretty/renderer/renderer.py | 3 +- src/hgvs/pretty/renderer/ruler.py | 6 +- src/hgvs/pretty/renderer/shuffled_variant.py | 17 +-- src/hgvs/pretty/renderer/tx_alig_renderer.py | 21 ++- .../pretty/renderer/tx_mapping_renderer.py | 120 ++++++++++-------- src/hgvs/pretty/renderer/tx_pos.py | 10 +- .../renderer/tx_ref_disagree_renderer.py | 11 +- src/hgvs/shell.py | 2 +- tests/test_pretty_print.py | 30 ++--- 13 files changed, 112 insertions(+), 123 deletions(-) diff --git a/src/hgvs/pretty/renderer/chrom_seq_renderer.py b/src/hgvs/pretty/renderer/chrom_seq_renderer.py index dfd888ed..8204f518 100644 --- a/src/hgvs/pretty/renderer/chrom_seq_renderer.py +++ b/src/hgvs/pretty/renderer/chrom_seq_renderer.py @@ -4,10 +4,10 @@ class ChromSeqRendered(BasicRenderer): - def legend(self)->str: + def legend(self) -> str: return "seq -> : " - def display(self, data:VariantData)->str: + def display(self, data: VariantData) -> str: """colors the ref sequences with adenine (A, green), thymine (T, red), cytosine (C, yellow), and guanine (G, blue)""" from hgvs.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW diff --git a/src/hgvs/pretty/renderer/chrom_seq_reverse_renderer.py b/src/hgvs/pretty/renderer/chrom_seq_reverse_renderer.py index 6de33b99..9516a1c0 100644 --- a/src/hgvs/pretty/renderer/chrom_seq_reverse_renderer.py +++ b/src/hgvs/pretty/renderer/chrom_seq_reverse_renderer.py @@ -1,14 +1,15 @@ +from bioutils.sequences import reverse_complement + from hgvs.pretty.models import VariantData from hgvs.pretty.renderer.renderer import BasicRenderer -from bioutils.sequences import reverse_complement class ChromReverseSeqRendered(BasicRenderer): - def legend(self)->str: + def legend(self) -> str: return "seq <- : " - def display(self, data:VariantData)->str: + def display(self, data: VariantData) -> str: """colors the ref sequences with adenine (A, green), thymine (T, red), cytosine (C, yellow), and guanine (G, blue)""" from hgvs.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW diff --git a/src/hgvs/pretty/renderer/pos_info.py b/src/hgvs/pretty/renderer/pos_info.py index 85395c3e..3a0b8f51 100644 --- a/src/hgvs/pretty/renderer/pos_info.py +++ b/src/hgvs/pretty/renderer/pos_info.py @@ -30,4 +30,4 @@ def display(self, data: VariantData) -> str: else: var_seq += " " - return var_seq.rstrip() \ No newline at end of file + return var_seq.rstrip() diff --git a/src/hgvs/pretty/renderer/prot_seq_renderer.py b/src/hgvs/pretty/renderer/prot_seq_renderer.py index 82dabddd..046e9fad 100644 --- a/src/hgvs/pretty/renderer/prot_seq_renderer.py +++ b/src/hgvs/pretty/renderer/prot_seq_renderer.py @@ -72,4 +72,4 @@ def display(self, data: VariantData) -> str: var_str += aa_char continue - return var_str \ No newline at end of file + return var_str diff --git a/src/hgvs/pretty/renderer/renderer.py b/src/hgvs/pretty/renderer/renderer.py index a0d33d5e..eab9a816 100644 --- a/src/hgvs/pretty/renderer/renderer.py +++ b/src/hgvs/pretty/renderer/renderer.py @@ -1,7 +1,8 @@ from abc import ABC, abstractmethod + class BasicRenderer(ABC): - def __init__(self, config, orientation:int): + def __init__(self, config, orientation: int): self.config = config self.orientation = orientation diff --git a/src/hgvs/pretty/renderer/ruler.py b/src/hgvs/pretty/renderer/ruler.py index e8d3f3cb..3317f605 100644 --- a/src/hgvs/pretty/renderer/ruler.py +++ b/src/hgvs/pretty/renderer/ruler.py @@ -3,9 +3,9 @@ class ChrRuler(BasicRenderer): - + def legend(self): - """ returns the legend for this category of display""" + """returns the legend for this category of display""" return "chrom pos : " def display(self, data: VariantData) -> str: @@ -28,4 +28,4 @@ def display(self, data: VariantData) -> str: ruler += "." else: ruler += " " - return ruler \ No newline at end of file + return ruler diff --git a/src/hgvs/pretty/renderer/shuffled_variant.py b/src/hgvs/pretty/renderer/shuffled_variant.py index 74b0d735..cf652b39 100644 --- a/src/hgvs/pretty/renderer/shuffled_variant.py +++ b/src/hgvs/pretty/renderer/shuffled_variant.py @@ -1,5 +1,3 @@ - - from hgvs.pretty.models import VariantCoords, VariantData from hgvs.pretty.renderer.renderer import BasicRenderer from hgvs.sequencevariant import SequenceVariant @@ -7,20 +5,17 @@ class ShuffledVariant(BasicRenderer): - def __init__(self, config, orientation:int, var_g: SequenceVariant, vc: VariantCoords)->None: - super().__init__(config, orientation) + def __init__(self, config, orientation: int, var_g: SequenceVariant, vc: VariantCoords) -> None: + super().__init__(config, orientation) self.var_g = var_g self.vc = vc - def legend(self - ) ->str: - + def legend(self) -> str: + return "region : " - def display( - self, data: VariantData - ) -> str: + def display(self, data: VariantData) -> str: from hgvs.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW @@ -90,4 +85,4 @@ def display( else: var_str += " " - return var_str \ No newline at end of file + return var_str diff --git a/src/hgvs/pretty/renderer/tx_alig_renderer.py b/src/hgvs/pretty/renderer/tx_alig_renderer.py index eeb84b34..678934c5 100644 --- a/src/hgvs/pretty/renderer/tx_alig_renderer.py +++ b/src/hgvs/pretty/renderer/tx_alig_renderer.py @@ -1,25 +1,26 @@ import math + from hgvs.pretty.models import VariantData from hgvs.pretty.renderer.renderer import BasicRenderer class TxAligRenderer(BasicRenderer): - def legend(self)->str: + def legend(self) -> str: orientation = "->" if self.orientation < 0: orientation = "<-" return f"tx seq {orientation} : " - def display(self, data: VariantData) -> str: """If transcript info is available show the details of the tx for the region.""" if not data.var_c_or_n: return "" - from hgvs.pretty_print import ENDC, TYELLOW, TPURPLE + from hgvs.pretty_print import ENDC, TPURPLE, TYELLOW + var_str = "" - + first_c = None last_c = None @@ -28,14 +29,13 @@ def display(self, data: VariantData) -> str: counter = -1 for pdata in data.position_details: - + counter += 1 if not pdata.mapped_pos: var_str += " " continue - n_pos = pdata.n_pos c_pos = pdata.c_pos cig = pdata.cigar_ref @@ -45,9 +45,8 @@ def display(self, data: VariantData) -> str: coding = False var_str += " " continue - - if c_pos: + if c_pos: # if we get here we are coding... last_c = f"c.{c_pos}" else: @@ -61,7 +60,7 @@ def display(self, data: VariantData) -> str: coding = True if cig == "=": - #c3 = (c_pos - 1) % 3 + # c3 = (c_pos - 1) % 3 if c_pos: bg_col = math.ceil(c_pos / 3) % 2 elif n_pos: @@ -69,7 +68,7 @@ def display(self, data: VariantData) -> str: else: var_str += " " continue - + # print(p, c_pos, c3, bg_col ) if n_pos >= 0: base = pdata.tx @@ -93,4 +92,4 @@ def display(self, data: VariantData) -> str: else: var_str += "-" - return var_str \ No newline at end of file + return var_str diff --git a/src/hgvs/pretty/renderer/tx_mapping_renderer.py b/src/hgvs/pretty/renderer/tx_mapping_renderer.py index 52d21e70..6eb04721 100644 --- a/src/hgvs/pretty/renderer/tx_mapping_renderer.py +++ b/src/hgvs/pretty/renderer/tx_mapping_renderer.py @@ -3,65 +3,75 @@ class TxMappingRenderer(BasicRenderer): - """ prints the position in c/n coordinates. """ - + """prints the position in c/n coordinates.""" + def legend(self): return "tx pos : " def display(self, data: VariantData) -> str: - """show the position of the transcript seq""" - - var_str = "" - - count = -1 - prev_c_pos = '' - for pdata in data.position_details: - count += 1 - if not pdata.mapped_pos: - var_str += " " - prev_c_pos = '' - continue - - c_pos = pdata.c_pos - if c_pos is None and pdata.n_pos: - c_pos = pdata.n_pos - - if c_pos is None: - var_str += " " - prev_c_pos = c_pos - continue - - if pdata.cigar_ref == 'N': - var_str += " " - prev_c_pos = c_pos - continue - - if len(var_str) > count: - prev_c_pos = c_pos - continue - - if (c_pos + 1) % 10 == 0: - var_str += "|" - prev_c_pos = c_pos - continue - - elif (c_pos + 1) % 5 == 0: - var_str += "." - prev_c_pos = c_pos - continue - - elif prev_c_pos and prev_c_pos == c_pos and pdata.c_offset > 0 and (pdata.c_offset % 10) == 0: - var_str += "^" - prev_c_pos = c_pos - continue - elif prev_c_pos and prev_c_pos == c_pos and pdata.c_offset > 0 and (pdata.c_offset % 5) == 0: - var_str += "." - prev_c_pos = c_pos - continue - - elif c_pos == 0: - var_str += "|" + """show the position of the transcript seq""" + + var_str = "" + + count = -1 + prev_c_pos = "" + for pdata in data.position_details: + count += 1 + if not pdata.mapped_pos: + var_str += " " + prev_c_pos = "" + continue + + c_pos = pdata.c_pos + if c_pos is None and pdata.n_pos: + c_pos = pdata.n_pos + + if c_pos is None: + var_str += " " + prev_c_pos = c_pos + continue + if pdata.cigar_ref == "N": var_str += " " + prev_c_pos = c_pos + continue + + if len(var_str) > count: + prev_c_pos = c_pos + continue + + if (c_pos + 1) % 10 == 0: + var_str += "|" + prev_c_pos = c_pos + continue + + elif (c_pos + 1) % 5 == 0: + var_str += "." + prev_c_pos = c_pos + continue + + elif ( + prev_c_pos + and prev_c_pos == c_pos + and pdata.c_offset > 0 + and (pdata.c_offset % 10) == 0 + ): + var_str += "^" + prev_c_pos = c_pos + continue + elif ( + prev_c_pos + and prev_c_pos == c_pos + and pdata.c_offset > 0 + and (pdata.c_offset % 5) == 0 + ): + var_str += "." + prev_c_pos = c_pos + continue + + elif c_pos == 0: + var_str += "|" + + var_str += " " - return var_str \ No newline at end of file + return var_str diff --git a/src/hgvs/pretty/renderer/tx_pos.py b/src/hgvs/pretty/renderer/tx_pos.py index 6ff222c8..a58f0d10 100644 --- a/src/hgvs/pretty/renderer/tx_pos.py +++ b/src/hgvs/pretty/renderer/tx_pos.py @@ -2,11 +2,9 @@ from hgvs.pretty.renderer.renderer import BasicRenderer - - class TxRulerRenderer(BasicRenderer): - - def legend(self)->str: + + def legend(self) -> str: return " : " def display(self, data: VariantData) -> str: @@ -28,7 +26,7 @@ def display(self, data: VariantData) -> str: c_pos = pdata.n_pos interval = pdata.n_interval - if c_pos is None: + if c_pos is None: var_str += " " continue @@ -46,4 +44,4 @@ def display(self, data: VariantData) -> str: continue var_str += " " - return var_str.rstrip() \ No newline at end of file + return var_str.rstrip() diff --git a/src/hgvs/pretty/renderer/tx_ref_disagree_renderer.py b/src/hgvs/pretty/renderer/tx_ref_disagree_renderer.py index 30ca5287..a729bab6 100644 --- a/src/hgvs/pretty/renderer/tx_ref_disagree_renderer.py +++ b/src/hgvs/pretty/renderer/tx_ref_disagree_renderer.py @@ -3,10 +3,11 @@ class TxRefDisagreeRenderer(BasicRenderer): - """ Display tx-ref-disagree positions""" - def legend(self)->str: + """Display tx-ref-disagree positions""" - return f"tx ref dif: " + def legend(self) -> str: + + return f"tx ref dif: " def display(self, data: VariantData) -> str: """show differences between tx and ref genome, if there are any""" @@ -14,7 +15,6 @@ def display(self, data: VariantData) -> str: if not data.var_c_or_n: return "" - from hgvs.pretty_print import ENDC, TRED var_str = "" @@ -43,5 +43,4 @@ def display(self, data: VariantData) -> str: if var_str.isspace(): return "" - - return var_str \ No newline at end of file + return var_str diff --git a/src/hgvs/shell.py b/src/hgvs/shell.py index c81ea02d..d4d571c6 100755 --- a/src/hgvs/shell.py +++ b/src/hgvs/shell.py @@ -72,6 +72,7 @@ def shell(): normalizer, parse, parser, + pretty, projector, t_to_g, t_to_p, @@ -79,7 +80,6 @@ def shell(): validator, variant_mapper, vm, - pretty, ) from hgvs.utils.context import variant_context_w_alignment # noqa diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index 8a4697fc..f254254c 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -15,9 +15,7 @@ class Test_SimplePosition(unittest.TestCase): @classmethod def setUpClass(cls): cls.hp = hgvs.parser.Parser() - cls.hdp = hgvs.dataproviders.uta.connect( - mode=None, cache=None - ) + cls.hdp = hgvs.dataproviders.uta.connect(mode=None, cache=None) cls.pp = PrettyPrint( cls.hdp, ) @@ -58,8 +56,6 @@ def test_var_c1_forward(self): ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) - - def test_var_c1_reverse(self): """test c1 on <- strand""" @@ -84,7 +80,6 @@ def test_var_c1_reverse(self): for r, e in zip(result, expected_str): self.assertEqual(e, r) - def test_var_g_substitution(self): hgvs_g = "NC_000007.13:g.36561662C>T" var_g = self.hp.parse(hgvs_g) @@ -109,7 +104,6 @@ def test_var_g_substitution(self): ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) - def test_var_g_ins(self): """[ATTA]x2 -> x3""" @@ -156,7 +150,7 @@ def test_insertion(self): + " : 1,643,270 1,643,280 1,643,290 1,643,300 1,643,310\n" + "chrom pos : . | . | . | . | . | \n" + "seq -> : TCACTGGGGTGTCATCCTCATCGTCATCTTCGTAATTGAGGGAGCAAA\n" - + "region : |------| \n" + + "region : |------| \n" + "aa seq <- : sValProThrAspAspGluAspAspAspGluTyrAsnLeuSerCysLe\n" + "tx seq <- : AGTGACCCCACAGTAGGAGTAGCAGTAGAAGCATTAACTCCCTCGTTT\n" + "tx pos : | . | . | . | . | .\n" @@ -182,7 +176,7 @@ def test_insertion_size_1(self): + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" + "region : ^^ \n" + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" - + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + "tx pos : | . | . | . | \n" + " : 1500 1490 1480 1470\n" ).split("\n") @@ -208,7 +202,6 @@ def test_del_2bp(self): + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGAC\n" + "tx pos : | . | . | . | .\n" + " : 1500 1490 1480 1470\n" - ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -232,7 +225,6 @@ def test_del_1bp_shuffleable(self): + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + "tx pos : | . | . | . | \n" + " : 1500 1490 1480 1470\n" - + "ref>alt : CC>C\n" ).split("\n") for r, e in zip(result, expected_str): @@ -257,7 +249,6 @@ def test_del_1bp(self): + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGAC\n" + "tx pos : | . | . | . | .\n" + " : 1500 1490 1480 1470\n" - ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -330,7 +321,6 @@ def test_identity(self): + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGAC\n" + "tx pos : | . | . | . | .\n" + " : 1500 1490 1480 1470\n" - ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -425,7 +415,6 @@ def test_ref_disagree(self): + "tx seq <- : AGAGACCTCGGGGACTGAAGGCTCTACGTGCGGGGACCCCT\n" + "tx pos : . | . | . | . | \n" + " : 310 300 290 280\n" - ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -464,11 +453,10 @@ def test_ref_disagree_ref_ins(self): self.assertEqual(e, r) def test_ref_disagree_del(self): - # hgvs_g = "NC_000001.10:g.154574820_154574821delinsCA" - one base is a svn relative to the tx and part of the variant -> NM_001025107.2:c.-589C>T + # hgvs_g = "NC_000001.10:g.154574820_154574821delinsCA" - one base is a svn relative to the tx and part of the variant -> NM_001025107.2:c.-589C>T # var_g = self.hp.parse(hgvs_g) # hgvs_c = "NM_020469.2:c.188_189=" is NC_000009.11:g.136135237_136135238delinsGC in ref - hgvs_c = "NM_000682.6:c.901_911del" # a del variant var_c = self.hp.parse(hgvs_c) @@ -494,7 +482,6 @@ def test_ref_disagree_del(self): for r, e in zip(result, expected_str): self.assertEqual(e, r) - @pytest.mark.skip(reason="actually not that special, but still a nice variant.") def test_exon_boundary_overlap_forward_strand(self): hgvs_c = "NM_001283009.2:c.1228_1266+39del" @@ -535,8 +522,8 @@ def test_ruler(self): self.assertEqual(e, r) def test_rna_coding(self): - """ a rna coding transcript.""" - hgvs_n = 'NR_146230.2:n.10G>A' + """a rna coding transcript.""" + hgvs_n = "NR_146230.2:n.10G>A" var_n = self.hp.parse(hgvs_n) pp = PrettyPrint(self.hdp, show_reverse_strand=True) @@ -552,9 +539,8 @@ def test_rna_coding(self): + "region : A \n" + "aa seq -> : \n" + "tx seq -> : GATGTTCTGGTTAGTCTAAGAAGGAGAGT\n" - + "tx pos : . | . | . |\n" - + " : 10 20 30\n" + + "tx pos : . | . | . |\n" + + " : 10 20 30\n" ) for r, e in zip(result, expected_str): self.assertEqual(e, r) - From 0dcab289cf12d7d703d62bdbba11d1acbb08cc25 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Thu, 6 Jun 2024 08:04:18 -0700 Subject: [PATCH 10/31] feat(prot-pos): now showing the protein (amino acid) positions more clearly --- src/hgvs/pretty/datacompiler.py | 12 +-- src/hgvs/pretty/models.py | 7 +- .../pretty/renderer/prot_mapping_renderer.py | 45 ++++++++++ .../pretty/renderer/prot_ruler_renderer.py | 53 +++++++++++ src/hgvs/pretty/renderer/tx_alig_renderer.py | 7 +- src/hgvs/pretty_print.py | 4 +- tests/test_pretty_print.py | 90 +++++++++++++------ 7 files changed, 180 insertions(+), 38 deletions(-) create mode 100644 src/hgvs/pretty/renderer/prot_mapping_renderer.py create mode 100644 src/hgvs/pretty/renderer/prot_ruler_renderer.py diff --git a/src/hgvs/pretty/datacompiler.py b/src/hgvs/pretty/datacompiler.py index 58083b8d..ac3684a3 100644 --- a/src/hgvs/pretty/datacompiler.py +++ b/src/hgvs/pretty/datacompiler.py @@ -1,4 +1,4 @@ -from typing import Optional, Tuple +from typing import List, Optional, Tuple from bioutils.normalize import normalize from bioutils.sequences import aa1_to_aa3_lut @@ -154,7 +154,7 @@ def _get_prot_alt( if strand < 0: aa_char = tlc[2 - c3] - return ProteinData(c_pos, aa, tlc, aa_char, var_p, is_init_met, is_stop_codon) + return ProteinData(c_pos, aa, tlc, aa_char, var_p, is_init_met, is_stop_codon, aa_index) def data( self, @@ -226,7 +226,7 @@ def data( else: reference_data = None - position_details = [] + position_details:List[PositionDetail] = [] prev_mapped_pos = None prev_c_pos = -1 @@ -337,9 +337,9 @@ def data( c_interval, ) - # print(position_details[0].get_header()) - # for p in position_details: - # print(f"{p}\t{p.protein_data}\t") + print(position_details[0].get_header()+"\t"+ProteinData.get_header()) + for p in position_details: + print(f"{p}\t{p.protein_data}\t") vd = VariantData( seq_start, diff --git a/src/hgvs/pretty/models.py b/src/hgvs/pretty/models.py index 67b7f80f..9e1c4e05 100644 --- a/src/hgvs/pretty/models.py +++ b/src/hgvs/pretty/models.py @@ -27,9 +27,14 @@ class ProteinData: var_p: SequenceVariant = None is_init_met: bool = False is_stop_codon: bool = False + aa_pos: int = -1 + + @classmethod + def get_header(cls) -> str: + return f"c_pos\ttlc\taa_char\tc_pos\taa_pos" def __repr__(self) -> str: - return f"{self.c_pos}\t{self.tlc}\t{self.aa_char}\t{self.c_pos %3}" + return f"{self.c_pos}\t{self.tlc}\t{self.aa_char}\t{self.c_pos %3}\t{self.aa_pos}" @dataclass(eq=True, frozen=False, order=True) diff --git a/src/hgvs/pretty/renderer/prot_mapping_renderer.py b/src/hgvs/pretty/renderer/prot_mapping_renderer.py new file mode 100644 index 00000000..9de2316c --- /dev/null +++ b/src/hgvs/pretty/renderer/prot_mapping_renderer.py @@ -0,0 +1,45 @@ +from hgvs.pretty.models import VariantData +from hgvs.pretty.renderer.renderer import BasicRenderer + + +class ProtMappingRenderer(BasicRenderer): + """prints the position in p (amino acid) coordinates.""" + + def legend(self): + return "aa pos : " + + def display(self, data: VariantData) -> str: + """show the position of the transcript seq""" + + var_str = "" + + count = -1 + for pdata in data.position_details: + count += 1 + if not pdata.mapped_pos: + var_str += " " + continue + + prot_data = pdata.protein_data + + if not prot_data or prot_data.aa_pos < 0: + var_str +=" " + continue + + + if len(var_str) > count: + continue + + aa_pos = prot_data.aa_pos + if (aa_pos + 1) % 10 == 0: + var_str += "|" + continue + + elif (aa_pos + 1) % 5 == 0: + var_str += "." + continue + + + var_str += " " + + return var_str diff --git a/src/hgvs/pretty/renderer/prot_ruler_renderer.py b/src/hgvs/pretty/renderer/prot_ruler_renderer.py new file mode 100644 index 00000000..a31433e2 --- /dev/null +++ b/src/hgvs/pretty/renderer/prot_ruler_renderer.py @@ -0,0 +1,53 @@ +from hgvs.pretty.models import VariantData +from hgvs.pretty.renderer.renderer import BasicRenderer + + +class ProtRulerRenderer(BasicRenderer): + + def legend(self) -> str: + return " : " + + def display(self, data: VariantData) -> str: + """show the position of the protein seq""" + var_str = "" + + count = -1 + prev_aa = -1 + for pdata in data.position_details: + count += 1 + + if not pdata.mapped_pos: + var_str += " " + prev_aa = -1 + continue + + prot_data = pdata.protein_data + + if not prot_data or prot_data.aa_pos < 0: + var_str += " " + prev_aa = -1 + continue + + + if len(var_str) > count: + continue + + aa_pos = prot_data.aa_pos + + if aa_pos == prev_aa : + var_str += ' ' + continue + + prev_aa = aa_pos + + if (aa_pos + 1) % 10 == 0: + var_str += f"{aa_pos+1} " + continue + + elif aa_pos == 0: + var_str += f"{aa_pos+1} " + continue + + var_str += " " + + return var_str diff --git a/src/hgvs/pretty/renderer/tx_alig_renderer.py b/src/hgvs/pretty/renderer/tx_alig_renderer.py index 678934c5..27c4a948 100644 --- a/src/hgvs/pretty/renderer/tx_alig_renderer.py +++ b/src/hgvs/pretty/renderer/tx_alig_renderer.py @@ -59,12 +59,11 @@ def display(self, data: VariantData) -> str: coding = True - if cig == "=": - # c3 = (c_pos - 1) % 3 + if cig == "=": if c_pos: - bg_col = math.ceil(c_pos / 3) % 2 + bg_col = math.ceil((c_pos+1) / 3) % 2 elif n_pos: - bg_col = math.ceil(n_pos / 3) % 2 + bg_col = math.ceil((n_pos+1) / 3) % 2 else: var_str += " " continue diff --git a/src/hgvs/pretty_print.py b/src/hgvs/pretty_print.py index 69b46fa9..6d38604a 100644 --- a/src/hgvs/pretty_print.py +++ b/src/hgvs/pretty_print.py @@ -7,6 +7,8 @@ from hgvs.pretty.renderer.chrom_seq_renderer import ChromSeqRendered from hgvs.pretty.renderer.chrom_seq_reverse_renderer import ChromReverseSeqRendered from hgvs.pretty.renderer.pos_info import ChrPositionInfo +from hgvs.pretty.renderer.prot_mapping_renderer import ProtMappingRenderer +from hgvs.pretty.renderer.prot_ruler_renderer import ProtRulerRenderer from hgvs.pretty.renderer.prot_seq_renderer import ProtSeqRenderer from hgvs.pretty.renderer.ruler import ChrRuler from hgvs.pretty.renderer.shuffled_variant import ShuffledVariant @@ -253,7 +255,7 @@ def create_repre( else: var_str += shuffled_seq_header + left_shuffled_str + "\n" - renderers_cls = [ProtSeqRenderer, TxAligRenderer, TxMappingRenderer, TxRulerRenderer] + renderers_cls = [TxAligRenderer, TxMappingRenderer, TxRulerRenderer, ProtSeqRenderer, ProtMappingRenderer, ProtRulerRenderer ] for cls in renderers_cls: renderer = cls(self.config, data.strand) diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index f254254c..2a46c877 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -26,10 +26,12 @@ def setUpClass(cls): + "chrom pos : | . | . | . | . | .\n" + "seq -> : ATAAAGCTTTTCCAAATGTTATTAATTACTGGCATTGCTTTTTGCCAA\n" + "region : |------| \n" - + "aa seq <- : TerAsnSerAlaAsnSerLysAlaLeu\n" + "tx seq <- : TATTTCGAAAAGGTTTACAATAATTAATGACCGTAACGAAAAACGGTT\n" + "tx pos : | . | . | | . | . | \n" + " : *20 *10 *1 2880 2870 2860\n" + + "aa seq <- : TerAsnSerAlaAsnSerLysAlaLeu\n" + + "aa pos : ||| ... \n" + + " : 960 \n" + "ref>alt : ATTAATTA>ATTAATTAATTA\n" ) @@ -49,10 +51,12 @@ def test_var_c1_forward(self): + "seq -> : CCTCCAGTTCAATCCCCAGCATGGCCGCGTCCACTATGTCT\n" + "tx ref dif: X \n" + "region : = \n" - + "aa seq -> : MetAlaAlaSerThrMetSer\n" + "tx seq -> : cctccagttcaatccccagcATGGCTGCGTCCACTATGTCT\n" + "tx pos : | . | . | . | . | \n" + " : -20 -10 1 10 20\n" + + "aa seq -> : MetAlaAlaSerThrMetSer\n" + + "aa pos : ... \n" + + " : 1 \n" ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -72,10 +76,12 @@ def test_var_c1_reverse(self): + "chrom pos : . | . | . | . | \n" + "seq -> : GATTTTCCAGGGGGACTGCATCTCCGAGCTATGCACCCCAA\n" + "region : = \n" - + "aa seq <- : IleLysTrpProSerGlnMet \n" + "tx seq <- : CTAAAAGGTCCCCCTGACGTAgaggctcgatacgtggggtt\n" + "tx pos : | . | . | . | . |\n" + " : 20 10 1 -10 -20\n" + + "aa seq <- : IleLysTrpProSerGlnMet \n" + + "aa pos : ... \n" + + " : 1 \n" ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -97,10 +103,12 @@ def test_var_g_substitution(self): + "seq -> : TACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" + "seq <- : ATGGAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + "region : T \n" - + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + "tx pos : | . | . | . | \n" + " : 1500 1490 1480 1470\n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + + "aa pos : ||| ... ||| \n" + + " : 500 490 \n" ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -150,11 +158,13 @@ def test_insertion(self): + " : 1,643,270 1,643,280 1,643,290 1,643,300 1,643,310\n" + "chrom pos : . | . | . | . | . | \n" + "seq -> : TCACTGGGGTGTCATCCTCATCGTCATCTTCGTAATTGAGGGAGCAAA\n" - + "region : |------| \n" - + "aa seq <- : sValProThrAspAspGluAspAspAspGluTyrAsnLeuSerCysLe\n" + + "region : |------| \n" + "tx seq <- : AGTGACCCCACAGTAGGAGTAGCAGTAGAAGCATTAACTCCCTCGTTT\n" + "tx pos : | . | . | . | . | .\n" + " : 950 940 930 920 910\n" + + "aa seq <- : sValProThrAspAspGluAspAspAspGluTyrAsnLeuSerCysLe\n" + + "aa pos : ... ||| ... \n" + + " : 310 \n" + "ref>alt : TCGTCATC>TCGTCATCGTCGTCATC\n" ).split("\n") for r, e in zip(result, expected_str): @@ -174,11 +184,13 @@ def test_insertion_size_1(self): + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" - + "region : ^^ \n" - + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + + "region : ^^ \n" + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + "tx pos : | . | . | . | \n" + " : 1500 1490 1480 1470\n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + + "aa pos : ||| ... ||| \n" + + " : 500 490 \n" ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -197,11 +209,13 @@ def test_del_2bp(self): + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : TACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCTG\n" - + "region : xx \n" - + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGln\n" + + "region : xx \n" + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGAC\n" + "tx pos : | . | . | . | .\n" + " : 1500 1490 1480 1470\n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGln\n" + + "aa pos : ||| ... ||| \n" + + " : 500 490 \n" ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -220,11 +234,13 @@ def test_del_1bp_shuffleable(self): + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : TTACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" - + "region : xx \n" - + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + + "region : xx \n" + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + "tx pos : | . | . | . | \n" + " : 1500 1490 1480 1470\n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + + "aa pos : ||| ... ||| \n" + + " : 500 490 \n" + "ref>alt : CC>C\n" ).split("\n") for r, e in zip(result, expected_str): @@ -245,10 +261,12 @@ def test_del_1bp(self): + "chrom pos : . | . | . | . | \n" + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCTG\n" + "region : x \n" - + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGln\n" + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGAC\n" + "tx pos : | . | . | . | .\n" + " : 1500 1490 1480 1470\n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGln\n" + + "aa pos : ||| ... ||| \n" + + " : 500 490 \n" ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -268,10 +286,12 @@ def test_dup_1bp_shuffleable(self): + "chrom pos : . | . | . | . | \n" + "seq -> : TTACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" + "region : || \n" - + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + "tx pos : | . | . | . | \n" + " : 1500 1490 1480 1470\n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + + "aa pos : ||| ... ||| \n" + + " : 500 490 \n" + "ref>alt : CC>CCC\n" ).split("\n") for r, e in zip(result, expected_str): @@ -292,10 +312,12 @@ def test_dup_1bp(self): + "chrom pos : . | . | . | . | \n" + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCTG\n" + "region : | \n" - + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGln\n" + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGAC\n" + "tx pos : | . | . | . | .\n" + " : 1500 1490 1480 1470\n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGln\n" + + "aa pos : ||| ... ||| \n" + + " : 500 490 \n" + "ref>alt : A>AA\n" ).split("\n") for r, e in zip(result, expected_str): @@ -316,11 +338,13 @@ def test_identity(self): + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCTG\n" - + "region : = \n" - + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGln\n" + + "region : = \n" + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGAC\n" + "tx pos : | . | . | . | .\n" + " : 1500 1490 1480 1470\n" + + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGln\n" + + "aa pos : ||| ... ||| \n" + + " : 500 490 \n" ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -344,10 +368,12 @@ def test_tiny(self): + "chrom pos : | .\n" + "seq -> : ATTAATTA\n" + "region : |------|\n" - + "aa seq <- : TerAsnS\n" + "tx seq <- : TAATTAAT\n" + "tx pos : | | \n" + " : *1 2880\n" + + "aa seq <- : TerAsnS\n" + + "aa pos : ||| \n" + + " : 960 \n" + "ref>alt : ATTAATTA>ATTAATTAATTA" ).split("\n") @@ -382,11 +408,13 @@ def test_hgvs_c(self): + " 33,049,650 33,049,670 33,049,690 33,049,710 33,049,730 33,049,750 33,049,770 33,049,790\n" + " . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |\n" + "CTGGGGCGCCGGGGGCTGCCATGGGGCCGGTGGGGGCGACCGAGCTGCTCGCCTGCCTCTGGACTCGCGGGCGAAGCCGCCACGGAGCTGGGGGCGCTGGCGCGAGCCCCGCCCCGCTCGAGTCCGGCCCCGCCCCTGGCCCGCCCC\n" - + " |-------------------------| \n" - + "aProAlaGlyProAlaAlaMet \n" + + " |-------------------------| \n" + "GACCCCGCGGCCCCCGACGGTAccccggccacccccgctggctcgacgagcggacggagacctgagcgcccgcttcggcggtgcctcgacccccgcgaccgcgctcggggcggggcgagctcaggccggggcgggga \n" + " | . | . | . | . | . | . | . | . | . | . | . | . | . | . \n" + " 20 10 1 -10 -20 -30 -40 -50 -60 -70 -80 -90 -100 -110\n" + + "aProAlaGlyProAlaAlaMet \n" + + " ... \n" + + " 1 \n" + "GGGGGCTGCCATGGGGCCGGTGGGGGC>GGGGGCTGCCATGGGGCCGGTGGGGGCTGCCATGGGGCCGGTGGGGGC" ).split("\n") for r, e in zip(result, expected_str): @@ -411,10 +439,12 @@ def test_ref_disagree(self): + "seq -> : TCTCTGGAGCCCCTGACTTCTGAGATGCACGCCCCTGGGGA\n" + "tx ref dif: X \n" + "region : = \n" - + "aa seq <- : ArgGlnLeuGlyGlnSerGlyLeuHisValGlyArgProVa\n" + "tx seq <- : AGAGACCTCGGGGACTGAAGGCTCTACGTGCGGGGACCCCT\n" + "tx pos : . | . | . | . | \n" + " : 310 300 290 280\n" + + "aa seq <- : ArgGlnLeuGlyGlnSerGlyLeuHisValGlyArgProVa\n" + + "aa pos : ... ||| ... \n" + + " : 100 \n" ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -442,10 +472,12 @@ def test_ref_disagree_ref_ins(self): "seq -> : CGACTGCCCAGAGAGCTGCTGCGAGCCCCCCTGCTGCGCCCCCAGCTGCTGCGCCCCGGCCCCCTGCCTGAGCCTGGTCTGCACCCCAGTGAGCCGT\n" "tx ref dif: IIIIIIIIIIIIIII XX \n" "region : x-------------------------x \n" - "aa seq -> : pAspCysProGluSerCysCysGluProPr---------------oCysCysAlaProAlaProCysLeuSerLeuValCysThrProValSerTyr\n" "tx seq -> : CGACTGCCCAGAGAGCTGCTGCGAGCCCCC---------------CTGCTGCGCCCCGGCCCCCTGCCTGAGCCTGGTCTGCACCCCAGTGAGCTAT\n" "tx pos : . | . | . | . | . | . | . | . | . \n" " : 110 120 130 140 150 160 170 180\n" + "aa seq -> : pAspCysProGluSerCysCysGluProPr---------------oCysCysAlaProAlaProCysLeuSerLeuValCysThrProValSerTyr\n" + "aa pos : . ||| .. . ||| ... ||| \n" + " : 40 50 60 \n" "ref>alt : CTGCTGCGCCCCCAGCTGCTGCGCCCC>CTGCTGCGCCCC\n" ).split("\n") @@ -473,10 +505,12 @@ def test_ref_disagree_del(self): + "seq -> : TGCCTGGGGTTCACACTCTTCCTCCTCCTCCTCCTCCTCTTC.........GGCTTCATCCTCTGGAGATGCCCCACAAACACCCTCCTTC\n" + "tx ref dif: DDDDDDDDD \n" + "region : x----------x \n" - + "aa seq <- : AlaGlnProGluCysGluGluGluGluGluGluGluGluGluGluGluGluAlaGluAspGluProSerAlaGlyCysValGlyGluLysG\n" + "tx seq <- : ACGGACCCCAAGTGTGAGAAGGAGGAGGAGGAGGAGGAGAAGGAGGAGAAGTCGAAGTAGGAGACCTCTACGGGGTGTTTGTGGGAGGAAG\n" + "tx pos : | . | . | . | . | . | . | . | . | . \n" + " : 940 930 920 910 900 890 880 870 860\n" + + "aa seq <- : AlaGlnProGluCysGluGluGluGluGluGluGluGluGluGluGluGluAlaGluAspGluProSerAlaGlyCysValGlyGluLysG\n" + + "aa pos : ||| ... ||| ... ||| ... \n" + + " : 310 300 290 \n" + "ref>alt : CTCCTCCTCTTC>C\n" ).split("\n") for r, e in zip(result, expected_str): @@ -513,10 +547,12 @@ def test_ruler(self): + "TCTCTGGAGCCCCTGACTTCTGAGATGCACGCCCCTGGGGA\n" + " X \n" + " = \n" - + "ArgGlnLeuGlyGlnSerGlyLeuHisValGlyArgProVa\n" + "AGAGACCTCGGGGACTGAAGGCTCTACGTGCGGGGACCCCT\n" + " . | . | . | . | \n" + " 310 300 290 280\n" + + "ArgGlnLeuGlyGlnSerGlyLeuHisValGlyArgProVa\n" + + " ... ||| ... \n" + + " 100 \n" ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -536,11 +572,13 @@ def test_rna_coding(self): + "chrom pos : | . | . | . | . |\n" + "seq -> : TGCTGATCTTTGGATGTTCTGGTTAGTCTAAGAAGGAGAGT\n" + "seq <- : ACGACTAGAAACCTACAAGACCAATCAGATTCTTCCTCTCA\n" - + "region : A \n" - + "aa seq -> : \n" + + "region : A \n" + "tx seq -> : GATGTTCTGGTTAGTCTAAGAAGGAGAGT\n" + "tx pos : . | . | . |\n" + " : 10 20 30\n" + + "aa seq -> : \n" + + "aa pos : \n" + + " : \n" ) for r, e in zip(result, expected_str): self.assertEqual(e, r) From e9f578af23de3d7ac90610fa39eafda11129d2b9 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Thu, 6 Jun 2024 22:36:35 -0700 Subject: [PATCH 11/31] feat(repeat-detection): now with better detection of repetitive units that are larger than 1 bp. --- src/hgvs/repeats.py | 23 ++++++++++++++++++++--- tests/test_repeats.py | 29 ++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/hgvs/repeats.py b/src/hgvs/repeats.py index 3c4379eb..e09d45d4 100644 --- a/src/hgvs/repeats.py +++ b/src/hgvs/repeats.py @@ -15,6 +15,17 @@ def count_pattern_occurence(pattern, string): matches = re.findall(pattern, string) return len(matches) +def count_repetitive_units(s): + length = len(s) + + for i in range(1, length + 1): + unit = s[:i] + if length % i == 0: + if unit * (length // i) == s: + return length // i, unit + + return 1, s + class RepeatAnalyser: @@ -48,20 +59,26 @@ def __repr__(self): def _get_repeat_unit(self, fs: VariantCoords) -> Optional[str]: """Takes fully justified coordiantes and tries to detect a repeat in them.""" # analyze for repeat: - if len(fs.ref) == len(fs.alt): + if len(fs.ref) == len(fs.alt) > 0: # seems we cant shuffle. is an SVN or delins return None + print(len(fs.alt)) + if len(fs.alt) > 0: if fs.alt in fs.ref: if fs.ref.startswith(fs.alt): d = fs.ref[len(fs.alt) :] if count_pattern_occurence(d, fs.ref) > 1: - return d + c, u = count_repetitive_units(d) + print (f"found repeat {d} has {c} smaller repeats {u}") + return u elif fs.ref in fs.alt: if fs.alt.startswith(fs.ref): d = fs.alt[len(fs.ref) :] if count_pattern_occurence(d, fs.ref) > 1: - return d + c, u = count_repetitive_units(d) + print (f"found repeat {d} has {c} smaller repeats {u}") + return u return None diff --git a/tests/test_repeats.py b/tests/test_repeats.py index 62bbeb4f..51206d13 100644 --- a/tests/test_repeats.py +++ b/tests/test_repeats.py @@ -7,7 +7,7 @@ import hgvs from hgvs.parser import Parser -from hgvs.repeats import RepeatAnalyser +from hgvs.repeats import RepeatAnalyser, count_repetitive_units from hgvs.variantmapper import VariantMapper @@ -20,24 +20,47 @@ def setUpClass(cls): cls.vm = hgvs.variantmapper.VariantMapper(cls.hdp) cls.hp = hgvs.parser.Parser() + + @parameterized.expand( + [ + ('abc' , 'abc' ,1), + ('CC' , 'C' , 2), + ('GAGA' , 'GA', 2), + ('', '' , 1), + ('AAAAAB', 'AAAAAB', 1), + ('ABBCABBCABBC', 'ABBC', 3) + ] + ) + def test_count_repetitive_units(self, s: str, u:str, c: int): + + observed_c, observed_u = count_repetitive_units(s) + + self.assertEqual(u, observed_u) + self.assertEqual(c, observed_c) + + + @parameterized.expand( [ ("NC_000019.10:g.45770205del", True, "C", 6, 5, "C[6]>C[5]"), + ("NC_000019.10:g.45770204_45770205del", True, "C", 6, 4, "C[6]>C[4]"), ("NC_000019.10:g.45770205insC", True, "C", 6, 7, "C[6]>C[7]"), ("NC_000019.10:g.45770206del", False, None, 0, 0, "A>"), ("NC_000007.13:g.36561662dup", True, "C", 2, 3, "C[2]>C[3]"), + ("NC_000019.10:g.45770210_45770212del", True, 'GCA', 20, 19, "GCA[20]>GCA[19]") ] ) def test_homopolymer(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, s): - # hgvs_g = "NC_000019.10:g.45770205del" var_g = self.hp.parse_hgvs_variant(hgvs_g) ra = RepeatAnalyser(self.hdp, var_g) + print(f"{hgvs_g} : {ra} : desired: {repeat_unit} {ref_count} {s}") + self.assertEqual(s, str(ra)) self.assertEqual(is_repeat, ra.is_repeat) self.assertEqual(repeat_unit, ra.repeat_unit) self.assertEqual(ref_count, ra.ref_count) self.assertEqual(alt_count, ra.alt_count) - self.assertEqual(s, str(ra)) + @parameterized.expand( [ From 99a614f4060caf898be36151022810779196d33b Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Thu, 6 Jun 2024 22:59:39 -0700 Subject: [PATCH 12/31] feat(repeat-detection): hooking repeat detection improvements in pretty print. --- src/hgvs/pretty_print.py | 9 +++++++-- src/hgvs/repeats.py | 8 ++------ tests/test_pretty_print.py | 8 ++++---- tests/test_repeats.py | 23 +++++++++++++++-------- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/hgvs/pretty_print.py b/src/hgvs/pretty_print.py index 6d38604a..98cf101d 100644 --- a/src/hgvs/pretty_print.py +++ b/src/hgvs/pretty_print.py @@ -17,7 +17,7 @@ from hgvs.pretty.renderer.tx_pos import TxRulerRenderer from hgvs.pretty.renderer.tx_ref_disagree_renderer import TxRefDisagreeRenderer from hgvs.sequencevariant import SequenceVariant - +from hgvs.repeats import RepeatAnalyser TGREEN = "\033[32m" # Green Text TGREENBG = "\033[30;42m" TRED = "\033[31m" # Red Text @@ -271,6 +271,11 @@ def create_repre( # TODO: detect repeats? fully_justified_ref = fully_justified_var.ref fully_justified_alt = fully_justified_var.alt - var_str += refa + f"{fully_justified_ref}>{fully_justified_alt}" + + ra = RepeatAnalyser(fully_justified_var) + if ra.is_repeat: + var_str += refa + str(ra) + else: + var_str += refa + f"{fully_justified_ref}>{fully_justified_alt}" return var_str diff --git a/src/hgvs/repeats.py b/src/hgvs/repeats.py index e09d45d4..4d2104c8 100644 --- a/src/hgvs/repeats.py +++ b/src/hgvs/repeats.py @@ -29,13 +29,9 @@ def count_repetitive_units(s): class RepeatAnalyser: - def __init__(self, hdp: Interface, var_g: SequenceVariant) -> None: - - config = PrettyConfig(hdp, None, None) - dc = DataCompiler(config) - - fs = dc.get_shuffled_variant(var_g, 0) + def __init__(self, fs: VariantCoords) -> None: + self.is_repeat = False self.ref_count = 0 self.alt_count = 0 diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index 2a46c877..ec65bdbc 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -32,7 +32,7 @@ def setUpClass(cls): + "aa seq <- : TerAsnSerAlaAsnSerLysAlaLeu\n" + "aa pos : ||| ... \n" + " : 960 \n" - + "ref>alt : ATTAATTA>ATTAATTAATTA\n" + + "ref>alt : ATTA[2]>ATTA[3]\n" ) def test_var_c1_forward(self): @@ -241,7 +241,7 @@ def test_del_1bp_shuffleable(self): + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + "aa pos : ||| ... ||| \n" + " : 500 490 \n" - + "ref>alt : CC>C\n" + + "ref>alt : C[2]>C[1]\n" ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -292,7 +292,7 @@ def test_dup_1bp_shuffleable(self): + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGl\n" + "aa pos : ||| ... ||| \n" + " : 500 490 \n" - + "ref>alt : CC>CCC\n" + + "ref>alt : C[2]>C[3]\n" ).split("\n") for r, e in zip(result, expected_str): self.assertEqual(e, r) @@ -374,7 +374,7 @@ def test_tiny(self): + "aa seq <- : TerAsnS\n" + "aa pos : ||| \n" + " : 960 \n" - + "ref>alt : ATTAATTA>ATTAATTAATTA" + + "ref>alt : ATTA[2]>ATTA[3]" ).split("\n") for r, e in zip(result, expected_str): diff --git a/tests/test_repeats.py b/tests/test_repeats.py index 51206d13..df28c562 100644 --- a/tests/test_repeats.py +++ b/tests/test_repeats.py @@ -1,14 +1,15 @@ import os from unittest import TestCase +from hgvs.pretty.datacompiler import DataCompiler +from hgvs.pretty.models import PrettyConfig import pytest from parameterized import parameterized from support import CACHE import hgvs -from hgvs.parser import Parser + from hgvs.repeats import RepeatAnalyser, count_repetitive_units -from hgvs.variantmapper import VariantMapper class TestRepeats(TestCase): @@ -46,15 +47,17 @@ def test_count_repetitive_units(self, s: str, u:str, c: int): ("NC_000019.10:g.45770204_45770205del", True, "C", 6, 4, "C[6]>C[4]"), ("NC_000019.10:g.45770205insC", True, "C", 6, 7, "C[6]>C[7]"), ("NC_000019.10:g.45770206del", False, None, 0, 0, "A>"), - ("NC_000007.13:g.36561662dup", True, "C", 2, 3, "C[2]>C[3]"), - ("NC_000019.10:g.45770210_45770212del", True, 'GCA', 20, 19, "GCA[20]>GCA[19]") + ("NC_000007.13:g.36561662dup", True, "C", 2, 3, "C[2]>C[3]"), ] ) def test_homopolymer(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, s): var_g = self.hp.parse_hgvs_variant(hgvs_g) - ra = RepeatAnalyser(self.hdp, var_g) - print(f"{hgvs_g} : {ra} : desired: {repeat_unit} {ref_count} {s}") + config = PrettyConfig(self.hdp, None, None) + dc = DataCompiler(config) + fs = dc.get_shuffled_variant(var_g, 0) + ra = RepeatAnalyser(fs) + self.assertEqual(s, str(ra)) self.assertEqual(is_repeat, ra.is_repeat) self.assertEqual(repeat_unit, ra.repeat_unit) @@ -82,13 +85,17 @@ def test_homopolymer(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, ), ("NC_000005.10:g.123346517_123346518insATTA", True, "ATTA", 2, 3, "ATTA[2]>ATTA[3]"), ("NC_000005.10:g.123346522_123346525dup", True, "ATTA", 2, 3, "ATTA[2]>ATTA[3]"), + ("NC_000019.10:g.45770210_45770212del", True, 'GCA', 20, 19, "GCA[20]>GCA[19]") ] ) def test_repeats(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, s): - # hgvs_g = "NC_000019.10:g.45770205del" var_g = self.hp.parse_hgvs_variant(hgvs_g) - ra = RepeatAnalyser(self.hdp, var_g) + config = PrettyConfig(self.hdp, None, None) + dc = DataCompiler(config) + fs = dc.get_shuffled_variant(var_g, 0) + ra = RepeatAnalyser(fs) + self.assertEqual(is_repeat, ra.is_repeat) self.assertEqual(repeat_unit, ra.repeat_unit) self.assertEqual(ref_count, ra.ref_count) From f1e9703f8e1dfaff73ab369dfbebbf07a95df160 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sat, 29 Jun 2024 23:33:14 -0700 Subject: [PATCH 13/31] feat(pretty_print): now showing hgvs_p, making code more accessible for consumer --- src/hgvs/pretty/datacompiler.py | 14 +++-- src/hgvs/pretty/models.py | 2 + src/hgvs/pretty_print.py | 57 ++++++++++++++------- src/hgvs/repeats.py | 9 +--- tests/test_pretty_print.py | 91 ++++++++++++++++++++------------- 5 files changed, 106 insertions(+), 67 deletions(-) diff --git a/src/hgvs/pretty/datacompiler.py b/src/hgvs/pretty/datacompiler.py index ac3684a3..95781ed3 100644 --- a/src/hgvs/pretty/datacompiler.py +++ b/src/hgvs/pretty/datacompiler.py @@ -6,7 +6,7 @@ import hgvs import hgvs.utils.altseq_to_hgvsp as altseq_to_hgvsp import hgvs.utils.altseqbuilder as altseqbuilder -from hgvs.exceptions import HGVSDataNotAvailableError, HGVSInvalidIntervalError +from hgvs.exceptions import HGVSInvalidIntervalError from hgvs.pretty.models import ( PositionDetail, PrettyConfig, @@ -187,7 +187,7 @@ def data( seq_end = display_end if var_c_or_n is not None: - tx_ac = var_c_or_n.ac + tx_ac = var_c_or_n.ac else: tx_ac = "" # can't show transcript , since there is none. @@ -222,13 +222,16 @@ def data( # we don't know the protein ac, get it looked up: pro_ac = None if var_c_or_n and var_c_or_n.type == "c": + var_p = am.c_to_p(var_c_or_n) reference_data = RefTranscriptData(self.config.hdp, tx_ac, pro_ac) else: + var_p = None reference_data = None position_details:List[PositionDetail] = [] prev_mapped_pos = None prev_c_pos = -1 + prev_n_pos = -1 for chromosome_pos in range(seq_start + 1, seq_end + 1): @@ -337,9 +340,9 @@ def data( c_interval, ) - print(position_details[0].get_header()+"\t"+ProteinData.get_header()) - for p in position_details: - print(f"{p}\t{p.protein_data}\t") + # print(position_details[0].get_header()+"\t"+ProteinData.get_header()) + # for p in position_details: + # print(f"{p}\t{p.protein_data}\t") vd = VariantData( seq_start, @@ -353,6 +356,7 @@ def data( var_g, mapper.strand, var_c_or_n, + var_p, position_details, ) diff --git a/src/hgvs/pretty/models.py b/src/hgvs/pretty/models.py index 9e1c4e05..dd32f1f7 100644 --- a/src/hgvs/pretty/models.py +++ b/src/hgvs/pretty/models.py @@ -88,6 +88,7 @@ class VariantData: var_g: SequenceVariant strand: int var_c_or_n: SequenceVariant = None + var_p: SequenceVariant = None position_details: List[PositionDetail] = None all: bool = False @@ -104,6 +105,7 @@ class PrettyConfig: default_assembly: str = "GRCh37" useColor: bool = False showLegend: bool = True + showAllShuffleableRegions = False infer_hgvs_c: bool = True all: bool = False # print all possible hgvs_c (for all UTA transcripts) show_reverse_strand: bool = False # show the reverse strand sequence for the chromosome diff --git a/src/hgvs/pretty_print.py b/src/hgvs/pretty_print.py index 98cf101d..96328ae2 100644 --- a/src/hgvs/pretty_print.py +++ b/src/hgvs/pretty_print.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Tuple import hgvs from hgvs.assemblymapper import AssemblyMapper @@ -131,15 +131,9 @@ def _colorize_hgvs(self, hgvs_str: str) -> str: return var_str - def display( - self, - sv: SequenceVariant, - tx_ac: str = None, - display_start: int = None, - display_end: int = None, - ) -> str: - """Takes a variant and prints the genomic context around it.""" + def get_hgvs_names(self, sv: SequenceVariant, tx_ac:str =None)->Tuple[SequenceVariant, SequenceVariant]: + var_c_or_n = None if sv.type == "g": var_g = sv @@ -152,19 +146,31 @@ def display( # map back to genome var_g = self._map_to_chrom(sv) var_c_or_n = sv + + return var_g, var_c_or_n - data_compiler = DataCompiler(config=self.config) + def display( + self, + sv: SequenceVariant, + tx_ac: str = None, + display_start: int = None, + display_end: int = None, + ) -> str: + """Takes a variant and prints the genomic context around it.""" + + var_g, var_c_or_n = self.get_hgvs_names(sv,tx_ac) + + self.data_compiler = DataCompiler(config=self.config) if self.config.all: # get all overlapping transcripts response = "" tx_acs = self._get_all_transcripts(var_g) - print(f"displaying {len(tx_acs)} alternative transcripts") for tx_ac in tx_acs: var_c_or_n = self._infer_hgvs_c(var_g, tx_ac) response += self.create_repre( - var_g, var_c_or_n, display_start, display_end, data_compiler + var_g, var_c_or_n, display_start, display_end, self.data_compiler ) response += "\n---\n" return response @@ -174,7 +180,7 @@ def display( if self.config.infer_hgvs_c: var_c_or_n = self._infer_hgvs_c(var_g) - return self.create_repre(var_g, var_c_or_n, display_start, display_end, data_compiler) + return self.create_repre(var_g, var_c_or_n, display_start, display_end, self.data_compiler) def create_repre( self, @@ -191,10 +197,13 @@ def create_repre( fully_justified_var = data.fully_justified if self.config.showLegend: - head = "hgvs : " + head = "hgvs_g : " + head_c = "hgvs_c : " + head_n = "hgvs_n : " + head_p = "hgvs_p : " refa = "ref>alt : " else: - head = refa = "" + head = head_c = head_p = refa = "" if self.config.useColor: var_g_print = self._colorize_hgvs(str(var_g)) @@ -207,7 +216,18 @@ def create_repre( var_c_print = self._colorize_hgvs(str(var_c_or_n)) else: var_c_print = str(var_c_or_n) - var_str += head + var_c_print + "\n" + if data.var_c_or_n.type == 'c': + var_str += head_c + elif data.var_c_or_n.type == 'n': + var_str += head_n + var_str += var_c_print + "\n" + + if data.var_p: + if self.config.useColor: + var_p_print = self._colorize_hgvs(str(data.var_p)) + else: + var_p_print = str(data.var_p) + var_str += head_p + var_p_print + "\n" renderer_cls = [ChrPositionInfo, ChrRuler, ChromSeqRendered] @@ -248,8 +268,9 @@ def create_repre( ) fully_justified_str = fully_justified_renderer.display(data) - # var_str += shuffled_seq_header + left_shuffled_str + "\n" - # var_str += shuffled_seq_header + right_shuffled_str + "\n" + if self.config.showAllShuffleableRegions : + var_str += shuffled_seq_header + left_shuffled_str + "\n" + var_str += shuffled_seq_header + right_shuffled_str + "\n" var_str += shuffled_seq_header + fully_justified_str + "\n" else: diff --git a/src/hgvs/repeats.py b/src/hgvs/repeats.py index 4d2104c8..cdfb511e 100644 --- a/src/hgvs/repeats.py +++ b/src/hgvs/repeats.py @@ -4,10 +4,7 @@ import re from typing import Optional -from hgvs.dataproviders.interface import Interface -from hgvs.pretty.datacompiler import DataCompiler -from hgvs.pretty.models import PrettyConfig, VariantCoords -from hgvs.sequencevariant import SequenceVariant +from hgvs.pretty.models import VariantCoords def count_pattern_occurence(pattern, string): @@ -59,22 +56,18 @@ def _get_repeat_unit(self, fs: VariantCoords) -> Optional[str]: # seems we cant shuffle. is an SVN or delins return None - print(len(fs.alt)) - if len(fs.alt) > 0: if fs.alt in fs.ref: if fs.ref.startswith(fs.alt): d = fs.ref[len(fs.alt) :] if count_pattern_occurence(d, fs.ref) > 1: c, u = count_repetitive_units(d) - print (f"found repeat {d} has {c} smaller repeats {u}") return u elif fs.ref in fs.alt: if fs.alt.startswith(fs.ref): d = fs.alt[len(fs.ref) :] if count_pattern_occurence(d, fs.ref) > 1: c, u = count_repetitive_units(d) - print (f"found repeat {d} has {c} smaller repeats {u}") return u return None diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index ec65bdbc..06b196a6 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -44,8 +44,9 @@ def test_var_c1_forward(self): print(result) result = result.split("\n") expected_str = ( - "hgvs : NC_000021.8:g.46020522=\n" - + "hgvs : NM_198689.2:c.1=\n" + "hgvs_g : NC_000021.8:g.46020522=\n" + + "hgvs_c : NM_198689.2:c.1=\n" + + "hgvs_p : NP_941962.1:p.Met1?\n" + " : 46,020,510 46,020,530\n" + "chrom pos : . | . | . | . | \n" + "seq -> : CCTCCAGTTCAATCCCCAGCATGGCCGCGTCCACTATGTCT\n" @@ -70,8 +71,9 @@ def test_var_c1_reverse(self): print(result) result = result.split("\n") expected_str = ( - "hgvs : NC_000007.13:g.36763753=\n" - + "hgvs : NM_001177507.2:c.1=\n" + "hgvs_g : NC_000007.13:g.36763753=\n" + + "hgvs_c : NM_001177507.2:c.1=\n" + + "hgvs_p : NP_001170978.1:p.Met1?\n" + " : 36,763,740 36,763,760\n" + "chrom pos : . | . | . | . | \n" + "seq -> : GATTTTCCAGGGGGACTGCATCTCCGAGCTATGCACCCCAA\n" @@ -96,8 +98,9 @@ def test_var_g_substitution(self): print(result) result = result.split("\n") expected_str = ( - "hgvs : NC_000007.13:g.36561662C>T\n" - + "hgvs : NM_001177507.2:c.1486G>A\n" + "hgvs_g : NC_000007.13:g.36561662C>T\n" + + "hgvs_c : NM_001177507.2:c.1486G>A\n" + + "hgvs_p : NP_001170978.1:p.(Gly496Arg)\n" + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : TACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" @@ -122,8 +125,9 @@ def test_var_g_ins(self): print(result) result = result.split("\n") expected_str = ( - "hgvs : NC_000005.10:g.123346517_123346518insATTA\n" - + "hgvs : NM_001166226.1:c.*1_*2insTAAT\n" + "hgvs_g : NC_000005.10:g.123346517_123346518insATTA\n" + + "hgvs_c : NM_001166226.1:c.*1_*2insTAAT\n" + + "hgvs_p : NP_001159698.1:p.?\n" + self.atta_expected_results ).split("\n") for r, e in zip(result, expected_str): @@ -137,8 +141,9 @@ def test_var_g_dup(self): print(result) result = result.split("\n") expected_str = ( - "hgvs : NC_000005.10:g.123346522_123346525dup\n" - + "hgvs : NM_001166226.1:c.2880_2883dup\n" + "hgvs_g : NC_000005.10:g.123346522_123346525dup\n" + + "hgvs_c : NM_001166226.1:c.2880_2883dup\n" + + "hgvs_p : NP_001159698.1:p.(=)\n" + self.atta_expected_results ).split("\n") for r, e in zip(result, expected_str): @@ -153,8 +158,9 @@ def test_insertion(self): print(result) result = result.split("\n") expected_str = ( - "hgvs : NC_000004.11:g.1643284_1643285insTCGTCATCG\n" - + "hgvs : NM_001174070.2:c.932_933insCGATGACGA\n" + "hgvs_g : NC_000004.11:g.1643284_1643285insTCGTCATCG\n" + + "hgvs_c : NM_001174070.2:c.932_933insCGATGACGA\n" + + "hgvs_p : NP_001167541.1:p.(Asp309_Asp311dup)\n" + " : 1,643,270 1,643,280 1,643,290 1,643,300 1,643,310\n" + "chrom pos : . | . | . | . | . | \n" + "seq -> : TCACTGGGGTGTCATCCTCATCGTCATCTTCGTAATTGAGGGAGCAAA\n" @@ -179,8 +185,9 @@ def test_insertion_size_1(self): result = result.split("\n") expected_str = ( - "hgvs : NC_000007.13:g.36561662_36561663insT\n" - + "hgvs : NM_001177507.2:c.1485_1486insA\n" + "hgvs_g : NC_000007.13:g.36561662_36561663insT\n" + + "hgvs_c : NM_001177507.2:c.1485_1486insA\n" + + "hgvs_p : NP_001170978.1:p.(Gly496ArgfsTer39)\n" + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" @@ -204,8 +211,9 @@ def test_del_2bp(self): result = result.split("\n") expected_str = ( - "hgvs : NC_000007.13:g.36561662_36561663del\n" - + "hgvs : NM_001177507.2:c.1485_1486del\n" + "hgvs_g : NC_000007.13:g.36561662_36561663del\n" + + "hgvs_c : NM_001177507.2:c.1485_1486del\n" + + "hgvs_p : NP_001170978.1:p.(Asp495GlufsTer39)\n" + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : TACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCTG\n" @@ -229,8 +237,9 @@ def test_del_1bp_shuffleable(self): result = result.split("\n") expected_str = ( - "hgvs : NC_000007.13:g.36561662del\n" - + "hgvs : NM_001177507.2:c.1487del\n" + "hgvs_g : NC_000007.13:g.36561662del\n" + + "hgvs_c : NM_001177507.2:c.1487del\n" + + "hgvs_p : NP_001170978.1:p.(Gly496AspfsTer122)\n" + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : TTACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" @@ -255,8 +264,9 @@ def test_del_1bp(self): result = result.split("\n") expected_str = ( - "hgvs : NC_000007.13:g.36561663del\n" - + "hgvs : NM_001177507.2:c.1485del\n" + "hgvs_g : NC_000007.13:g.36561663del\n" + + "hgvs_c : NM_001177507.2:c.1485del\n" + + "hgvs_p : NP_001170978.1:p.(Asp495GlufsTer123)\n" + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCTG\n" @@ -280,8 +290,9 @@ def test_dup_1bp_shuffleable(self): result = result.split("\n") expected_str = ( - "hgvs : NC_000007.13:g.36561662dup\n" - + "hgvs : NM_001177507.2:c.1487dup\n" + "hgvs_g : NC_000007.13:g.36561662dup\n" + + "hgvs_c : NM_001177507.2:c.1487dup\n" + + "hgvs_p : NP_001170978.1:p.(Phe497IlefsTer38)\n" + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : TTACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" @@ -306,8 +317,9 @@ def test_dup_1bp(self): result = result.split("\n") expected_str = ( - "hgvs : NC_000007.13:g.36561663dup\n" - + "hgvs : NM_001177507.2:c.1485dup\n" + "hgvs_g : NC_000007.13:g.36561663dup\n" + + "hgvs_c : NM_001177507.2:c.1485dup\n" + + "hgvs_p : NP_001170978.1:p.(Gly496TrpfsTer39)\n" + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCTG\n" @@ -333,8 +345,9 @@ def test_identity(self): result = result.split("\n") expected_str = ( - "hgvs : NC_000007.13:g.36561663=\n" - + "hgvs : NM_001177507.2:c.1485=\n" + "hgvs_g : NC_000007.13:g.36561663=\n" + + "hgvs_c : NM_001177507.2:c.1485=\n" + + "hgvs_p : NP_001170978.1:p.(Asp495=)\n" + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCTG\n" @@ -362,8 +375,9 @@ def test_tiny(self): result = result.split("\n") expected_str = ( - "hgvs : NC_000005.10:g.123346517_123346518insATTA\n" - + "hgvs : NM_001166226.1:c.*1_*2insTAAT\n" + "hgvs_g : NC_000005.10:g.123346517_123346518insATTA\n" + + "hgvs_c : NM_001166226.1:c.*1_*2insTAAT\n" + + "hgvs_p : NP_001159698.1:p.?\n" + " : 123,346,520\n" + "chrom pos : | .\n" + "seq -> : ATTAATTA\n" @@ -405,6 +419,7 @@ def test_hgvs_c(self): expected_str = ( "NC_000012.11:g.33049660_33049680dup\n" + "NM_004572.3:c.-9_12dup\n" + + "NP_004563.2:p.(Met1_Pro4dup)\n" + " 33,049,650 33,049,670 33,049,690 33,049,710 33,049,730 33,049,750 33,049,770 33,049,790\n" + " . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |\n" + "CTGGGGCGCCGGGGGCTGCCATGGGGCCGGTGGGGGCGACCGAGCTGCTCGCCTGCCTCTGGACTCGCGGGCGAAGCCGCCACGGAGCTGGGGGCGCTGGCGCGAGCCCCGCCCCGCTCGAGTCCGGCCCCGCCCCTGGCCCGCCCC\n" @@ -432,8 +447,9 @@ def test_ref_disagree(self): # note the X in the transscript sequence expected_str = ( - "hgvs : NC_000001.10:g.154574820=\n" - + "hgvs : NM_001111.4:c.298G>A\n" + "hgvs_g : NC_000001.10:g.154574820=\n" + + "hgvs_c : NM_001111.4:c.298G>A\n" + + "hgvs_p : NP_001102.2:p.(Gly100Arg)\n" + " : 154,574,800 154,574,820 154,574,840\n" + "chrom pos : | . | . | . | . |\n" + "seq -> : TCTCTGGAGCCCCTGACTTCTGAGATGCACGCCCCTGGGGA\n" @@ -465,8 +481,9 @@ def test_ref_disagree_ref_ins(self): print(result) result = result.split("\n") expected_str = ( - "hgvs : NC_000021.8:g.46020668_46020682del\n" - "hgvs : NM_198689.2:c.124_135=\n" + "hgvs_g : NC_000021.8:g.46020668_46020682del\n" + "hgvs_c : NM_198689.2:c.124_135=\n" + "hgvs_p : NP_941962.1:p.(Cys42=)\n" " : 46,020,630 46,020,650 46,020,670 46,020,690 46,020,710\n" "chrom pos : | . | . | . | . | . | . | . | . | . | \n" "seq -> : CGACTGCCCAGAGAGCTGCTGCGAGCCCCCCTGCTGCGCCCCCAGCTGCTGCGCCCCGGCCCCCTGCCTGAGCCTGGTCTGCACCCCAGTGAGCCGT\n" @@ -498,8 +515,9 @@ def test_ref_disagree_del(self): result = result.split("\n") expected_str = ( - "hgvs : NC_000002.11:g.96780987_96780997del\n" - + "hgvs : NM_000682.6:c.901_911del\n" + "hgvs_g : NC_000002.11:g.96780987_96780997del\n" + + "hgvs_c : NM_000682.6:c.901_911del\n" + + "hgvs_p : NP_000673.2:p.(Glu301GlyfsTer7)\n" + " : 96,780,960 96,780,980 96,781,000 96,781,020\n" + "chrom pos : | . | . | . | . _________ | . | . | . | . \n" + "seq -> : TGCCTGGGGTTCACACTCTTCCTCCTCCTCCTCCTCCTCTTC.........GGCTTCATCCTCTGGAGATGCCCCACAAACACCCTCCTTC\n" @@ -542,6 +560,7 @@ def test_ruler(self): expected_str = ( "NC_000001.10:g.154574820=\n" + "NM_001111.4:c.298G>A\n" + + "NP_001102.2:p.(Gly100Arg)\n" + "154,574,800 154,574,820 154,574,840\n" + "| . | . | . | . |\n" + "TCTCTGGAGCCCCTGACTTCTGAGATGCACGCCCCTGGGGA\n" @@ -566,8 +585,8 @@ def test_rna_coding(self): result = pp.display(var_n) print(result) expected_str = ( - "hgvs : NC_000001.10:g.167905930G>A\n" - + "hgvs : NR_146230.2:n.10G>A\n" + "hgvs_g : NC_000001.10:g.167905930G>A\n" + + "hgvs_n : NR_146230.2:n.10G>A\n" + " : 167,905,910 167,905,930 167,905,950\n" + "chrom pos : | . | . | . | . |\n" + "seq -> : TGCTGATCTTTGGATGTTCTGGTTAGTCTAAGAAGGAGAGT\n" From a31cd26e2f61597f6f67197c19c6cdd858eab17a Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sat, 29 Jun 2024 23:42:31 -0700 Subject: [PATCH 14/31] feat(ci): trying to get pytest to work again --- .github/workflows/python-package.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 011055c0..c26c0710 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -58,6 +58,7 @@ jobs: - name: Install dependencies run: | pip install -e .[dev] + pip install pytest pytest-cov - name: Test with pytest run: | From 30addf27099e4d5b5dde620fb837b4c806cc8288 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sun, 30 Jun 2024 22:36:32 -0700 Subject: [PATCH 15/31] feat(cleanup): removing cache from test_repeats. They are creating too big of a cache. --- src/hgvs/pretty/datacompiler.py | 4 +- .../pretty/renderer/prot_mapping_renderer.py | 10 ++-- .../pretty/renderer/prot_ruler_renderer.py | 7 +-- src/hgvs/pretty/renderer/tx_alig_renderer.py | 6 +- src/hgvs/pretty_print.py | 35 +++++++---- src/hgvs/repeats.py | 6 +- tests/test_pretty_print.py | 18 +++--- tests/test_repeats.py | 59 ++++++++----------- 8 files changed, 72 insertions(+), 73 deletions(-) diff --git a/src/hgvs/pretty/datacompiler.py b/src/hgvs/pretty/datacompiler.py index 95781ed3..45e770d2 100644 --- a/src/hgvs/pretty/datacompiler.py +++ b/src/hgvs/pretty/datacompiler.py @@ -187,7 +187,7 @@ def data( seq_end = display_end if var_c_or_n is not None: - tx_ac = var_c_or_n.ac + tx_ac = var_c_or_n.ac else: tx_ac = "" # can't show transcript , since there is none. @@ -228,7 +228,7 @@ def data( var_p = None reference_data = None - position_details:List[PositionDetail] = [] + position_details: List[PositionDetail] = [] prev_mapped_pos = None prev_c_pos = -1 prev_n_pos = -1 diff --git a/src/hgvs/pretty/renderer/prot_mapping_renderer.py b/src/hgvs/pretty/renderer/prot_mapping_renderer.py index 9de2316c..decb0243 100644 --- a/src/hgvs/pretty/renderer/prot_mapping_renderer.py +++ b/src/hgvs/pretty/renderer/prot_mapping_renderer.py @@ -13,21 +13,20 @@ def display(self, data: VariantData) -> str: var_str = "" - count = -1 + count = -1 for pdata in data.position_details: count += 1 if not pdata.mapped_pos: - var_str += " " + var_str += " " continue prot_data = pdata.protein_data if not prot_data or prot_data.aa_pos < 0: - var_str +=" " + var_str += " " continue - - if len(var_str) > count: + if len(var_str) > count: continue aa_pos = prot_data.aa_pos @@ -39,7 +38,6 @@ def display(self, data: VariantData) -> str: var_str += "." continue - var_str += " " return var_str diff --git a/src/hgvs/pretty/renderer/prot_ruler_renderer.py b/src/hgvs/pretty/renderer/prot_ruler_renderer.py index a31433e2..3bcc48a4 100644 --- a/src/hgvs/pretty/renderer/prot_ruler_renderer.py +++ b/src/hgvs/pretty/renderer/prot_ruler_renderer.py @@ -28,19 +28,18 @@ def display(self, data: VariantData) -> str: prev_aa = -1 continue - if len(var_str) > count: continue aa_pos = prot_data.aa_pos - if aa_pos == prev_aa : - var_str += ' ' + if aa_pos == prev_aa: + var_str += " " continue prev_aa = aa_pos - if (aa_pos + 1) % 10 == 0: + if (aa_pos + 1) % 10 == 0: var_str += f"{aa_pos+1} " continue diff --git a/src/hgvs/pretty/renderer/tx_alig_renderer.py b/src/hgvs/pretty/renderer/tx_alig_renderer.py index 27c4a948..90418742 100644 --- a/src/hgvs/pretty/renderer/tx_alig_renderer.py +++ b/src/hgvs/pretty/renderer/tx_alig_renderer.py @@ -59,11 +59,11 @@ def display(self, data: VariantData) -> str: coding = True - if cig == "=": + if cig == "=": if c_pos: - bg_col = math.ceil((c_pos+1) / 3) % 2 + bg_col = math.ceil((c_pos + 1) / 3) % 2 elif n_pos: - bg_col = math.ceil((n_pos+1) / 3) % 2 + bg_col = math.ceil((n_pos + 1) / 3) % 2 else: var_str += " " continue diff --git a/src/hgvs/pretty_print.py b/src/hgvs/pretty_print.py index 96328ae2..adcb1e6d 100644 --- a/src/hgvs/pretty_print.py +++ b/src/hgvs/pretty_print.py @@ -16,8 +16,9 @@ from hgvs.pretty.renderer.tx_mapping_renderer import TxMappingRenderer from hgvs.pretty.renderer.tx_pos import TxRulerRenderer from hgvs.pretty.renderer.tx_ref_disagree_renderer import TxRefDisagreeRenderer -from hgvs.sequencevariant import SequenceVariant from hgvs.repeats import RepeatAnalyser +from hgvs.sequencevariant import SequenceVariant + TGREEN = "\033[32m" # Green Text TGREENBG = "\033[30;42m" TRED = "\033[31m" # Red Text @@ -131,9 +132,10 @@ def _colorize_hgvs(self, hgvs_str: str) -> str: return var_str + def get_hgvs_names( + self, sv: SequenceVariant, tx_ac: str = None + ) -> Tuple[SequenceVariant, SequenceVariant]: - def get_hgvs_names(self, sv: SequenceVariant, tx_ac:str =None)->Tuple[SequenceVariant, SequenceVariant]: - var_c_or_n = None if sv.type == "g": var_g = sv @@ -146,7 +148,7 @@ def get_hgvs_names(self, sv: SequenceVariant, tx_ac:str =None)->Tuple[SequenceVa # map back to genome var_g = self._map_to_chrom(sv) var_c_or_n = sv - + return var_g, var_c_or_n def display( @@ -158,7 +160,7 @@ def display( ) -> str: """Takes a variant and prints the genomic context around it.""" - var_g, var_c_or_n = self.get_hgvs_names(sv,tx_ac) + var_g, var_c_or_n = self.get_hgvs_names(sv, tx_ac) self.data_compiler = DataCompiler(config=self.config) @@ -180,7 +182,9 @@ def display( if self.config.infer_hgvs_c: var_c_or_n = self._infer_hgvs_c(var_g) - return self.create_repre(var_g, var_c_or_n, display_start, display_end, self.data_compiler) + return self.create_repre( + var_g, var_c_or_n, display_start, display_end, self.data_compiler + ) def create_repre( self, @@ -197,7 +201,7 @@ def create_repre( fully_justified_var = data.fully_justified if self.config.showLegend: - head = "hgvs_g : " + head = "hgvs_g : " head_c = "hgvs_c : " head_n = "hgvs_n : " head_p = "hgvs_p : " @@ -216,9 +220,9 @@ def create_repre( var_c_print = self._colorize_hgvs(str(var_c_or_n)) else: var_c_print = str(var_c_or_n) - if data.var_c_or_n.type == 'c': - var_str += head_c - elif data.var_c_or_n.type == 'n': + if data.var_c_or_n.type == "c": + var_str += head_c + elif data.var_c_or_n.type == "n": var_str += head_n var_str += var_c_print + "\n" @@ -268,7 +272,7 @@ def create_repre( ) fully_justified_str = fully_justified_renderer.display(data) - if self.config.showAllShuffleableRegions : + if self.config.showAllShuffleableRegions: var_str += shuffled_seq_header + left_shuffled_str + "\n" var_str += shuffled_seq_header + right_shuffled_str + "\n" var_str += shuffled_seq_header + fully_justified_str + "\n" @@ -276,7 +280,14 @@ def create_repre( else: var_str += shuffled_seq_header + left_shuffled_str + "\n" - renderers_cls = [TxAligRenderer, TxMappingRenderer, TxRulerRenderer, ProtSeqRenderer, ProtMappingRenderer, ProtRulerRenderer ] + renderers_cls = [ + TxAligRenderer, + TxMappingRenderer, + TxRulerRenderer, + ProtSeqRenderer, + ProtMappingRenderer, + ProtRulerRenderer, + ] for cls in renderers_cls: renderer = cls(self.config, data.strand) diff --git a/src/hgvs/repeats.py b/src/hgvs/repeats.py index cdfb511e..c3cc146e 100644 --- a/src/hgvs/repeats.py +++ b/src/hgvs/repeats.py @@ -12,15 +12,16 @@ def count_pattern_occurence(pattern, string): matches = re.findall(pattern, string) return len(matches) + def count_repetitive_units(s): length = len(s) - + for i in range(1, length + 1): unit = s[:i] if length % i == 0: if unit * (length // i) == s: return length // i, unit - + return 1, s @@ -28,7 +29,6 @@ class RepeatAnalyser: def __init__(self, fs: VariantCoords) -> None: - self.is_repeat = False self.ref_count = 0 self.alt_count = 0 diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index 06b196a6..d94f8633 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -164,7 +164,7 @@ def test_insertion(self): + " : 1,643,270 1,643,280 1,643,290 1,643,300 1,643,310\n" + "chrom pos : . | . | . | . | . | \n" + "seq -> : TCACTGGGGTGTCATCCTCATCGTCATCTTCGTAATTGAGGGAGCAAA\n" - + "region : |------| \n" + + "region : |------| \n" + "tx seq <- : AGTGACCCCACAGTAGGAGTAGCAGTAGAAGCATTAACTCCCTCGTTT\n" + "tx pos : | . | . | . | . | .\n" + " : 950 940 930 920 910\n" @@ -191,7 +191,7 @@ def test_insertion_size_1(self): + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" - + "region : ^^ \n" + + "region : ^^ \n" + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + "tx pos : | . | . | . | \n" + " : 1500 1490 1480 1470\n" @@ -217,7 +217,7 @@ def test_del_2bp(self): + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : TACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCTG\n" - + "region : xx \n" + + "region : xx \n" + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGAC\n" + "tx pos : | . | . | . | .\n" + " : 1500 1490 1480 1470\n" @@ -243,7 +243,7 @@ def test_del_1bp_shuffleable(self): + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : TTACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCT\n" - + "region : xx \n" + + "region : xx \n" + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGA\n" + "tx pos : | . | . | . | \n" + " : 1500 1490 1480 1470\n" @@ -264,7 +264,7 @@ def test_del_1bp(self): result = result.split("\n") expected_str = ( - "hgvs_g : NC_000007.13:g.36561663del\n" + "hgvs_g : NC_000007.13:g.36561663del\n" + "hgvs_c : NM_001177507.2:c.1485del\n" + "hgvs_p : NP_001170978.1:p.(Asp495GlufsTer123)\n" + " : 36,561,650 36,561,670\n" @@ -329,7 +329,7 @@ def test_dup_1bp(self): + " : 1500 1490 1480 1470\n" + "aa seq <- : GluAsnProHisPheGlyAspValProGluIleLeuGln\n" + "aa pos : ||| ... ||| \n" - + " : 500 490 \n" + + " : 500 490 \n" + "ref>alt : A>AA\n" ).split("\n") for r, e in zip(result, expected_str): @@ -351,7 +351,7 @@ def test_identity(self): + " : 36,561,650 36,561,670\n" + "chrom pos : . | . | . | . | \n" + "seq -> : ACCTCGTTGGGGTGGAATCCATCCACGGGCTCGATGAGCTG\n" - + "region : = \n" + + "region : = \n" + "tx seq <- : GAGCAACCCCACCTTAGGTAGGTGCCCGAGCTACTCGAC\n" + "tx pos : | . | . | . | .\n" + " : 1500 1490 1480 1470\n" @@ -423,7 +423,7 @@ def test_hgvs_c(self): + " 33,049,650 33,049,670 33,049,690 33,049,710 33,049,730 33,049,750 33,049,770 33,049,790\n" + " . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |\n" + "CTGGGGCGCCGGGGGCTGCCATGGGGCCGGTGGGGGCGACCGAGCTGCTCGCCTGCCTCTGGACTCGCGGGCGAAGCCGCCACGGAGCTGGGGGCGCTGGCGCGAGCCCCGCCCCGCTCGAGTCCGGCCCCGCCCCTGGCCCGCCCC\n" - + " |-------------------------| \n" + + " |-------------------------| \n" + "GACCCCGCGGCCCCCGACGGTAccccggccacccccgctggctcgacgagcggacggagacctgagcgcccgcttcggcggtgcctcgacccccgcgaccgcgctcggggcggggcgagctcaggccggggcgggga \n" + " | . | . | . | . | . | . | . | . | . | . | . | . | . | . \n" + " 20 10 1 -10 -20 -30 -40 -50 -60 -70 -80 -90 -100 -110\n" @@ -591,7 +591,7 @@ def test_rna_coding(self): + "chrom pos : | . | . | . | . |\n" + "seq -> : TGCTGATCTTTGGATGTTCTGGTTAGTCTAAGAAGGAGAGT\n" + "seq <- : ACGACTAGAAACCTACAAGACCAATCAGATTCTTCCTCTCA\n" - + "region : A \n" + + "region : A \n" + "tx seq -> : GATGTTCTGGTTAGTCTAAGAAGGAGAGT\n" + "tx pos : . | . | . |\n" + " : 10 20 30\n" diff --git a/tests/test_repeats.py b/tests/test_repeats.py index df28c562..74c37b7f 100644 --- a/tests/test_repeats.py +++ b/tests/test_repeats.py @@ -1,69 +1,60 @@ -import os -from unittest import TestCase +import unittest -from hgvs.pretty.datacompiler import DataCompiler -from hgvs.pretty.models import PrettyConfig -import pytest from parameterized import parameterized -from support import CACHE import hgvs - +import hgvs.dataproviders +from hgvs.pretty.datacompiler import DataCompiler +from hgvs.pretty.models import PrettyConfig from hgvs.repeats import RepeatAnalyser, count_repetitive_units -class TestRepeats(TestCase): - @classmethod - def setUpClass(cls): - cls.hdp = hgvs.dataproviders.uta.connect( - mode=os.environ.get("HGVS_CACHE_MODE", "run"), cache=CACHE - ) - cls.vm = hgvs.variantmapper.VariantMapper(cls.hdp) - cls.hp = hgvs.parser.Parser() +class TestRepeats(unittest.TestCase): + @classmethod + def setUpClass(self): + self.hdp = hgvs.dataproviders.uta.connect() @parameterized.expand( - [ - ('abc' , 'abc' ,1), - ('CC' , 'C' , 2), - ('GAGA' , 'GA', 2), - ('', '' , 1), - ('AAAAAB', 'AAAAAB', 1), - ('ABBCABBCABBC', 'ABBC', 3) - ] + [ + ("abc", "abc", 1), + ("CC", "C", 2), + ("GAGA", "GA", 2), + ("", "", 1), + ("AAAAAB", "AAAAAB", 1), + ("ABBCABBCABBC", "ABBC", 3), + ] ) - def test_count_repetitive_units(self, s: str, u:str, c: int): + def test_count_repetitive_units(self, s: str, u: str, c: int): observed_c, observed_u = count_repetitive_units(s) - + self.assertEqual(u, observed_u) self.assertEqual(c, observed_c) - - @parameterized.expand( [ ("NC_000019.10:g.45770205del", True, "C", 6, 5, "C[6]>C[5]"), ("NC_000019.10:g.45770204_45770205del", True, "C", 6, 4, "C[6]>C[4]"), ("NC_000019.10:g.45770205insC", True, "C", 6, 7, "C[6]>C[7]"), ("NC_000019.10:g.45770206del", False, None, 0, 0, "A>"), - ("NC_000007.13:g.36561662dup", True, "C", 2, 3, "C[2]>C[3]"), + ("NC_000007.13:g.36561662dup", True, "C", 2, 3, "C[2]>C[3]"), ] ) def test_homopolymer(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, s): - var_g = self.hp.parse_hgvs_variant(hgvs_g) + hp = hgvs.parser.Parser() + var_g = hp.parse_hgvs_variant(hgvs_g) config = PrettyConfig(self.hdp, None, None) dc = DataCompiler(config) fs = dc.get_shuffled_variant(var_g, 0) ra = RepeatAnalyser(fs) - + self.assertEqual(s, str(ra)) self.assertEqual(is_repeat, ra.is_repeat) self.assertEqual(repeat_unit, ra.repeat_unit) self.assertEqual(ref_count, ra.ref_count) self.assertEqual(alt_count, ra.alt_count) - @parameterized.expand( [ @@ -85,12 +76,12 @@ def test_homopolymer(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, ), ("NC_000005.10:g.123346517_123346518insATTA", True, "ATTA", 2, 3, "ATTA[2]>ATTA[3]"), ("NC_000005.10:g.123346522_123346525dup", True, "ATTA", 2, 3, "ATTA[2]>ATTA[3]"), - ("NC_000019.10:g.45770210_45770212del", True, 'GCA', 20, 19, "GCA[20]>GCA[19]") + ("NC_000019.10:g.45770210_45770212del", True, "GCA", 20, 19, "GCA[20]>GCA[19]"), ] ) def test_repeats(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, s): - - var_g = self.hp.parse_hgvs_variant(hgvs_g) + hp = hgvs.parser.Parser() + var_g = hp.parse_hgvs_variant(hgvs_g) config = PrettyConfig(self.hdp, None, None) dc = DataCompiler(config) fs = dc.get_shuffled_variant(var_g, 0) From bc483b57184302be8e86d40de619ac9465862376 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Mon, 12 Aug 2024 19:49:48 -0700 Subject: [PATCH 16/31] cleanup --- src/hgvs/pretty/models.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/hgvs/pretty/models.py b/src/hgvs/pretty/models.py index dd32f1f7..d3a8cef5 100644 --- a/src/hgvs/pretty/models.py +++ b/src/hgvs/pretty/models.py @@ -1,12 +1,11 @@ from dataclasses import dataclass from typing import List -import hgvs from hgvs.alignmentmapper import AlignmentMapper from hgvs.assemblymapper import AssemblyMapper from hgvs.location import Interval from hgvs.sequencevariant import SequenceVariant - +from hgvs.dataproviders.interface import Interface @dataclass(eq=True, repr=True, frozen=True, order=True) class VariantCoords: @@ -31,7 +30,7 @@ class ProteinData: @classmethod def get_header(cls) -> str: - return f"c_pos\ttlc\taa_char\tc_pos\taa_pos" + return "c_pos\ttlc\taa_char\tc_pos\taa_pos" def __repr__(self) -> str: return f"{self.c_pos}\t{self.tlc}\t{self.aa_char}\t{self.c_pos %3}\t{self.aa_pos}" @@ -97,7 +96,7 @@ class VariantData: class PrettyConfig: """acontainer for various configurations.""" - hdp: hgvs.dataproviders.interface.Interface + hdp: Interface am37: AssemblyMapper am38: AssemblyMapper padding_left: int = 20 From cf22aae6672b570bf1882d937b307823a42756f5 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sun, 1 Sep 2024 22:34:32 -0700 Subject: [PATCH 17/31] #741 small refactoring to update directory structure, based on PR comments --- src/hgvs/easy.py | 2 +- .../chrom_seq_renderer.py | 4 +- .../chrom_seq_reverse_renderer.py | 4 +- src/hgvs/pretty/console/constants.py | 14 +++++ .../pretty/{renderer => console}/pos_info.py | 2 +- .../prot_mapping_renderer.py | 2 +- .../prot_ruler_renderer.py | 2 +- .../prot_seq_renderer.py | 4 +- src/hgvs/pretty/console/renderer.py | 32 +++++++++++ .../pretty/{renderer => console}/ruler.py | 2 +- .../{renderer => console}/shuffled_variant.py | 4 +- .../{renderer => console}/tx_alig_renderer.py | 4 +- .../tx_mapping_renderer.py | 2 +- .../pretty/{renderer => console}/tx_pos.py | 2 +- .../tx_ref_disagree_renderer.py | 4 +- src/hgvs/pretty/datacompiler.py | 2 +- src/hgvs/pretty/models.py | 2 +- src/hgvs/{ => pretty}/pretty_print.py | 56 ++++++------------- src/hgvs/pretty/renderer/renderer.py | 15 ----- tests/test_pretty_print.py | 4 +- 20 files changed, 84 insertions(+), 79 deletions(-) rename src/hgvs/pretty/{renderer => console}/chrom_seq_renderer.py (87%) rename src/hgvs/pretty/{renderer => console}/chrom_seq_reverse_renderer.py (88%) create mode 100644 src/hgvs/pretty/console/constants.py rename src/hgvs/pretty/{renderer => console}/pos_info.py (92%) rename src/hgvs/pretty/{renderer => console}/prot_mapping_renderer.py (94%) rename src/hgvs/pretty/{renderer => console}/prot_ruler_renderer.py (95%) rename src/hgvs/pretty/{renderer => console}/prot_seq_renderer.py (93%) create mode 100644 src/hgvs/pretty/console/renderer.py rename src/hgvs/pretty/{renderer => console}/ruler.py (92%) rename src/hgvs/pretty/{renderer => console}/shuffled_variant.py (94%) rename src/hgvs/pretty/{renderer => console}/tx_alig_renderer.py (95%) rename src/hgvs/pretty/{renderer => console}/tx_mapping_renderer.py (97%) rename src/hgvs/pretty/{renderer => console}/tx_pos.py (95%) rename src/hgvs/pretty/{renderer => console}/tx_ref_disagree_renderer.py (91%) rename src/hgvs/{ => pretty}/pretty_print.py (83%) delete mode 100644 src/hgvs/pretty/renderer/renderer.py diff --git a/src/hgvs/easy.py b/src/hgvs/easy.py index f9bb8e15..66d82a90 100644 --- a/src/hgvs/easy.py +++ b/src/hgvs/easy.py @@ -37,7 +37,7 @@ from hgvs.dataproviders.uta import connect from hgvs.normalizer import Normalizer from hgvs.parser import Parser -from hgvs.pretty_print import PrettyPrint +from hgvs.pretty.pretty_print import PrettyPrint from hgvs.validator import Validator from hgvs.variantmapper import VariantMapper diff --git a/src/hgvs/pretty/renderer/chrom_seq_renderer.py b/src/hgvs/pretty/console/chrom_seq_renderer.py similarity index 87% rename from src/hgvs/pretty/renderer/chrom_seq_renderer.py rename to src/hgvs/pretty/console/chrom_seq_renderer.py index 8204f518..0278a682 100644 --- a/src/hgvs/pretty/renderer/chrom_seq_renderer.py +++ b/src/hgvs/pretty/console/chrom_seq_renderer.py @@ -1,5 +1,5 @@ from hgvs.pretty.models import VariantData -from hgvs.pretty.renderer.renderer import BasicRenderer +from hgvs.pretty.console.renderer import BasicRenderer class ChromSeqRendered(BasicRenderer): @@ -9,7 +9,7 @@ def legend(self) -> str: def display(self, data: VariantData) -> str: """colors the ref sequences with adenine (A, green), thymine (T, red), cytosine (C, yellow), and guanine (G, blue)""" - from hgvs.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW + from hgvs.pretty.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW var_seq = "" for p in data.position_details: diff --git a/src/hgvs/pretty/renderer/chrom_seq_reverse_renderer.py b/src/hgvs/pretty/console/chrom_seq_reverse_renderer.py similarity index 88% rename from src/hgvs/pretty/renderer/chrom_seq_reverse_renderer.py rename to src/hgvs/pretty/console/chrom_seq_reverse_renderer.py index 9516a1c0..01effa76 100644 --- a/src/hgvs/pretty/renderer/chrom_seq_reverse_renderer.py +++ b/src/hgvs/pretty/console/chrom_seq_reverse_renderer.py @@ -1,7 +1,7 @@ from bioutils.sequences import reverse_complement from hgvs.pretty.models import VariantData -from hgvs.pretty.renderer.renderer import BasicRenderer +from hgvs.pretty.console.renderer import BasicRenderer class ChromReverseSeqRendered(BasicRenderer): @@ -11,7 +11,7 @@ def legend(self) -> str: def display(self, data: VariantData) -> str: """colors the ref sequences with adenine (A, green), thymine (T, red), cytosine (C, yellow), and guanine (G, blue)""" - from hgvs.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW + from hgvs.pretty.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW var_seq = "" for p in data.position_details: diff --git a/src/hgvs/pretty/console/constants.py b/src/hgvs/pretty/console/constants.py new file mode 100644 index 00000000..7e93ec27 --- /dev/null +++ b/src/hgvs/pretty/console/constants.py @@ -0,0 +1,14 @@ +### Color constants for rendering text on the console + +TGREEN = "\033[32m" # Green Text +TGREENBG = "\033[30;42m" +TRED = "\033[31m" # Red Text +TREDBG = "\033[30;41m" +TBLUE = "\033[34m" # Blue Text +TBLUEBG = "\033[30;44m" +TPURPLE = "\033[35m" # Purple Text +TPURPLEBG = "\033[30;45m" +TYELLOW = "\033[33m" # Yellow Text +TYELLOWBG = "\033[30;43m" + +ENDC = "\033[m" # reset to the defaults \ No newline at end of file diff --git a/src/hgvs/pretty/renderer/pos_info.py b/src/hgvs/pretty/console/pos_info.py similarity index 92% rename from src/hgvs/pretty/renderer/pos_info.py rename to src/hgvs/pretty/console/pos_info.py index 3a0b8f51..a10f2a9f 100644 --- a/src/hgvs/pretty/renderer/pos_info.py +++ b/src/hgvs/pretty/console/pos_info.py @@ -1,5 +1,5 @@ from hgvs.pretty.models import VariantData -from hgvs.pretty.renderer.renderer import BasicRenderer +from hgvs.pretty.console.renderer import BasicRenderer class ChrPositionInfo(BasicRenderer): diff --git a/src/hgvs/pretty/renderer/prot_mapping_renderer.py b/src/hgvs/pretty/console/prot_mapping_renderer.py similarity index 94% rename from src/hgvs/pretty/renderer/prot_mapping_renderer.py rename to src/hgvs/pretty/console/prot_mapping_renderer.py index decb0243..e4ccf512 100644 --- a/src/hgvs/pretty/renderer/prot_mapping_renderer.py +++ b/src/hgvs/pretty/console/prot_mapping_renderer.py @@ -1,5 +1,5 @@ from hgvs.pretty.models import VariantData -from hgvs.pretty.renderer.renderer import BasicRenderer +from hgvs.pretty.console.renderer import BasicRenderer class ProtMappingRenderer(BasicRenderer): diff --git a/src/hgvs/pretty/renderer/prot_ruler_renderer.py b/src/hgvs/pretty/console/prot_ruler_renderer.py similarity index 95% rename from src/hgvs/pretty/renderer/prot_ruler_renderer.py rename to src/hgvs/pretty/console/prot_ruler_renderer.py index 3bcc48a4..402f7502 100644 --- a/src/hgvs/pretty/renderer/prot_ruler_renderer.py +++ b/src/hgvs/pretty/console/prot_ruler_renderer.py @@ -1,5 +1,5 @@ from hgvs.pretty.models import VariantData -from hgvs.pretty.renderer.renderer import BasicRenderer +from hgvs.pretty.console.renderer import BasicRenderer class ProtRulerRenderer(BasicRenderer): diff --git a/src/hgvs/pretty/renderer/prot_seq_renderer.py b/src/hgvs/pretty/console/prot_seq_renderer.py similarity index 93% rename from src/hgvs/pretty/renderer/prot_seq_renderer.py rename to src/hgvs/pretty/console/prot_seq_renderer.py index 046e9fad..79be30a2 100644 --- a/src/hgvs/pretty/renderer/prot_seq_renderer.py +++ b/src/hgvs/pretty/console/prot_seq_renderer.py @@ -1,5 +1,5 @@ from hgvs.pretty.models import VariantData -from hgvs.pretty.renderer.renderer import BasicRenderer +from hgvs.pretty.console.renderer import BasicRenderer class ProtSeqRenderer(BasicRenderer): @@ -14,7 +14,7 @@ def display(self, data: VariantData) -> str: if not data.var_c_or_n: return "" - from hgvs.pretty_print import ENDC, TGREEN, TRED + from hgvs.pretty.pretty_print import ENDC, TGREEN, TRED var_str = "" for pdata in data.position_details: diff --git a/src/hgvs/pretty/console/renderer.py b/src/hgvs/pretty/console/renderer.py new file mode 100644 index 00000000..b247c961 --- /dev/null +++ b/src/hgvs/pretty/console/renderer.py @@ -0,0 +1,32 @@ +from abc import ABC, abstractmethod + +from hgvs.pretty.console.constants import ENDC, TPURPLE, TYELLOW + + +def colorize_hgvs(hgvs_str: str) -> str: + """ Takes a string representation of a hgvs Sequence Variant and renders it with console colors. + """ + + spl = hgvs_str.split(":") + var_str = TPURPLE + spl[0] + ENDC + var_str += ":" + + sec = spl[1].split(".") + var_str += TYELLOW + sec[0] + ENDC + var_str += "." + var_str += sec[1] + + return var_str + +class BasicRenderer(ABC): + def __init__(self, config, orientation: int): + self.config = config + self.orientation = orientation + + @abstractmethod + def legend(self): + pass + + @abstractmethod + def display(self): + pass diff --git a/src/hgvs/pretty/renderer/ruler.py b/src/hgvs/pretty/console/ruler.py similarity index 92% rename from src/hgvs/pretty/renderer/ruler.py rename to src/hgvs/pretty/console/ruler.py index 3317f605..69b0810e 100644 --- a/src/hgvs/pretty/renderer/ruler.py +++ b/src/hgvs/pretty/console/ruler.py @@ -1,5 +1,5 @@ from hgvs.pretty.models import VariantData -from hgvs.pretty.renderer.renderer import BasicRenderer +from hgvs.pretty.console.renderer import BasicRenderer class ChrRuler(BasicRenderer): diff --git a/src/hgvs/pretty/renderer/shuffled_variant.py b/src/hgvs/pretty/console/shuffled_variant.py similarity index 94% rename from src/hgvs/pretty/renderer/shuffled_variant.py rename to src/hgvs/pretty/console/shuffled_variant.py index cf652b39..5989ca9c 100644 --- a/src/hgvs/pretty/renderer/shuffled_variant.py +++ b/src/hgvs/pretty/console/shuffled_variant.py @@ -1,5 +1,5 @@ from hgvs.pretty.models import VariantCoords, VariantData -from hgvs.pretty.renderer.renderer import BasicRenderer +from hgvs.pretty.console.renderer import BasicRenderer from hgvs.sequencevariant import SequenceVariant @@ -17,7 +17,7 @@ def legend(self) -> str: def display(self, data: VariantData) -> str: - from hgvs.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW + from hgvs.pretty.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW seq_start = data.display_start seq_end = data.display_end diff --git a/src/hgvs/pretty/renderer/tx_alig_renderer.py b/src/hgvs/pretty/console/tx_alig_renderer.py similarity index 95% rename from src/hgvs/pretty/renderer/tx_alig_renderer.py rename to src/hgvs/pretty/console/tx_alig_renderer.py index 90418742..5afb226f 100644 --- a/src/hgvs/pretty/renderer/tx_alig_renderer.py +++ b/src/hgvs/pretty/console/tx_alig_renderer.py @@ -1,7 +1,7 @@ import math from hgvs.pretty.models import VariantData -from hgvs.pretty.renderer.renderer import BasicRenderer +from hgvs.pretty.console.renderer import BasicRenderer class TxAligRenderer(BasicRenderer): @@ -17,7 +17,7 @@ def display(self, data: VariantData) -> str: if not data.var_c_or_n: return "" - from hgvs.pretty_print import ENDC, TPURPLE, TYELLOW + from hgvs.pretty.pretty_print import ENDC, TPURPLE, TYELLOW var_str = "" diff --git a/src/hgvs/pretty/renderer/tx_mapping_renderer.py b/src/hgvs/pretty/console/tx_mapping_renderer.py similarity index 97% rename from src/hgvs/pretty/renderer/tx_mapping_renderer.py rename to src/hgvs/pretty/console/tx_mapping_renderer.py index 6eb04721..8fd78778 100644 --- a/src/hgvs/pretty/renderer/tx_mapping_renderer.py +++ b/src/hgvs/pretty/console/tx_mapping_renderer.py @@ -1,5 +1,5 @@ from hgvs.pretty.models import VariantData -from hgvs.pretty.renderer.renderer import BasicRenderer +from hgvs.pretty.console.renderer import BasicRenderer class TxMappingRenderer(BasicRenderer): diff --git a/src/hgvs/pretty/renderer/tx_pos.py b/src/hgvs/pretty/console/tx_pos.py similarity index 95% rename from src/hgvs/pretty/renderer/tx_pos.py rename to src/hgvs/pretty/console/tx_pos.py index a58f0d10..6f9f7097 100644 --- a/src/hgvs/pretty/renderer/tx_pos.py +++ b/src/hgvs/pretty/console/tx_pos.py @@ -1,5 +1,5 @@ from hgvs.pretty.models import VariantData -from hgvs.pretty.renderer.renderer import BasicRenderer +from hgvs.pretty.console.renderer import BasicRenderer class TxRulerRenderer(BasicRenderer): diff --git a/src/hgvs/pretty/renderer/tx_ref_disagree_renderer.py b/src/hgvs/pretty/console/tx_ref_disagree_renderer.py similarity index 91% rename from src/hgvs/pretty/renderer/tx_ref_disagree_renderer.py rename to src/hgvs/pretty/console/tx_ref_disagree_renderer.py index a729bab6..1fe5177d 100644 --- a/src/hgvs/pretty/renderer/tx_ref_disagree_renderer.py +++ b/src/hgvs/pretty/console/tx_ref_disagree_renderer.py @@ -1,5 +1,5 @@ from hgvs.pretty.models import VariantData -from hgvs.pretty.renderer.renderer import BasicRenderer +from hgvs.pretty.console.renderer import BasicRenderer class TxRefDisagreeRenderer(BasicRenderer): @@ -15,7 +15,7 @@ def display(self, data: VariantData) -> str: if not data.var_c_or_n: return "" - from hgvs.pretty_print import ENDC, TRED + from hgvs.pretty.pretty_print import ENDC, TRED var_str = "" counter = -1 diff --git a/src/hgvs/pretty/datacompiler.py b/src/hgvs/pretty/datacompiler.py index 45e770d2..ae73fb4e 100644 --- a/src/hgvs/pretty/datacompiler.py +++ b/src/hgvs/pretty/datacompiler.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Tuple +from typing import List, Tuple from bioutils.normalize import normalize from bioutils.sequences import aa1_to_aa3_lut diff --git a/src/hgvs/pretty/models.py b/src/hgvs/pretty/models.py index d3a8cef5..4afaca68 100644 --- a/src/hgvs/pretty/models.py +++ b/src/hgvs/pretty/models.py @@ -94,7 +94,7 @@ class VariantData: @dataclass class PrettyConfig: - """acontainer for various configurations.""" + """A container for various configurations.""" hdp: Interface am37: AssemblyMapper diff --git a/src/hgvs/pretty_print.py b/src/hgvs/pretty/pretty_print.py similarity index 83% rename from src/hgvs/pretty_print.py rename to src/hgvs/pretty/pretty_print.py index adcb1e6d..49b22104 100644 --- a/src/hgvs/pretty_print.py +++ b/src/hgvs/pretty/pretty_print.py @@ -2,35 +2,25 @@ import hgvs from hgvs.assemblymapper import AssemblyMapper +from hgvs.pretty.console.renderer import colorize_hgvs from hgvs.pretty.datacompiler import DataCompiler from hgvs.pretty.models import PrettyConfig -from hgvs.pretty.renderer.chrom_seq_renderer import ChromSeqRendered -from hgvs.pretty.renderer.chrom_seq_reverse_renderer import ChromReverseSeqRendered -from hgvs.pretty.renderer.pos_info import ChrPositionInfo -from hgvs.pretty.renderer.prot_mapping_renderer import ProtMappingRenderer -from hgvs.pretty.renderer.prot_ruler_renderer import ProtRulerRenderer -from hgvs.pretty.renderer.prot_seq_renderer import ProtSeqRenderer -from hgvs.pretty.renderer.ruler import ChrRuler -from hgvs.pretty.renderer.shuffled_variant import ShuffledVariant -from hgvs.pretty.renderer.tx_alig_renderer import TxAligRenderer -from hgvs.pretty.renderer.tx_mapping_renderer import TxMappingRenderer -from hgvs.pretty.renderer.tx_pos import TxRulerRenderer -from hgvs.pretty.renderer.tx_ref_disagree_renderer import TxRefDisagreeRenderer +from hgvs.pretty.console.chrom_seq_renderer import ChromSeqRendered +from hgvs.pretty.console.chrom_seq_reverse_renderer import ChromReverseSeqRendered +from hgvs.pretty.console.pos_info import ChrPositionInfo +from hgvs.pretty.console.prot_mapping_renderer import ProtMappingRenderer +from hgvs.pretty.console.prot_ruler_renderer import ProtRulerRenderer +from hgvs.pretty.console.prot_seq_renderer import ProtSeqRenderer +from hgvs.pretty.console.ruler import ChrRuler +from hgvs.pretty.console.shuffled_variant import ShuffledVariant +from hgvs.pretty.console.tx_alig_renderer import TxAligRenderer +from hgvs.pretty.console.tx_mapping_renderer import TxMappingRenderer +from hgvs.pretty.console.tx_pos import TxRulerRenderer +from hgvs.pretty.console.tx_ref_disagree_renderer import TxRefDisagreeRenderer from hgvs.repeats import RepeatAnalyser from hgvs.sequencevariant import SequenceVariant -TGREEN = "\033[32m" # Green Text -TGREENBG = "\033[30;42m" -TRED = "\033[31m" # Red Text -TREDBG = "\033[30;41m" -TBLUE = "\033[34m" # Blue Text -TBLUEBG = "\033[30;44m" -TPURPLE = "\033[35m" # Purple Text -TPURPLEBG = "\033[30;45m" -TYELLOW = "\033[33m" # Yellow Text -TYELLOWBG = "\033[30;43m" -ENDC = "\033[m" # reset to the defaults class PrettyPrint: @@ -117,20 +107,6 @@ def _map_to_chrom(self, sv: SequenceVariant) -> SequenceVariant: elif sv.type == "t": return am.t_to_g(sv) - def _colorize_hgvs(self, hgvs_str: str) -> str: - if not self.config.useColor: - return hgvs_str - - spl = hgvs_str.split(":") - var_str = TPURPLE + spl[0] + ENDC - var_str += ":" - - sec = spl[1].split(".") - var_str += TYELLOW + sec[0] + ENDC - var_str += "." - var_str += sec[1] - - return var_str def get_hgvs_names( self, sv: SequenceVariant, tx_ac: str = None @@ -210,14 +186,14 @@ def create_repre( head = head_c = head_p = refa = "" if self.config.useColor: - var_g_print = self._colorize_hgvs(str(var_g)) + var_g_print = colorize_hgvs(str(var_g)) else: var_g_print = str(var_g) var_str = head + var_g_print + "\n" if data.var_c_or_n: if self.config.useColor: - var_c_print = self._colorize_hgvs(str(var_c_or_n)) + var_c_print = colorize_hgvs(str(var_c_or_n)) else: var_c_print = str(var_c_or_n) if data.var_c_or_n.type == "c": @@ -228,7 +204,7 @@ def create_repre( if data.var_p: if self.config.useColor: - var_p_print = self._colorize_hgvs(str(data.var_p)) + var_p_print = colorize_hgvs(str(data.var_p)) else: var_p_print = str(data.var_p) var_str += head_p + var_p_print + "\n" diff --git a/src/hgvs/pretty/renderer/renderer.py b/src/hgvs/pretty/renderer/renderer.py deleted file mode 100644 index eab9a816..00000000 --- a/src/hgvs/pretty/renderer/renderer.py +++ /dev/null @@ -1,15 +0,0 @@ -from abc import ABC, abstractmethod - - -class BasicRenderer(ABC): - def __init__(self, config, orientation: int): - self.config = config - self.orientation = orientation - - @abstractmethod - def legend(self): - pass - - @abstractmethod - def display(self): - pass diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index d94f8633..0cb76f41 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- -import os import unittest import pytest -from support import CACHE import hgvs -from hgvs.pretty_print import PrettyPrint +from hgvs.pretty.pretty_print import PrettyPrint @pytest.mark.quick From d08f02db16a1f37a6d7529102a50c374d71494ea Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sun, 1 Sep 2024 23:11:15 -0700 Subject: [PATCH 18/31] #741 small refactoring to update directory structure, based on PR comments, also disabling slow running unit test --- src/hgvs/pretty/console/chrom_seq_renderer.py | 2 +- src/hgvs/pretty/console/chrom_seq_reverse_renderer.py | 2 +- src/hgvs/pretty/console/prot_mapping_renderer.py | 2 +- src/hgvs/pretty/console/prot_seq_renderer.py | 2 +- src/hgvs/pretty/console/shuffled_variant.py | 2 +- src/hgvs/pretty/console/tx_alig_renderer.py | 2 +- src/hgvs/pretty/console/tx_ref_disagree_renderer.py | 5 ++--- src/hgvs/pretty/datacompiler.py | 1 + tests/test_pretty_print.py | 1 + 9 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/hgvs/pretty/console/chrom_seq_renderer.py b/src/hgvs/pretty/console/chrom_seq_renderer.py index 0278a682..6152d474 100644 --- a/src/hgvs/pretty/console/chrom_seq_renderer.py +++ b/src/hgvs/pretty/console/chrom_seq_renderer.py @@ -9,7 +9,7 @@ def legend(self) -> str: def display(self, data: VariantData) -> str: """colors the ref sequences with adenine (A, green), thymine (T, red), cytosine (C, yellow), and guanine (G, blue)""" - from hgvs.pretty.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW + from hgvs.pretty.console.constants import ENDC, TBLUE, TGREEN, TRED, TYELLOW var_seq = "" for p in data.position_details: diff --git a/src/hgvs/pretty/console/chrom_seq_reverse_renderer.py b/src/hgvs/pretty/console/chrom_seq_reverse_renderer.py index 01effa76..22d98ffa 100644 --- a/src/hgvs/pretty/console/chrom_seq_reverse_renderer.py +++ b/src/hgvs/pretty/console/chrom_seq_reverse_renderer.py @@ -11,7 +11,7 @@ def legend(self) -> str: def display(self, data: VariantData) -> str: """colors the ref sequences with adenine (A, green), thymine (T, red), cytosine (C, yellow), and guanine (G, blue)""" - from hgvs.pretty.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW + from hgvs.pretty.console.constants import ENDC, TBLUE, TGREEN, TRED, TYELLOW var_seq = "" for p in data.position_details: diff --git a/src/hgvs/pretty/console/prot_mapping_renderer.py b/src/hgvs/pretty/console/prot_mapping_renderer.py index e4ccf512..3a9b6cab 100644 --- a/src/hgvs/pretty/console/prot_mapping_renderer.py +++ b/src/hgvs/pretty/console/prot_mapping_renderer.py @@ -3,7 +3,7 @@ class ProtMappingRenderer(BasicRenderer): - """prints the position in p (amino acid) coordinates.""" + """Prints the position in p (amino acid) coordinates.""" def legend(self): return "aa pos : " diff --git a/src/hgvs/pretty/console/prot_seq_renderer.py b/src/hgvs/pretty/console/prot_seq_renderer.py index 79be30a2..7782621e 100644 --- a/src/hgvs/pretty/console/prot_seq_renderer.py +++ b/src/hgvs/pretty/console/prot_seq_renderer.py @@ -14,7 +14,7 @@ def display(self, data: VariantData) -> str: if not data.var_c_or_n: return "" - from hgvs.pretty.pretty_print import ENDC, TGREEN, TRED + from hgvs.pretty.console.constants import ENDC, TGREEN, TRED var_str = "" for pdata in data.position_details: diff --git a/src/hgvs/pretty/console/shuffled_variant.py b/src/hgvs/pretty/console/shuffled_variant.py index 5989ca9c..1bbdeebc 100644 --- a/src/hgvs/pretty/console/shuffled_variant.py +++ b/src/hgvs/pretty/console/shuffled_variant.py @@ -17,7 +17,7 @@ def legend(self) -> str: def display(self, data: VariantData) -> str: - from hgvs.pretty.pretty_print import ENDC, TBLUE, TGREEN, TRED, TYELLOW + from hgvs.pretty.console.constants import ENDC, TRED seq_start = data.display_start seq_end = data.display_end diff --git a/src/hgvs/pretty/console/tx_alig_renderer.py b/src/hgvs/pretty/console/tx_alig_renderer.py index 5afb226f..810ecf2f 100644 --- a/src/hgvs/pretty/console/tx_alig_renderer.py +++ b/src/hgvs/pretty/console/tx_alig_renderer.py @@ -17,7 +17,7 @@ def display(self, data: VariantData) -> str: if not data.var_c_or_n: return "" - from hgvs.pretty.pretty_print import ENDC, TPURPLE, TYELLOW + from hgvs.pretty.console.constants import ENDC, TPURPLE, TYELLOW var_str = "" diff --git a/src/hgvs/pretty/console/tx_ref_disagree_renderer.py b/src/hgvs/pretty/console/tx_ref_disagree_renderer.py index 1fe5177d..083939c8 100644 --- a/src/hgvs/pretty/console/tx_ref_disagree_renderer.py +++ b/src/hgvs/pretty/console/tx_ref_disagree_renderer.py @@ -6,8 +6,7 @@ class TxRefDisagreeRenderer(BasicRenderer): """Display tx-ref-disagree positions""" def legend(self) -> str: - - return f"tx ref dif: " + return "tx ref dif: " def display(self, data: VariantData) -> str: """show differences between tx and ref genome, if there are any""" @@ -15,7 +14,7 @@ def display(self, data: VariantData) -> str: if not data.var_c_or_n: return "" - from hgvs.pretty.pretty_print import ENDC, TRED + from hgvs.pretty.console.constants import ENDC, TRED var_str = "" counter = -1 diff --git a/src/hgvs/pretty/datacompiler.py b/src/hgvs/pretty/datacompiler.py index ae73fb4e..3685af11 100644 --- a/src/hgvs/pretty/datacompiler.py +++ b/src/hgvs/pretty/datacompiler.py @@ -24,6 +24,7 @@ def __init__(self, config: PrettyConfig): self.config = config def get_shuffled_variant(self, var_g: SequenceVariant, direction: int) -> VariantCoords: + """ Takes a sequence variant and returns VariantCoords that have been shuffled accordingly.""" # get shuffled representation: if direction == 5: diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index 0cb76f41..931b7ba5 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -7,6 +7,7 @@ from hgvs.pretty.pretty_print import PrettyPrint +@pytest.mark.skip("The pretty print tests are data hungry. If we were to add the data to the test cache, we would inflate the size of the cache. As such only running when necessary.") @pytest.mark.quick @pytest.mark.models class Test_SimplePosition(unittest.TestCase): From 401c5a38261908fd6d587ecccd3a6c14264c3692 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Thu, 5 Sep 2024 22:07:58 -0700 Subject: [PATCH 19/31] #741 excluding data intensive tests from CI. They would create too much caching burden. --- tests/test_pretty_print.py | 2 +- tests/test_repeats.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index 931b7ba5..946ffc43 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -7,7 +7,7 @@ from hgvs.pretty.pretty_print import PrettyPrint -@pytest.mark.skip("The pretty print tests are data hungry. If we were to add the data to the test cache, we would inflate the size of the cache. As such only running when necessary.") +@pytest.mark.skip(reason="The pretty print tests are data hungry. If we were to add the data to the test cache, we would inflate the size of the cache. As such only running when necessary.") @pytest.mark.quick @pytest.mark.models class Test_SimplePosition(unittest.TestCase): diff --git a/tests/test_repeats.py b/tests/test_repeats.py index 74c37b7f..73da4e0f 100644 --- a/tests/test_repeats.py +++ b/tests/test_repeats.py @@ -2,6 +2,7 @@ from parameterized import parameterized +import pytest import hgvs import hgvs.dataproviders from hgvs.pretty.datacompiler import DataCompiler @@ -32,6 +33,7 @@ def test_count_repetitive_units(self, s: str, u: str, c: int): self.assertEqual(u, observed_u) self.assertEqual(c, observed_c) + @pytest.mark.skip(reason="would add too much caching burden.") @parameterized.expand( [ ("NC_000019.10:g.45770205del", True, "C", 6, 5, "C[6]>C[5]"), @@ -56,6 +58,7 @@ def test_homopolymer(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, self.assertEqual(ref_count, ra.ref_count) self.assertEqual(alt_count, ra.alt_count) + @pytest.mark.skip(reason="would add too much caching burden.") @parameterized.expand( [ ( From 336ea2c21f325fcc534a19c0023b07b635a36b92 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Thu, 5 Sep 2024 22:11:04 -0700 Subject: [PATCH 20/31] trying to fix CI --- .github/workflows/python-package.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 640bb61c..688e2d6c 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -57,7 +57,8 @@ jobs: - name: Install dependencies run: | - pip install -e .[dev] + pip install setuptools + pip install -e .[dev] pip install pytest pytest-cov - name: Test with pytest From d216bba682de78bf6eaa607c76c60c95df6207f8 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Fri, 6 Sep 2024 23:35:13 -0700 Subject: [PATCH 21/31] feat(repeats): found an issue with count_pattern_occurences, fixed and with more unit tests. --- src/hgvs/repeats.py | 4 ++-- tests/test_repeats.py | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/hgvs/repeats.py b/src/hgvs/repeats.py index c3cc146e..439f0646 100644 --- a/src/hgvs/repeats.py +++ b/src/hgvs/repeats.py @@ -9,8 +9,8 @@ def count_pattern_occurence(pattern, string): """Counts how often a pattern can be found in the string.""" - matches = re.findall(pattern, string) - return len(matches) + matches = re.finditer(f'(?={pattern})', string) + return sum(1 for _ in matches) def count_repetitive_units(s): diff --git a/tests/test_repeats.py b/tests/test_repeats.py index 73da4e0f..b1f4b352 100644 --- a/tests/test_repeats.py +++ b/tests/test_repeats.py @@ -7,7 +7,7 @@ import hgvs.dataproviders from hgvs.pretty.datacompiler import DataCompiler from hgvs.pretty.models import PrettyConfig -from hgvs.repeats import RepeatAnalyser, count_repetitive_units +from hgvs.repeats import RepeatAnalyser, count_pattern_occurence, count_repetitive_units class TestRepeats(unittest.TestCase): @@ -33,6 +33,22 @@ def test_count_repetitive_units(self, s: str, u: str, c: int): self.assertEqual(u, observed_u) self.assertEqual(c, observed_c) + @parameterized.expand( + [ + ('T','TTTTTTT',7), + ('TT','TTTTTTT',6), + ('TTTT','TTTTTTT',4), + ('GA', 'GAGA', 2), + ('abc', 'abc', 1), + ('C', 'CC', 2), + ('', '', 1), + ('AA', 'A', 0) + ] + ) + def test_count_pattern_occurence(self, pattern:str, string:str, c: int): + l_matches = count_pattern_occurence(pattern, string) + self.assertEqual(l_matches, c) + @pytest.mark.skip(reason="would add too much caching burden.") @parameterized.expand( [ @@ -80,6 +96,7 @@ def test_homopolymer(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, ("NC_000005.10:g.123346517_123346518insATTA", True, "ATTA", 2, 3, "ATTA[2]>ATTA[3]"), ("NC_000005.10:g.123346522_123346525dup", True, "ATTA", 2, 3, "ATTA[2]>ATTA[3]"), ("NC_000019.10:g.45770210_45770212del", True, "GCA", 20, 19, "GCA[20]>GCA[19]"), + ("NC_000007.14:g.117548628_117548629insTTTT", True, "T", 7 , 11, "T[7]>T[11]") ] ) def test_repeats(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, s): From 9d15a229e740fef6a8a4bcfc45a17759265ab568 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sun, 15 Sep 2024 21:25:47 -0700 Subject: [PATCH 22/31] feat(repeats): adding examples from HGVS repeats recommendations page. Improved repeat analyzer, that now supports 5' and 3' shuffling. --- src/hgvs/pretty/console/tx_pos.py | 5 +- src/hgvs/pretty/datacompiler.py | 4 +- src/hgvs/repeats.py | 255 ++++++++++++++++++++++++------ tests/test_repeats.py | 247 +++++++++++++++++++++++------ 4 files changed, 417 insertions(+), 94 deletions(-) diff --git a/src/hgvs/pretty/console/tx_pos.py b/src/hgvs/pretty/console/tx_pos.py index 6f9f7097..bf2d09f1 100644 --- a/src/hgvs/pretty/console/tx_pos.py +++ b/src/hgvs/pretty/console/tx_pos.py @@ -11,7 +11,10 @@ def display(self, data: VariantData) -> str: """show the position of the transcript seq""" var_str = "" - rna_coding = data.var_c_or_n.ac.startswith("NR_") + if data.var_c_or_n: + rna_coding = data.var_c_or_n.ac.startswith("NR_") + else: + rna_coding = False count = -1 for pdata in data.position_details: diff --git a/src/hgvs/pretty/datacompiler.py b/src/hgvs/pretty/datacompiler.py index 3685af11..b7b83cc3 100644 --- a/src/hgvs/pretty/datacompiler.py +++ b/src/hgvs/pretty/datacompiler.py @@ -345,6 +345,8 @@ def data( # for p in position_details: # print(f"{p}\t{p.protein_data}\t") + strand = mapper.strand if mapper else 1 + vd = VariantData( seq_start, seq_end, @@ -355,7 +357,7 @@ def data( tx_seq, mapper, var_g, - mapper.strand, + strand, var_c_or_n, var_p, position_details, diff --git a/src/hgvs/repeats.py b/src/hgvs/repeats.py index 439f0646..edec3b6b 100644 --- a/src/hgvs/repeats.py +++ b/src/hgvs/repeats.py @@ -1,73 +1,236 @@ # -*- coding: utf-8 -*- """ A class to manage conversion of SequenceVariants to repeat representation""" +from dataclasses import dataclass import re -from typing import Optional from hgvs.pretty.models import VariantCoords -def count_pattern_occurence(pattern, string): - """Counts how often a pattern can be found in the string.""" - matches = re.finditer(f'(?={pattern})', string) - return sum(1 for _ in matches) - - -def count_repetitive_units(s): - length = len(s) - - for i in range(1, length + 1): - unit = s[:i] - if length % i == 0: - if unit * (length // i) == s: - return length // i, unit - - return 1, s +@dataclass(eq=True, repr=True, frozen=True, order=True) +class RepeatUnit: + repeat_count: int + repeat_unit:str + block_size:int + block: str class RepeatAnalyser: - def __init__(self, fs: VariantCoords) -> None: + def __init__(self, fs: VariantCoords, reverse:bool=False) -> None: self.is_repeat = False - self.ref_count = 0 - self.alt_count = 0 + self.repeat_units_ref = None + self.repeat_units_alt = None self.ref_str = fs.ref self.alt_str = fs.alt + self.reverse = reverse - self.repeat_unit = self._get_repeat_unit(fs) - if self.repeat_unit is None: + self.repeat_units_ref = detect_repetitive_block_lengths(self.ref_str, self.reverse) + self.repeat_units_alt = detect_repetitive_block_lengths(self.alt_str, self.reverse) + + if len(self.repeat_units_ref) == 0 and len(self.repeat_units_alt) ==0 : + return + + # check longest repeat blocks: + # we only look at ref to determine if there are repeats + # If ref has no repeat, we don't call this a repeat variant, even if alt would have a repetitive unit + longest_r_unit = self._get_longest_repeat_unit(self.repeat_units_ref) + if longest_r_unit is None: + return + + # filter our too fragmented results + expected_size = len(self.ref_str) / 3 + if longest_r_unit.block_size < expected_size: return + + if longest_r_unit.repeat_unit not in self.alt_str: + return + self.is_repeat = True - self.ref_count = count_pattern_occurence(self.repeat_unit, fs.ref) - self.alt_count = count_pattern_occurence(self.repeat_unit, fs.alt) - self.ref_str = f"{self.repeat_unit}[{self.ref_count}]" - self.alt_str = f"{self.repeat_unit}[{self.alt_count}]" + ref_repeat = get_repeat_str(self.ref_str, self.alt_str, self.repeat_units_ref, self.repeat_units_alt, self.reverse) + alt_repeat = get_repeat_str(self.alt_str, self.ref_str, self.repeat_units_alt, self.repeat_units_ref, self.reverse) + + self.ref_str = ref_repeat + self.alt_str = alt_repeat def __repr__(self): return f"{self.ref_str}>{self.alt_str}" - def _get_repeat_unit(self, fs: VariantCoords) -> Optional[str]: - """Takes fully justified coordiantes and tries to detect a repeat in them.""" - # analyze for repeat: - if len(fs.ref) == len(fs.alt) > 0: - # seems we cant shuffle. is an SVN or delins - return None - - if len(fs.alt) > 0: - if fs.alt in fs.ref: - if fs.ref.startswith(fs.alt): - d = fs.ref[len(fs.alt) :] - if count_pattern_occurence(d, fs.ref) > 1: - c, u = count_repetitive_units(d) - return u - elif fs.ref in fs.alt: - if fs.alt.startswith(fs.ref): - d = fs.alt[len(fs.ref) :] - if count_pattern_occurence(d, fs.ref) > 1: - c, u = count_repetitive_units(d) - return u + def _get_longest_repeat_unit(self, repeat_units:list[RepeatUnit])->RepeatUnit: + lru = None + for ru in repeat_units: + if not lru: + lru = ru + continue + + if ru.block_size > len(lru.block): + lru = ru + + return lru + + +def detect_repetitive_block_lengths(sequence: str, reverse: bool = False) -> list[RepeatUnit]: + """Detects the length of repetitive blocks in a string, with an option to search from left to right or reverse. + + In reverse mode, it creates the largest possible blocks of the smallest possible units. + """ + result: list[RepeatUnit] = [] + seq_len = len(sequence) + + if reverse: + i = seq_len # Start from the end of the sequence + while i > 0: + matched = False + # Iterate over block sizes from smallest to largest + for block_size in range(1, seq_len // 2 + 1): + if i - block_size < 0: + continue # Not enough characters to form a repeat unit + + # Extract the potential repeat unit ending at position i + repeat_unit = sequence[i - block_size:i] + + # Calculate the maximum possible number of repeats for this block size + max_possible_repeats = i // block_size + + # Initialize repeat_count to the maximum possible and decrease + for repeat_count in range(max_possible_repeats, 1, -1): + start_index = i - repeat_count * block_size + if start_index < 0: + continue # Not enough characters to form the repetitive block + + # Extract the substring that could be the repetitive block + substr = sequence[start_index:i] + + # Build the regex pattern for the current repeat unit and count + pattern = rf'({re.escape(repeat_unit)})' + r'{' + f'{repeat_count}' + r'}' + + # Check if the substring matches the pattern + if re.fullmatch(pattern, substr): + repetitive_block = substr + ru = RepeatUnit( + block=repetitive_block, + block_size=len(repetitive_block), + repeat_unit=repeat_unit, + repeat_count=repeat_count + ) + result.append(ru) + # Move the index `i` backward by the length of the repetitive block + i -= len(repetitive_block) + matched = True + break # Found the largest block for this unit size + + if matched: + break # Proceed to the next position `i` + + if not matched: + # No repeat found, remove one character + ru = RepeatUnit( + block=sequence[i - 1], + block_size=1, + repeat_unit=sequence[i - 1], + repeat_count=1 + ) + result.append(ru) + i -= 1 # Move back by one character if no match is found + + else: + i = 0 # Start from the beginning of the sequence + while i < seq_len: + matched = False + for block_size in range(1, seq_len // 2 + 1): + # Build the regex pattern for the current block size + pattern = rf'(.{{{block_size}}})\1+' + match = re.match(pattern, sequence[i:]) + + if match: + repetitive_block = match.group() # The full repeating pattern + repeated_unit = match.group(1) # The repeating unit + repetition_count = len(repetitive_block) // len(repeated_unit) + + # Add the repetitive block and its details to the result + ru = RepeatUnit( + block=repetitive_block, + block_size=len(repetitive_block), + repeat_unit=repeated_unit, + repeat_count=repetition_count + ) + result.append(ru) + + # Move the index `i` forward by the length of the repetitive block + i += len(repetitive_block) + matched = True + break # Found a match, break the loop for current block size + + if not matched: + i += 1 # Move forward by one character if no match is found + + return result + + +def get_repeat_str( + sequence: str, + other_seq: str, + primary_repeat_unit: list[RepeatUnit], + other_repeat_unit: list[RepeatUnit], + reverse: bool = False +) -> str: + if len(primary_repeat_unit) == 0 and len(other_repeat_unit) == 0: + return None + if len(primary_repeat_unit) == 0 and len(other_repeat_unit) == 1 and sequence == other_repeat_unit[0].repeat_unit: + return f"{sequence}[1]" + elif len(primary_repeat_unit) == 0 and len(other_repeat_unit) == 1 and sequence != other_repeat_unit[0].repeat_unit: return None + + if len(primary_repeat_unit) > 0 and len(other_repeat_unit) > 0: + return_str = assemble_repeat_string(sequence, primary_repeat_unit, reverse=reverse) + return return_str + + if len(other_repeat_unit) == 0 and len(other_seq) > 0: + return_str = assemble_repeat_string(sequence, primary_repeat_unit, reverse=reverse) + if len(return_str) > 0: + return return_str + + return None + +def assemble_repeat_string(sequence: str, repeat_units: list[RepeatUnit], reverse: bool = False) -> str: + return_str = "" + primary_repeat_unit = repeat_units.copy() + seq = sequence + + if reverse: + while len(seq) > 0: + found_unit = None + for ru in primary_repeat_unit: + if seq.endswith(ru.block): + return_str = f"{ru.repeat_unit}[{ru.repeat_count}]" + return_str + seq = seq[:-len(ru.block)] + found_unit = ru + break + if not found_unit: + # remove one character if no matching repeat unit is found + seq_char = seq[0] + seq = seq[:-1] + return_str = f"{seq_char}[1]" + return_str + + else: # forward direction + while len(seq) > 0: + found_unit = None + for ru in primary_repeat_unit: + if seq.startswith(ru.block): + return_str += f"{ru.repeat_unit}[{ru.repeat_count}]" + seq = seq[len(ru.block):] + found_unit = ru + break + if not found_unit: + # remove one character if no matching repeat unit is found + seq_char = seq[0] + seq = seq[1:] + return_str += f"{seq_char}[1]" + + + return return_str + diff --git a/tests/test_repeats.py b/tests/test_repeats.py index b1f4b352..c40435db 100644 --- a/tests/test_repeats.py +++ b/tests/test_repeats.py @@ -1,53 +1,124 @@ import unittest +from hgvs.assemblymapper import AssemblyMapper from parameterized import parameterized import pytest import hgvs import hgvs.dataproviders from hgvs.pretty.datacompiler import DataCompiler -from hgvs.pretty.models import PrettyConfig -from hgvs.repeats import RepeatAnalyser, count_pattern_occurence, count_repetitive_units +from hgvs.pretty.models import PrettyConfig, VariantCoords +from hgvs.repeats import RepeatAnalyser, detect_repetitive_block_lengths, get_repeat_str -class TestRepeats(unittest.TestCase): +def to_hgvs_repeat(chrom_ac, fs: VariantCoords , ra: RepeatAnalyser): + """ Experimental method to see how close we can get to HGVS repeat nomenclature. + Note: In many cases we are not yet creating valid HGVS strings here. However this works at least for some cases. + (homoploymeric repeats). If a repeat can be shuffled this needs some modification to use the right-aligned + representation as well as trimming off bases that can be shuffled (they come out as repeats of length 1 at the moment.) + """ + return f"{chrom_ac}:g.{fs.start+1}_{fs.end}{ra.alt_str}" + + +class TestHGVSExamples(unittest.TestCase): + """ The variants in this class are taken from the HGVS nomenclature page about repeats + https://hgvs-nomenclature.org/stable/recommendations/DNA/repeated/ + """ @classmethod def setUpClass(self): self.hdp = hgvs.dataproviders.uta.connect() + self.parser = hgvs.parser.Parser() + self.am = AssemblyMapper(self.hdp) + config = PrettyConfig(self.hdp, None, None) + self.dc = DataCompiler(config) - @parameterized.expand( - [ - ("abc", "abc", 1), - ("CC", "C", 2), - ("GAGA", "GA", 2), - ("", "", 1), - ("AAAAAB", "AAAAAB", 1), - ("ABBCABBCABBC", "ABBC", 3), - ] - ) - def test_count_repetitive_units(self, s: str, u: str, c: int): + def test_intergenic_repeat(self): + # we can't parse HGVS repeat representations yet: + # NC_000014.8:g.101179660_101179695TG[14] + # We have to to use an alternative way to describe this variant: + hgvs_g = 'NC_000014.8:g.101179660_101179667del' + + var_g = self.parser.parse_hgvs_variant(hgvs_g) + + fs = self.dc.get_shuffled_variant(var_g, 0) + ra = RepeatAnalyser(fs) - observed_c, observed_u = count_repetitive_units(s) + assert ra.is_repeat + assert ra.ref_str == 'TG[18]' + assert ra.alt_str == 'TG[14]' + assert to_hgvs_repeat(var_g.ac, fs, ra) == 'NC_000014.8:g.101179660_101179695TG[14]' # a valid HGVS name - self.assertEqual(u, observed_u) - self.assertEqual(c, observed_c) + def test_CACNA1_repeat(self): + """ NM_023035.2:c.6955_6993CAG[26] (or c.6955_6993dup ) + A repeated CAG tri-nucleotide sequence (CAG on reverse strand, CTG on forward strand). + """ + hgvs_c = 'NM_023035.2:c.6955_6993dup' + var_c = self.parser.parse_hgvs_variant(hgvs_c) + var_g = self.am.c_to_g(var_c) + assert str(var_g) == 'NC_000019.10:g.13207860_13207898dup' + fs = self.dc.get_shuffled_variant(var_g, 0) + ra = RepeatAnalyser(fs) + assert ra.is_repeat + assert ra.ref_str == "CTG[13]C[1]" + assert ra.alt_str == "CTG[26]C[1]" + assert to_hgvs_repeat(var_g.ac, fs, ra) == 'NC_000019.10:g.13207859_13207898CTG[26]C[1]' # not valid. It is 5' shuffled and there's a C that can be shuffled which needs to get trimmed. + + # This repeat is shuffle-able. These are alternate representations of each other: + # CTG[13]C[1] + # C[1]TGC[13] + + # here we test the alternative shuffled representation: + ra2 = RepeatAnalyser(fs, reverse=True) + assert ra2.is_repeat + assert ra2.ref_str == "C[1]TGC[13]" + assert ra2.alt_str == "C[1]TGC[26]" + assert to_hgvs_repeat(var_g.ac, fs, ra2) == 'NC_000019.10:g.13207859_13207898C[1]TGC[26]' # not valid, we should trim off the shuffle-able C for that. + + def test_ATXN7_repeat(self): + """ A repeated AGC tri-nucleotide sequence in the ATXN7 gene on chromosome 3 + + Note: In literature, the tri-nucleotide repeat, encoding a poly-Gln repeat on protein level, + is known as the CAG repeat (CAG is the codon for Gln). + + This repeat is shuffle-able. + + There are three alternative representations that are all equally valid for this repeat: + GCA[10]G[1]C[1] + G[1]CAG[10]C[1] + G[1]C[1]AGC[10] + + At the moment we can create the first and third representation of this repeat, but not yet the middle one. Note: the middle one is the one + that would match the codons in the transcript, and prob make most sense from a biological perspective. + """ + + hgvs_g = "NC_000003.12:g.63912688_63912689insCAGCAGCAG" + var_g = self.parser.parse_hgvs_variant(hgvs_g) + + fs = self.dc.get_shuffled_variant(var_g, 0) + ra = RepeatAnalyser(fs) + + assert ra.is_repeat + assert ra.ref_str == 'GCA[10]G[1]C[1]' + assert ra.alt_str == 'GCA[13]G[1]C[1]' + + # invalid HGVS, 5' aligned, and shows the two shuffle-able bases at the end. + assert to_hgvs_repeat(var_g.ac, fs, ra) == 'NC_000003.12:g.63912685_63912716GCA[13]G[1]C[1]' + + ra2 = RepeatAnalyser(fs, reverse=True) + assert ra2.is_repeat + assert ra2.ref_str == 'G[1]C[1]AGC[10]' + assert ra2.alt_str == 'G[1]C[1]AGC[13]' + + # invalid HGVS, due to the shuffle-able bases, but comes pretty close to what HGVS recommends. + assert to_hgvs_repeat(var_g.ac, fs, ra2) == 'NC_000003.12:g.63912685_63912716G[1]C[1]AGC[13]' - @parameterized.expand( - [ - ('T','TTTTTTT',7), - ('TT','TTTTTTT',6), - ('TTTT','TTTTTTT',4), - ('GA', 'GAGA', 2), - ('abc', 'abc', 1), - ('C', 'CC', 2), - ('', '', 1), - ('AA', 'A', 0) - ] - ) - def test_count_pattern_occurence(self, pattern:str, string:str, c: int): - l_matches = count_pattern_occurence(pattern, string) - self.assertEqual(l_matches, c) + +class TestRepeats(unittest.TestCase): + + @classmethod + def setUpClass(self): + self.hdp = hgvs.dataproviders.uta.connect() @pytest.mark.skip(reason="would add too much caching burden.") @parameterized.expand( @@ -70,9 +141,21 @@ def test_homopolymer(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, self.assertEqual(s, str(ra)) self.assertEqual(is_repeat, ra.is_repeat) - self.assertEqual(repeat_unit, ra.repeat_unit) - self.assertEqual(ref_count, ra.ref_count) - self.assertEqual(alt_count, ra.alt_count) + + if repeat_unit is None: + self.assertEqual(len(ra.repeat_units_alt) , 0) + else: + self.assertEqual(repeat_unit, ra.repeat_units_alt[0].repeat_unit) + + if ref_count == 0: + self.assertEqual(len(ra.repeat_units_ref), 0) + else: + self.assertEqual(ref_count, ra.repeat_units_ref[0].repeat_count) + + if alt_count == 0: + self.assertEqual(len(ra.repeat_units_alt), 0) + else: + self.assertEqual(alt_count, ra.repeat_units_alt[0].repeat_count) @pytest.mark.skip(reason="would add too much caching burden.") @parameterized.expand( @@ -80,22 +163,22 @@ def test_homopolymer(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, ( "NC_000021.8:g.46020668_46020682del", False, - None, - 0, - 0, + 'CTG', + 2, + 2, "CTGCTGCGCCCCCAGCTGCTGCGCCCC>CTGCTGCGCCCC", ), ( "NC_000012.11:g.33049660_33049680dup", False, - None, - 0, - 0, + 'G', + 5, + 5, "GGGGGCTGCCATGGGGCCGGTGGGGGC>GGGGGCTGCCATGGGGCCGGTGGGGGCTGCCATGGGGCCGGTGGGGGC", ), ("NC_000005.10:g.123346517_123346518insATTA", True, "ATTA", 2, 3, "ATTA[2]>ATTA[3]"), ("NC_000005.10:g.123346522_123346525dup", True, "ATTA", 2, 3, "ATTA[2]>ATTA[3]"), - ("NC_000019.10:g.45770210_45770212del", True, "GCA", 20, 19, "GCA[20]>GCA[19]"), + ("NC_000019.10:g.45770210_45770212del", True, "CAG", 20, 19, "CAG[20]C[1]A[1]>CAG[19]C[1]A[1]"), # note this one can be shuffled CAG/GCA ("NC_000007.14:g.117548628_117548629insTTTT", True, "T", 7 , 11, "T[7]>T[11]") ] ) @@ -107,8 +190,80 @@ def test_repeats(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, s): fs = dc.get_shuffled_variant(var_g, 0) ra = RepeatAnalyser(fs) - self.assertEqual(is_repeat, ra.is_repeat) - self.assertEqual(repeat_unit, ra.repeat_unit) - self.assertEqual(ref_count, ra.ref_count) - self.assertEqual(alt_count, ra.alt_count) self.assertEqual(s, str(ra)) + + # note: in some cases is_repeat is False, although repeat_unit contains some data. + # however in those cases the repeat patter was not considered significant enough to name the whole variant accordingly + self.assertEqual(is_repeat, ra.is_repeat) + self.assertEqual(repeat_unit, ra.repeat_units_alt[0].repeat_unit) + self.assertEqual(ref_count, ra.repeat_units_ref[0].repeat_count) + self.assertEqual(alt_count, ra.repeat_units_alt[0].repeat_count) + + + + @parameterized.expand( + [ + ('T','TTTTTTT', None, None, 7 ,'T', False, False, "T[1]", "T[7]"), + ('TTTTTTT', 'T', 7, 'T', None, None, False, False, "T[7]", "T[1]"), + ('T', 'TT', None, None, 2, 'T', False, False, "T[1]", "T[2]"), + ('TT', 'T', 2, 'T', None, None, False, False, "T[2]", "T[1]"), + ('TT', 'TT', 2, 'T', 2, 'T', False, False, "T[2]", "T[2]"), + ('', 'TT', None, None, 2, 'T', False, False, None, None), + ('TT', '', 2, 'T', None, None, False, False, None, None), + ('TT','TTTTTTT', 2, 'T', 7, 'T', False, False, "T[2]", "T[7]"), + ('GA', 'GAGA', None, None, 2, 'GA', False, False, "GA[1]", "GA[2]"), + ('TTTT','TTTTTTT',4, 'T', 7, 'T', False, False, "T[4]", "T[7]"), + ('abc' , 'abc', None, None, None, None, False, False, None, None), + ('abcabc' , 'abcab', 2, 'abc', None, None, False, False, 'abc[2]', None), + ('', '', None, None, None, None, False, False, None, None), + ('GA', 'GCGC', None, None, 2, 'GC', False, False, None, 'GC[2]'), + ('GCGC', 'GA', 2, 'GC', None, None, False, False, 'GC[2]', None), + ('GAGAAAA', 'GAGAAAAA', 2, 'GA', 3, 'A', True, True, "GA[2]A[3]", "GA[2]A[4]"), + ('GAGAAAA', 'GAGAA', 4, 'A', 2, 'GA', True, False, "GA[2]A[3]", "GA[2]A[1]"), + ('GAGAGAABCABCABCGA', 'ABCABC', 4, 'A', 2, 'ABC', True, False, "GA[3]ABC[3]G[1]A[1]", "ABC[2]"), + ('GAGAGAABCABCABCTGAGA', 'ABCABC', 4, 'A', 2, 'ABC', True, False, "GA[3]ABC[3]T[1]GA[2]", "ABC[2]"), + ('ABBCABBCABBC','ABBCABBC', 3, 'ABBC', 2, 'ABBC', False, False, "ABBC[3]","ABBC[2]") + + + ] + ) + def test_detect_repetitive_block_lengths(self, ref, alt, ref_count, ref_repeat_unit, alt_count, alt_repeat_unit, mixed_repeat_ref, mixed_repeat_alt, expected_ref_repeat, expected_alt_repeat): + + repeat_units_ref = detect_repetitive_block_lengths(ref) + repeat_units_alt = detect_repetitive_block_lengths(alt) + + if ref_count is None: + assert len(repeat_units_ref) == 0 + if alt_count is None: + assert len(repeat_units_alt) == 0 + + if len(repeat_units_ref) == 1: + ref_u = repeat_units_ref[0].repeat_unit + ref_l = repeat_units_ref[0].repeat_count + assert ref_u == ref_repeat_unit + assert ref_l == ref_count + assert not mixed_repeat_ref + elif len(repeat_units_ref) > 1: + # more than one repeat found. + assert mixed_repeat_ref + + + if len(repeat_units_alt) == 1: + alt_u = repeat_units_alt[0].repeat_unit + alt_l = repeat_units_alt[0].repeat_count + assert alt_u == alt_repeat_unit + assert alt_l == alt_count + assert not mixed_repeat_alt + elif len(repeat_units_alt) > 1: + assert mixed_repeat_alt + + # next: rebuild ref and alt strings: + ref_repeat = get_repeat_str(ref, alt, repeat_units_ref, repeat_units_alt) + assert ref_repeat == expected_ref_repeat + + alt_repeat = get_repeat_str(alt, ref, repeat_units_alt, repeat_units_ref) + assert alt_repeat == expected_alt_repeat + + + + From 99f27bf52e175f996fd78eb6eb384881d1b59c64 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Mon, 16 Sep 2024 19:19:33 -0700 Subject: [PATCH 23/31] feat(repeats): adding an example of a large variant to make sure we don't run repeat analysis where it does not make sense. Starting to add support for non-splign alignments. --- src/hgvs/pretty/models.py | 1 + src/hgvs/pretty/pretty_print.py | 6 ++++-- src/hgvs/repeats.py | 27 ++++++++++++++++++++++----- tests/test_repeats.py | 20 +++++++++++++++++--- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/hgvs/pretty/models.py b/src/hgvs/pretty/models.py index 4afaca68..c139ec10 100644 --- a/src/hgvs/pretty/models.py +++ b/src/hgvs/pretty/models.py @@ -108,3 +108,4 @@ class PrettyConfig: infer_hgvs_c: bool = True all: bool = False # print all possible hgvs_c (for all UTA transcripts) show_reverse_strand: bool = False # show the reverse strand sequence for the chromosome + alt_aln_method:str = 'splign' diff --git a/src/hgvs/pretty/pretty_print.py b/src/hgvs/pretty/pretty_print.py index 49b22104..573e7b6a 100644 --- a/src/hgvs/pretty/pretty_print.py +++ b/src/hgvs/pretty/pretty_print.py @@ -37,6 +37,7 @@ def __init__( infer_hgvs_c=True, all=False, show_reverse_strand=False, + alt_aln_method='splign' ): """ :param hdp: HGVS Data Provider Interface-compliant instance @@ -44,8 +45,8 @@ def __init__( :param padding: spacing left and right of the variant for display purposes. """ - am37: AssemblyMapper = AssemblyMapper(hdp, assembly_name="GRCh37") - am38: AssemblyMapper = AssemblyMapper(hdp, assembly_name="GRCh38") + am37: AssemblyMapper = AssemblyMapper(hdp, assembly_name="GRCh37", alt_aln_method=alt_aln_method) + am38: AssemblyMapper = AssemblyMapper(hdp, assembly_name="GRCh38", alt_aln_method=alt_aln_method) self.config = PrettyConfig( hdp, @@ -59,6 +60,7 @@ def __init__( infer_hgvs_c, all, show_reverse_strand, + alt_aln_method ) def _get_assembly_mapper(self) -> AssemblyMapper: diff --git a/src/hgvs/repeats.py b/src/hgvs/repeats.py index edec3b6b..4ce612a0 100644 --- a/src/hgvs/repeats.py +++ b/src/hgvs/repeats.py @@ -26,9 +26,15 @@ def __init__(self, fs: VariantCoords, reverse:bool=False) -> None: self.alt_str = fs.alt self.reverse = reverse - self.repeat_units_ref = detect_repetitive_block_lengths(self.ref_str, self.reverse) - self.repeat_units_alt = detect_repetitive_block_lengths(self.alt_str, self.reverse) - + # protect from too large sequences getting analyzed. + if len(self.ref_str) > 350: + self.repeat_units_ref = [] + self.repeat_units_alt = [] + return + + self.repeat_units_ref = detect_repetitive_block_lengths(self.ref_str, reverse=self.reverse) + self.repeat_units_alt = detect_repetitive_block_lengths(self.alt_str, reverse = self.reverse) + if len(self.repeat_units_ref) == 0 and len(self.repeat_units_alt) ==0 : return @@ -38,7 +44,7 @@ def __init__(self, fs: VariantCoords, reverse:bool=False) -> None: longest_r_unit = self._get_longest_repeat_unit(self.repeat_units_ref) if longest_r_unit is None: return - + # filter our too fragmented results expected_size = len(self.ref_str) / 3 if longest_r_unit.block_size < expected_size: @@ -47,6 +53,7 @@ def __init__(self, fs: VariantCoords, reverse:bool=False) -> None: if longest_r_unit.repeat_unit not in self.alt_str: return + self.repeat_units_alt = detect_repetitive_block_lengths(self.alt_str, longest_ref_unit = longest_r_unit, reverse = self.reverse) self.is_repeat = True @@ -72,7 +79,7 @@ def _get_longest_repeat_unit(self, repeat_units:list[RepeatUnit])->RepeatUnit: return lru -def detect_repetitive_block_lengths(sequence: str, reverse: bool = False) -> list[RepeatUnit]: +def detect_repetitive_block_lengths(sequence: str, longest_ref_unit:RepeatUnit|None=None, reverse: bool = False) -> list[RepeatUnit]: """Detects the length of repetitive blocks in a string, with an option to search from left to right or reverse. In reverse mode, it creates the largest possible blocks of the smallest possible units. @@ -80,6 +87,16 @@ def detect_repetitive_block_lengths(sequence: str, reverse: bool = False) -> lis result: list[RepeatUnit] = [] seq_len = len(sequence) + if longest_ref_unit is not None: + # look for full containment of the longest ref repeat + # this is so we can detect [2]>[1] (or really any repeat length variation to just 1) + pattern = f'({re.escape(longest_ref_unit.repeat_unit)})+' + match = re.fullmatch(pattern, sequence) + if match: + repeat_count = len(sequence) // len(longest_ref_unit.repeat_unit) + ru = RepeatUnit(repeat_count, longest_ref_unit.repeat_unit, len(sequence), sequence) + return [ru] + if reverse: i = seq_len # Start from the end of the sequence while i > 0: diff --git a/tests/test_repeats.py b/tests/test_repeats.py index c40435db..deb1f2ba 100644 --- a/tests/test_repeats.py +++ b/tests/test_repeats.py @@ -75,6 +75,7 @@ def test_CACNA1_repeat(self): assert ra2.alt_str == "C[1]TGC[26]" assert to_hgvs_repeat(var_g.ac, fs, ra2) == 'NC_000019.10:g.13207859_13207898C[1]TGC[26]' # not valid, we should trim off the shuffle-able C for that. + def test_ATXN7_repeat(self): """ A repeated AGC tri-nucleotide sequence in the ATXN7 gene on chromosome 3 @@ -179,7 +180,8 @@ def test_homopolymer(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, ("NC_000005.10:g.123346517_123346518insATTA", True, "ATTA", 2, 3, "ATTA[2]>ATTA[3]"), ("NC_000005.10:g.123346522_123346525dup", True, "ATTA", 2, 3, "ATTA[2]>ATTA[3]"), ("NC_000019.10:g.45770210_45770212del", True, "CAG", 20, 19, "CAG[20]C[1]A[1]>CAG[19]C[1]A[1]"), # note this one can be shuffled CAG/GCA - ("NC_000007.14:g.117548628_117548629insTTTT", True, "T", 7 , 11, "T[7]>T[11]") + ("NC_000007.14:g.117548628_117548629insTTTT", True, "T", 7 , 11, "T[7]>T[11]"), + ("NC_000009.11:g.35079521_35079523del", True, 'TGG', 2, 1, "TGG[2]>TGG[1]" ), ] ) def test_repeats(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, s): @@ -194,12 +196,24 @@ def test_repeats(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, s): # note: in some cases is_repeat is False, although repeat_unit contains some data. # however in those cases the repeat patter was not considered significant enough to name the whole variant accordingly - self.assertEqual(is_repeat, ra.is_repeat) + self.assertEqual(is_repeat, ra.is_repeat) self.assertEqual(repeat_unit, ra.repeat_units_alt[0].repeat_unit) - self.assertEqual(ref_count, ra.repeat_units_ref[0].repeat_count) + self.assertEqual(ref_count, ra.repeat_units_ref[0].repeat_count) self.assertEqual(alt_count, ra.repeat_units_alt[0].repeat_count) + def test_large_variant(self): + """ Large variants would take too much time to analyze, plus the results are not that useful.""" + + hgvs_g = "NC_000001.11:g.9976249_9983617dup" + hp = hgvs.parser.Parser() + var_g = hp.parse_hgvs_variant(hgvs_g) + config = PrettyConfig(self.hdp, None, None) + dc = DataCompiler(config) + fs = dc.get_shuffled_variant(var_g, 0) + ra = RepeatAnalyser(fs) + assert not ra.is_repeat + @parameterized.expand( [ From 703efbf637128820c73211f4d7eb1730a027f683 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Wed, 18 Sep 2024 20:37:11 -0700 Subject: [PATCH 24/31] feat(repeats): moving max repeat config to global config. Also a small improvement at the loss of repeat check. --- src/hgvs/_data/defaults.ini | 3 +++ src/hgvs/repeats.py | 23 +++++++++++++++-------- tests/test_repeats.py | 8 ++++---- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/hgvs/_data/defaults.ini b/src/hgvs/_data/defaults.ini index 7e1ecd2d..92227e52 100644 --- a/src/hgvs/_data/defaults.ini +++ b/src/hgvs/_data/defaults.ini @@ -30,6 +30,9 @@ shuffle_direction = 3 validate = True window_size = 20 +[repeats] +max_repeat_length = 200 + [lru_cache] maxsize = 100 diff --git a/src/hgvs/repeats.py b/src/hgvs/repeats.py index 4ce612a0..7ff4624a 100644 --- a/src/hgvs/repeats.py +++ b/src/hgvs/repeats.py @@ -3,7 +3,7 @@ from dataclasses import dataclass import re - +import hgvs from hgvs.pretty.models import VariantCoords @@ -27,7 +27,7 @@ def __init__(self, fs: VariantCoords, reverse:bool=False) -> None: self.reverse = reverse # protect from too large sequences getting analyzed. - if len(self.ref_str) > 350: + if len(self.ref_str) > hgvs.global_config.repeats.max_repeat_length: self.repeat_units_ref = [] self.repeat_units_alt = [] return @@ -49,7 +49,7 @@ def __init__(self, fs: VariantCoords, reverse:bool=False) -> None: expected_size = len(self.ref_str) / 3 if longest_r_unit.block_size < expected_size: return - + if longest_r_unit.repeat_unit not in self.alt_str: return @@ -59,7 +59,6 @@ def __init__(self, fs: VariantCoords, reverse:bool=False) -> None: ref_repeat = get_repeat_str(self.ref_str, self.alt_str, self.repeat_units_ref, self.repeat_units_alt, self.reverse) alt_repeat = get_repeat_str(self.alt_str, self.ref_str, self.repeat_units_alt, self.repeat_units_ref, self.reverse) - self.ref_str = ref_repeat self.alt_str = alt_repeat @@ -91,11 +90,20 @@ def detect_repetitive_block_lengths(sequence: str, longest_ref_unit:RepeatUnit|N # look for full containment of the longest ref repeat # this is so we can detect [2]>[1] (or really any repeat length variation to just 1) pattern = f'({re.escape(longest_ref_unit.repeat_unit)})+' - match = re.fullmatch(pattern, sequence) + match = re.search(pattern, sequence) + if match: repeat_count = len(sequence) // len(longest_ref_unit.repeat_unit) - ru = RepeatUnit(repeat_count, longest_ref_unit.repeat_unit, len(sequence), sequence) - return [ru] + block = repeat_count* longest_ref_unit.repeat_unit + ru = RepeatUnit(repeat_count, longest_ref_unit.repeat_unit, len(block), block) + result.append(ru) + shuffleable_bases = sequence[repeat_count*len(ru.repeat_unit):] + for b in shuffleable_bases: + ru = RepeatUnit(1, b, 1, b) + result.append(ru) + return result + + if reverse: i = seq_len # Start from the end of the sequence @@ -217,7 +225,6 @@ def assemble_repeat_string(sequence: str, repeat_units: list[RepeatUnit], revers return_str = "" primary_repeat_unit = repeat_units.copy() seq = sequence - if reverse: while len(seq) > 0: found_unit = None diff --git a/tests/test_repeats.py b/tests/test_repeats.py index deb1f2ba..9e572b41 100644 --- a/tests/test_repeats.py +++ b/tests/test_repeats.py @@ -2,7 +2,7 @@ from hgvs.assemblymapper import AssemblyMapper from parameterized import parameterized - +import time import pytest import hgvs import hgvs.dataproviders @@ -114,7 +114,6 @@ def test_ATXN7_repeat(self): # invalid HGVS, due to the shuffle-able bases, but comes pretty close to what HGVS recommends. assert to_hgvs_repeat(var_g.ac, fs, ra2) == 'NC_000003.12:g.63912685_63912716G[1]C[1]AGC[13]' - class TestRepeats(unittest.TestCase): @classmethod @@ -181,7 +180,8 @@ def test_homopolymer(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, ("NC_000005.10:g.123346522_123346525dup", True, "ATTA", 2, 3, "ATTA[2]>ATTA[3]"), ("NC_000019.10:g.45770210_45770212del", True, "CAG", 20, 19, "CAG[20]C[1]A[1]>CAG[19]C[1]A[1]"), # note this one can be shuffled CAG/GCA ("NC_000007.14:g.117548628_117548629insTTTT", True, "T", 7 , 11, "T[7]>T[11]"), - ("NC_000009.11:g.35079521_35079523del", True, 'TGG', 2, 1, "TGG[2]>TGG[1]" ), + ("NC_000009.11:g.35079521_35079523del", True, 'TGG', 2, 1, "TGG[2]>TGG[1]" ), + ("NC_000001.11:g.6490477_6490484del", True, "TCTAAGGC", 2, 1, "TCTAAGGC[2]T[1]C[1]>TCTAAGGC[1]T[1]C[1]") ] ) def test_repeats(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, s): @@ -279,5 +279,5 @@ def test_detect_repetitive_block_lengths(self, ref, alt, ref_count, ref_repeat_u assert alt_repeat == expected_alt_repeat - + From bb7eb3c01fe941473b5b1f134ce8f748cce45f24 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Wed, 18 Sep 2024 20:59:51 -0700 Subject: [PATCH 25/31] skipfeat(repeats): skipping more tests that are too data heavy. --- tests/test_repeats.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_repeats.py b/tests/test_repeats.py index 9e572b41..8ad48965 100644 --- a/tests/test_repeats.py +++ b/tests/test_repeats.py @@ -2,7 +2,6 @@ from hgvs.assemblymapper import AssemblyMapper from parameterized import parameterized -import time import pytest import hgvs import hgvs.dataproviders @@ -19,7 +18,8 @@ def to_hgvs_repeat(chrom_ac, fs: VariantCoords , ra: RepeatAnalyser): """ return f"{chrom_ac}:g.{fs.start+1}_{fs.end}{ra.alt_str}" - + +@pytest.mark.skip(reason="too data hungry") class TestHGVSExamples(unittest.TestCase): """ The variants in this class are taken from the HGVS nomenclature page about repeats https://hgvs-nomenclature.org/stable/recommendations/DNA/repeated/ @@ -114,6 +114,7 @@ def test_ATXN7_repeat(self): # invalid HGVS, due to the shuffle-able bases, but comes pretty close to what HGVS recommends. assert to_hgvs_repeat(var_g.ac, fs, ra2) == 'NC_000003.12:g.63912685_63912716G[1]C[1]AGC[13]' +@pytest.mark.skip(reason="too data hungry") class TestRepeats(unittest.TestCase): @classmethod @@ -214,6 +215,8 @@ def test_large_variant(self): ra = RepeatAnalyser(fs) assert not ra.is_repeat + +class TestRepeatMethods: @parameterized.expand( [ From fed5af4467009ef8831334bddd2e3df4dcc2d318 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Wed, 18 Sep 2024 22:02:06 -0700 Subject: [PATCH 26/31] feat(repeats): improvements in repeat check. --- src/hgvs/repeats.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/hgvs/repeats.py b/src/hgvs/repeats.py index 7ff4624a..088ef86a 100644 --- a/src/hgvs/repeats.py +++ b/src/hgvs/repeats.py @@ -37,7 +37,7 @@ def __init__(self, fs: VariantCoords, reverse:bool=False) -> None: if len(self.repeat_units_ref) == 0 and len(self.repeat_units_alt) ==0 : return - + # check longest repeat blocks: # we only look at ref to determine if there are repeats # If ref has no repeat, we don't call this a repeat variant, even if alt would have a repetitive unit @@ -98,9 +98,8 @@ def detect_repetitive_block_lengths(sequence: str, longest_ref_unit:RepeatUnit|N ru = RepeatUnit(repeat_count, longest_ref_unit.repeat_unit, len(block), block) result.append(ru) shuffleable_bases = sequence[repeat_count*len(ru.repeat_unit):] - for b in shuffleable_bases: - ru = RepeatUnit(1, b, 1, b) - result.append(ru) + rus = detect_repetitive_block_lengths(shuffleable_bases, reverse = reverse) + result.extend(rus) return result @@ -236,9 +235,16 @@ def assemble_repeat_string(sequence: str, repeat_units: list[RepeatUnit], revers break if not found_unit: # remove one character if no matching repeat unit is found + count = 1 seq_char = seq[0] seq = seq[:-1] - return_str = f"{seq_char}[1]" + return_str + + # count consecutive repeating chars from the end + while seq and seq[-1] == seq_char: + count += 1 + seq = seq[:-1] + + return_str = f"{seq_char}[{count}]" + return_str else: # forward direction while len(seq) > 0: @@ -251,9 +257,16 @@ def assemble_repeat_string(sequence: str, repeat_units: list[RepeatUnit], revers break if not found_unit: # remove one character if no matching repeat unit is found + count = 1 seq_char = seq[0] seq = seq[1:] - return_str += f"{seq_char}[1]" + + # count consecutive repeating chars + while seq and seq[0] == seq_char: + count += 1 + seq = seq[1:] + + return_str += f"{seq_char}[{count}]" return return_str From 9a2e049e16c121055510305b128810941f4a2767 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Wed, 2 Oct 2024 21:29:51 -0700 Subject: [PATCH 27/31] feat(reverse-strand): Now showing reverse strand transcript in 5'-3' orientation relative to the transcript by default (can be configured). --- src/hgvs/pretty/console/chrom_seq_renderer.py | 8 ++- .../console/chrom_seq_reverse_renderer.py | 7 +- src/hgvs/pretty/console/prot_seq_renderer.py | 15 ++-- src/hgvs/pretty/console/shuffled_variant.py | 22 ++++-- src/hgvs/pretty/console/tx_alig_renderer.py | 7 +- .../console/tx_ref_disagree_renderer.py | 6 +- src/hgvs/pretty/datacompiler.py | 6 +- src/hgvs/pretty/models.py | 1 + src/hgvs/pretty/pretty_print.py | 13 ++-- tests/test_pretty_print.py | 70 +++++++++++++++++-- 10 files changed, 124 insertions(+), 31 deletions(-) diff --git a/src/hgvs/pretty/console/chrom_seq_renderer.py b/src/hgvs/pretty/console/chrom_seq_renderer.py index 6152d474..7b1d8a89 100644 --- a/src/hgvs/pretty/console/chrom_seq_renderer.py +++ b/src/hgvs/pretty/console/chrom_seq_renderer.py @@ -5,7 +5,13 @@ class ChromSeqRendered(BasicRenderer): def legend(self) -> str: - return "seq -> : " + + if self.orientation < 0 and self.config.reverse_display: + arrow = '<-' + else: + arrow = '->' + + return f"seq {arrow} : " def display(self, data: VariantData) -> str: """colors the ref sequences with adenine (A, green), thymine (T, red), cytosine (C, yellow), and guanine (G, blue)""" diff --git a/src/hgvs/pretty/console/chrom_seq_reverse_renderer.py b/src/hgvs/pretty/console/chrom_seq_reverse_renderer.py index 22d98ffa..b4c9452d 100644 --- a/src/hgvs/pretty/console/chrom_seq_reverse_renderer.py +++ b/src/hgvs/pretty/console/chrom_seq_reverse_renderer.py @@ -7,7 +7,12 @@ class ChromReverseSeqRendered(BasicRenderer): def legend(self) -> str: - return "seq <- : " + if self.orientation < 0 and self.config.reverse_display: + arrow = '->' + else: + arrow = '<-' + + return f"seq {arrow} : " def display(self, data: VariantData) -> str: """colors the ref sequences with adenine (A, green), thymine (T, red), cytosine (C, yellow), and guanine (G, blue)""" diff --git a/src/hgvs/pretty/console/prot_seq_renderer.py b/src/hgvs/pretty/console/prot_seq_renderer.py index 7782621e..c2439913 100644 --- a/src/hgvs/pretty/console/prot_seq_renderer.py +++ b/src/hgvs/pretty/console/prot_seq_renderer.py @@ -5,10 +5,15 @@ class ProtSeqRenderer(BasicRenderer): def legend(self): - legend = "aa seq -> : " - if self.orientation < 0: - legend = "aa seq <- : " - return legend + + if self.orientation > 0: + arrow = "->" + elif self.orientation < 0 and self.config.reverse_display: + arrow = "->" + else: + arrow = "<-" + + return f"aa seq {arrow} : " def display(self, data: VariantData) -> str: if not data.var_c_or_n: @@ -19,8 +24,6 @@ def display(self, data: VariantData) -> str: var_str = "" for pdata in data.position_details: - p = pdata.chromosome_pos - if not pdata.mapped_pos: var_str += " " continue diff --git a/src/hgvs/pretty/console/shuffled_variant.py b/src/hgvs/pretty/console/shuffled_variant.py index 1bbdeebc..02bc1d35 100644 --- a/src/hgvs/pretty/console/shuffled_variant.py +++ b/src/hgvs/pretty/console/shuffled_variant.py @@ -1,3 +1,4 @@ +from logging import info from hgvs.pretty.models import VariantCoords, VariantData from hgvs.pretty.console.renderer import BasicRenderer from hgvs.sequencevariant import SequenceVariant @@ -61,9 +62,13 @@ def display(self, data: VariantData) -> str: var_str = "" in_range = False + + reverse_display = False + if self.orientation < 0 and self.config.reverse_display: + reverse_display = True + for pdata in data.position_details: p = pdata.chromosome_pos - if not p: if in_range: var_str += "-" @@ -71,13 +76,22 @@ def display(self, data: VariantData) -> str: var_str += " " continue - if p == start: + if not reverse_display and p == start: + var_str += split_char + in_range = True + elif reverse_display and p == end: var_str += split_char in_range = True - elif p == end: + elif not reverse_display and p == end: var_str += split_char in_range = False - elif p > end and in_range: + elif reverse_display and p == start: + var_str += split_char + in_range = False + elif not reverse_display and p > end and in_range: + in_range = False + var_str += " " + elif reverse_display and p < start and in_range: in_range = False var_str += " " elif in_range: diff --git a/src/hgvs/pretty/console/tx_alig_renderer.py b/src/hgvs/pretty/console/tx_alig_renderer.py index 810ecf2f..f5330e8e 100644 --- a/src/hgvs/pretty/console/tx_alig_renderer.py +++ b/src/hgvs/pretty/console/tx_alig_renderer.py @@ -7,8 +7,11 @@ class TxAligRenderer(BasicRenderer): def legend(self) -> str: - orientation = "->" - if self.orientation < 0: + if self.orientation > 0: + orientation = "->" + elif self.orientation < 0 and self.config.reverse_display: + orientation = "->" + else: orientation = "<-" return f"tx seq {orientation} : " diff --git a/src/hgvs/pretty/console/tx_ref_disagree_renderer.py b/src/hgvs/pretty/console/tx_ref_disagree_renderer.py index 083939c8..2c92679e 100644 --- a/src/hgvs/pretty/console/tx_ref_disagree_renderer.py +++ b/src/hgvs/pretty/console/tx_ref_disagree_renderer.py @@ -17,10 +17,8 @@ def display(self, data: VariantData) -> str: from hgvs.pretty.console.constants import ENDC, TRED var_str = "" - counter = -1 - for p in range(data.display_start + 1, data.display_end + 1): - counter += 1 - pdata = data.position_details[counter] + + for pdata in data.position_details: c_offset = pdata.c_offset if not pdata.mapped_pos: var_str += " " diff --git a/src/hgvs/pretty/datacompiler.py b/src/hgvs/pretty/datacompiler.py index b7b83cc3..8418aea5 100644 --- a/src/hgvs/pretty/datacompiler.py +++ b/src/hgvs/pretty/datacompiler.py @@ -152,7 +152,7 @@ def _get_prot_alt( c_pos = int(c_interval.start.base) - 1 c3 = (c_pos) % 3 aa_char = tlc[c3] - if strand < 0: + if strand < 0 and not self.config.reverse_display: aa_char = tlc[2 - c3] return ProteinData(c_pos, aa, tlc, aa_char, var_p, is_init_met, is_stop_codon, aa_index) @@ -347,6 +347,10 @@ def data( strand = mapper.strand if mapper else 1 + if strand < 0 and self.config.reverse_display: + pd = reversed(position_details) + position_details = list(pd) + vd = VariantData( seq_start, seq_end, diff --git a/src/hgvs/pretty/models.py b/src/hgvs/pretty/models.py index c139ec10..54e2d945 100644 --- a/src/hgvs/pretty/models.py +++ b/src/hgvs/pretty/models.py @@ -109,3 +109,4 @@ class PrettyConfig: all: bool = False # print all possible hgvs_c (for all UTA transcripts) show_reverse_strand: bool = False # show the reverse strand sequence for the chromosome alt_aln_method:str = 'splign' + reverse_display:bool = True # display reverse strand as the main strand (5'->3') diff --git a/src/hgvs/pretty/pretty_print.py b/src/hgvs/pretty/pretty_print.py index 573e7b6a..f144a189 100644 --- a/src/hgvs/pretty/pretty_print.py +++ b/src/hgvs/pretty/pretty_print.py @@ -21,8 +21,6 @@ from hgvs.sequencevariant import SequenceVariant - - class PrettyPrint: """A class that provides a pretty display of the genomic context of a variant.""" @@ -37,7 +35,8 @@ def __init__( infer_hgvs_c=True, all=False, show_reverse_strand=False, - alt_aln_method='splign' + alt_aln_method='splign', + reverse_display = True ): """ :param hdp: HGVS Data Provider Interface-compliant instance @@ -60,7 +59,8 @@ def __init__( infer_hgvs_c, all, show_reverse_strand, - alt_aln_method + alt_aln_method, + reverse_display ) def _get_assembly_mapper(self) -> AssemblyMapper: @@ -213,7 +213,9 @@ def create_repre( renderer_cls = [ChrPositionInfo, ChrRuler, ChromSeqRendered] - if self.config.show_reverse_strand: + # if we show reverse strand transcripts in forward facing orientation, always + # show both forward and reverse strand sequences. + if self.config.show_reverse_strand or self.config.reverse_display and data.strand < 0: renderer_cls.append(ChromReverseSeqRendered) renderer_cls.append(TxRefDisagreeRenderer) @@ -249,7 +251,6 @@ def create_repre( self.config, data.strand, var_g, fully_justified_var ) fully_justified_str = fully_justified_renderer.display(data) - if self.config.showAllShuffleableRegions: var_str += shuffled_seq_header + left_shuffled_str + "\n" var_str += shuffled_seq_header + right_shuffled_str + "\n" diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index 946ffc43..cbd3dc46 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -17,6 +17,7 @@ def setUpClass(cls): cls.hdp = hgvs.dataproviders.uta.connect(mode=None, cache=None) cls.pp = PrettyPrint( cls.hdp, + reverse_display=False ) cls.pp.useColor = False @@ -87,11 +88,39 @@ def test_var_c1_reverse(self): for r, e in zip(result, expected_str): self.assertEqual(e, r) + def test_var_c1_reverse_flipped_display(self): + """ test the reversed display on <- strand""" + hgvs_c = "NM_001177507.2:c.1=" + var_c = self.hp.parse(hgvs_c) + + pp = PrettyPrint(self.hdp, show_reverse_strand=True, reverse_display=True) + result = pp.display(var_c) + print(result) + result = result.split("\n") + expected_str = ( + "hgvs_g : NC_000007.13:g.36763753=\n" + + "hgvs_c : NM_001177507.2:c.1=\n" + + "hgvs_p : NP_001170978.1:p.Met1?\n" + + " : 36,763,770 36,763,750\n" + + "chrom pos : | . | . | . | . \n" + + "seq <- : AACCCCACGTATCGAGCCTCTACGTCAGGGGGACCTTTTAG\n" + + "seq -> : TTGGGGTGCATAGCTCGGAGATGCAGTCCCCCTGGAAAATC\n" + + "region : = \n" + + "tx seq -> : ttggggtgcatagctcggagATGCAGTCCCCCTGGAAAATC\n" + + "tx pos : | . | . | . | . | \n" + + " : -20 -10 1 10 20\n" + + "aa seq -> : MetGlnSerProTrpLysIle\n" + + "aa pos : ... \n" + + " : 1 \n" + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + def test_var_g_substitution(self): hgvs_g = "NC_000007.13:g.36561662C>T" var_g = self.hp.parse(hgvs_g) - pp = PrettyPrint(self.hdp, show_reverse_strand=True) + pp = PrettyPrint(self.hdp, show_reverse_strand=True, reverse_display=False) result = pp.display(var_g) print(result) @@ -132,6 +161,35 @@ def test_var_g_ins(self): for r, e in zip(result, expected_str): self.assertEqual(e, r) + def test_atta_forward(self): + """ the ATTA[2]>ATTA[3] variant now displayed forward facing:""" + + hgvs_g = "NC_000005.10:g.123346517_123346518insATTA" + var_g = self.hp.parse(hgvs_g) + pp = PrettyPrint(self.hdp, show_reverse_strand=True, reverse_display=True) + result = pp.display(var_g) + print(result) + result = result.split("\n") + expected_str = ( + "hgvs_g : NC_000005.10:g.123346517_123346518insATTA\n" + + "hgvs_c : NM_001166226.1:c.*1_*2insTAAT\n" + + "hgvs_p : NP_001159698.1:p.?\n" + + " : 123,346,540 123,346,520 123,346,500\n" + + "chrom pos : . | . | . | . | . | \n" + + "seq <- : AACCGTTTTTCGTTACGGTCATTAATTATTGTAAACCTTTTCGAAATA\n" + + "seq -> : TTGGCAAAAAGCAATGCCAGTAATTAATAACATTTGGAAAAGCTTTAT\n" + + "region : |------| \n" + + "tx seq -> : TTGGCAAAAAGCAATGCCAGTAATTAATAACATTTGGAAAAGCTTTAT\n" + + "tx pos : | . | . | | . | . | \n" + + " : 2860 2870 2880 *10 *20\n" + + "aa seq -> : LeuAlaLysSerAsnAlaSerAsnTer \n" + + "aa pos : ... ||| \n" + + " : 960 \n" + + "ref>alt : ATTA[2]>ATTA[3]\n" + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + def test_var_g_dup(self): hgvs_g = "NC_000005.10:g.123346522_123346525dup" var_g = self.hp.parse(hgvs_g) @@ -366,7 +424,7 @@ def test_tiny(self): hgvs_g = "NC_000005.10:g.123346517_123346518insATTA" var_g = self.hp.parse(hgvs_g) - tiny_pp = PrettyPrint(self.hdp, padding_left=0, padding_right=0) + tiny_pp = PrettyPrint(self.hdp, padding_left=0, padding_right=0, reverse_display=False) result = tiny_pp.display(var_g) print(result) @@ -408,7 +466,7 @@ def test_hgvs_c(self): hgvs_c = "NM_004572.3:c.-9_12dup" var_c = self.hp.parse(hgvs_c) pp = PrettyPrint( - self.hdp, padding_left=10, padding_right=110, useColor=False, showLegend=False + self.hdp, padding_left=10, padding_right=110, useColor=False, showLegend=False, reverse_display=False ) result = pp.display(var_c) @@ -508,7 +566,7 @@ def test_ref_disagree_del(self): hgvs_c = "NM_000682.6:c.901_911del" # a del variant var_c = self.hp.parse(hgvs_c) - pp = PrettyPrint(self.hdp, infer_hgvs_c=True, padding_left=30, padding_right=40) + pp = PrettyPrint(self.hdp, infer_hgvs_c=True, padding_left=30, padding_right=40, reverse_display=False) result = pp.display(var_c) print(result) result = result.split("\n") @@ -520,7 +578,7 @@ def test_ref_disagree_del(self): + " : 96,780,960 96,780,980 96,781,000 96,781,020\n" + "chrom pos : | . | . | . | . _________ | . | . | . | . \n" + "seq -> : TGCCTGGGGTTCACACTCTTCCTCCTCCTCCTCCTCCTCTTC.........GGCTTCATCCTCTGGAGATGCCCCACAAACACCCTCCTTC\n" - + "tx ref dif: DDDDDDDDD \n" + + "tx ref dif: DDDDDDDDD \n" + "region : x----------x \n" + "tx seq <- : ACGGACCCCAAGTGTGAGAAGGAGGAGGAGGAGGAGGAGAAGGAGGAGAAGTCGAAGTAGGAGACCTCTACGGGGTGTTTGTGGGAGGAAG\n" + "tx pos : | . | . | . | . | . | . | . | . | . \n" @@ -547,7 +605,7 @@ def test_ruler(self): """Test the ruler display option turned on.""" hgvs_c = "NM_001111.4:c.298G>A" var_c = self.hp.parse(hgvs_c) - pp = PrettyPrint(self.hdp, showLegend=False) + pp = PrettyPrint(self.hdp, showLegend=False, reverse_display=False) result = pp.display(var_c) From b82ab35630a96fc129600ae55295fb9a612fac38 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Wed, 2 Oct 2024 21:40:15 -0700 Subject: [PATCH 28/31] feat(is_rna): moving the check if a transcript is RNA only to datacompiler --- src/hgvs/pretty/console/tx_pos.py | 7 ++----- src/hgvs/pretty/datacompiler.py | 3 +++ src/hgvs/pretty/models.py | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/hgvs/pretty/console/tx_pos.py b/src/hgvs/pretty/console/tx_pos.py index bf2d09f1..eae78577 100644 --- a/src/hgvs/pretty/console/tx_pos.py +++ b/src/hgvs/pretty/console/tx_pos.py @@ -11,10 +11,7 @@ def display(self, data: VariantData) -> str: """show the position of the transcript seq""" var_str = "" - if data.var_c_or_n: - rna_coding = data.var_c_or_n.ac.startswith("NR_") - else: - rna_coding = False + is_rna = data.is_rna count = -1 for pdata in data.position_details: @@ -25,7 +22,7 @@ def display(self, data: VariantData) -> str: c_pos = pdata.c_pos interval = pdata.c_interval - if rna_coding: + if is_rna: c_pos = pdata.n_pos interval = pdata.n_interval diff --git a/src/hgvs/pretty/datacompiler.py b/src/hgvs/pretty/datacompiler.py index 8418aea5..a037bcce 100644 --- a/src/hgvs/pretty/datacompiler.py +++ b/src/hgvs/pretty/datacompiler.py @@ -351,6 +351,8 @@ def data( pd = reversed(position_details) position_details = list(pd) + is_rna = var_c_or_n.ac.startswith("NR_") # not sure how to check this for ENSTs + vd = VariantData( seq_start, seq_end, @@ -365,6 +367,7 @@ def data( var_c_or_n, var_p, position_details, + is_rna ) return vd diff --git a/src/hgvs/pretty/models.py b/src/hgvs/pretty/models.py index 54e2d945..d5fcd472 100644 --- a/src/hgvs/pretty/models.py +++ b/src/hgvs/pretty/models.py @@ -90,6 +90,7 @@ class VariantData: var_p: SequenceVariant = None position_details: List[PositionDetail] = None all: bool = False + is_rna: bool = False @dataclass From cdd87a52416c94429539c676de0d0988926239d3 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Wed, 2 Oct 2024 21:53:44 -0700 Subject: [PATCH 29/31] fix --- src/hgvs/pretty/datacompiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hgvs/pretty/datacompiler.py b/src/hgvs/pretty/datacompiler.py index a037bcce..1e1975a3 100644 --- a/src/hgvs/pretty/datacompiler.py +++ b/src/hgvs/pretty/datacompiler.py @@ -367,7 +367,7 @@ def data( var_c_or_n, var_p, position_details, - is_rna + is_rna=is_rna ) return vd From 9a778926640d30169a63fb4540bb53a804be2b88 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Fri, 15 Nov 2024 19:32:46 -0800 Subject: [PATCH 30/31] fixing off-by one error at transcript start, problematic intergenic variants, and only one assembly mapper on pretty print. --- src/hgvs/easy.py | 3 +- src/hgvs/pretty/console/tx_alig_renderer.py | 2 +- .../pretty/console/tx_mapping_renderer.py | 6 +- src/hgvs/pretty/console/tx_pos.py | 4 +- src/hgvs/pretty/datacompiler.py | 29 ++--- src/hgvs/pretty/models.py | 4 +- src/hgvs/pretty/pretty_print.py | 50 +++----- src/hgvs/shell.py | 3 +- tests/test_pretty_print.py | 107 +++++++++++++++--- 9 files changed, 133 insertions(+), 75 deletions(-) diff --git a/src/hgvs/easy.py b/src/hgvs/easy.py index 66d82a90..54ca26d4 100644 --- a/src/hgvs/easy.py +++ b/src/hgvs/easy.py @@ -67,7 +67,8 @@ t_to_g = projector.t_to_g t_to_p = projector.t_to_p get_relevant_transcripts = am38.relevant_transcripts -pretty = PrettyPrint(hdp, useColor=True, showLegend=True) +pretty37 = PrettyPrint(hdp, am37, useColor=True, showLegend=True) +pretty38 = PrettyPrint(hdp, am38, useColor=True, showLegend=True) # # Copyright 2018 HGVS Contributors (https://github.com/biocommons/hgvs) diff --git a/src/hgvs/pretty/console/tx_alig_renderer.py b/src/hgvs/pretty/console/tx_alig_renderer.py index f5330e8e..a6fbd3e3 100644 --- a/src/hgvs/pretty/console/tx_alig_renderer.py +++ b/src/hgvs/pretty/console/tx_alig_renderer.py @@ -35,7 +35,7 @@ def display(self, data: VariantData) -> str: counter += 1 - if not pdata.mapped_pos: + if pdata.mapped_pos is None: var_str += " " continue diff --git a/src/hgvs/pretty/console/tx_mapping_renderer.py b/src/hgvs/pretty/console/tx_mapping_renderer.py index 8fd78778..bc3ddffa 100644 --- a/src/hgvs/pretty/console/tx_mapping_renderer.py +++ b/src/hgvs/pretty/console/tx_mapping_renderer.py @@ -40,12 +40,12 @@ def display(self, data: VariantData) -> str: prev_c_pos = c_pos continue - if (c_pos + 1) % 10 == 0: + if (c_pos) % 10 == 0: var_str += "|" prev_c_pos = c_pos continue - elif (c_pos + 1) % 5 == 0: + elif (c_pos) % 5 == 0: var_str += "." prev_c_pos = c_pos continue @@ -69,7 +69,7 @@ def display(self, data: VariantData) -> str: prev_c_pos = c_pos continue - elif c_pos == 0: + elif c_pos == 1: var_str += "|" var_str += " " diff --git a/src/hgvs/pretty/console/tx_pos.py b/src/hgvs/pretty/console/tx_pos.py index eae78577..49f003b9 100644 --- a/src/hgvs/pretty/console/tx_pos.py +++ b/src/hgvs/pretty/console/tx_pos.py @@ -33,13 +33,13 @@ def display(self, data: VariantData) -> str: if len(var_str) > count: continue - if (c_pos + 1) % 10 == 0: + if (c_pos) % 10 == 0: # if pdata.c_interval.start.datum == Datum.CDS_END: # var_str += "*" var_str += f"{interval} " continue - elif c_pos == 0: + elif c_pos == 1: var_str += f"{interval} " continue var_str += " " diff --git a/src/hgvs/pretty/datacompiler.py b/src/hgvs/pretty/datacompiler.py index 1e1975a3..bdf2d0ba 100644 --- a/src/hgvs/pretty/datacompiler.py +++ b/src/hgvs/pretty/datacompiler.py @@ -206,24 +206,19 @@ def data( if tx_ac: tx_seq = self.config.hdp.get_seq(tx_ac) - if self.config.default_assembly == "GRCh37": - am = self.config.am37 - else: - am = self.config.am38 - - mapper = am._fetch_AlignmentMapper( + + mapper = self.config.assembly_mapper._fetch_AlignmentMapper( tx_ac=tx_ac, alt_ac=var_g.ac, alt_aln_method="splign" ) else: tx_seq = "" mapper = None - # print(tx_seq) # we don't know the protein ac, get it looked up: pro_ac = None if var_c_or_n and var_c_or_n.type == "c": - var_p = am.c_to_p(var_c_or_n) + var_p = self.config.assembly_mapper.c_to_p(var_c_or_n) reference_data = RefTranscriptData(self.config.hdp, tx_ac, pro_ac) else: var_p = None @@ -277,8 +272,8 @@ def data( reference_data, pdata, cig, - prev_c_pos + 1, - prev_n_pos + 1, + prev_c_pos, + prev_n_pos, ) exon_nr, feat = self._get_exon_nr(tx_exons, chromosome_pos) @@ -292,7 +287,7 @@ def data( pdata.mapped_pos = prev_mapped_pos pdata.mapped_pos_offset = mapped_pos_offset pdata.cigar_ref = cig - pdata.ref = chrom_seq[chromosome_pos] + pdata.ref = chrom_seq[chromosome_pos-1] if mapper.strand > 0: prev_c_pos += 1 @@ -321,12 +316,12 @@ def data( pdata.n_interval = n_interval if c_interval is not None: pdata.c_interval = c_interval - c_pos = int(c_interval.start.base) - 1 + c_pos = int(c_interval.start.base) prev_c_pos = c_pos else: prev_c_pos = -1 - n_pos = int(n_interval.start.base) - 1 + n_pos = int(n_interval.start.base) prev_n_pos = n_pos self._populate_with_n_c( @@ -351,7 +346,7 @@ def data( pd = reversed(position_details) position_details = list(pd) - is_rna = var_c_or_n.ac.startswith("NR_") # not sure how to check this for ENSTs + is_rna = var_c_or_n and var_c_or_n.ac.startswith("NR_") # not sure how to check this for ENSTs vd = VariantData( seq_start, @@ -421,10 +416,10 @@ def _populate_with_n_c( n_interval, c_interval, ): - n_pos = int(n_interval.start.base) - 1 + n_pos = int(n_interval.start.base) if c_interval: - c_pos = int(c_interval.start.base) - 1 + c_pos = int(c_interval.start.base) pdata.c_pos = c_pos pdata.c_offset = c_interval.start.offset else: @@ -435,7 +430,7 @@ def _populate_with_n_c( pdata.n_pos = n_pos - pdata.tx = tx_seq[pdata.n_pos] + pdata.tx = tx_seq[pdata.n_pos-1] coding = True if var_c_or_n.type == "n": # rna coding can't be in protein space diff --git a/src/hgvs/pretty/models.py b/src/hgvs/pretty/models.py index d5fcd472..871b1e4b 100644 --- a/src/hgvs/pretty/models.py +++ b/src/hgvs/pretty/models.py @@ -98,11 +98,9 @@ class PrettyConfig: """A container for various configurations.""" hdp: Interface - am37: AssemblyMapper - am38: AssemblyMapper + assembly_mapper: AssemblyMapper padding_left: int = 20 padding_right: int = 20 - default_assembly: str = "GRCh37" useColor: bool = False showLegend: bool = True showAllShuffleableRegions = False diff --git a/src/hgvs/pretty/pretty_print.py b/src/hgvs/pretty/pretty_print.py index f144a189..238fa9b0 100644 --- a/src/hgvs/pretty/pretty_print.py +++ b/src/hgvs/pretty/pretty_print.py @@ -27,7 +27,7 @@ class PrettyPrint: def __init__( self, hdp: hgvs.dataproviders.interface.Interface, - default_assembly: str = "GRCh37", + assembly_mapper:AssemblyMapper, padding_left: int = 20, padding_right: int = 20, useColor=False, @@ -44,37 +44,24 @@ def __init__( :param padding: spacing left and right of the variant for display purposes. """ - am37: AssemblyMapper = AssemblyMapper(hdp, assembly_name="GRCh37", alt_aln_method=alt_aln_method) - am38: AssemblyMapper = AssemblyMapper(hdp, assembly_name="GRCh38", alt_aln_method=alt_aln_method) - + self.config = PrettyConfig( - hdp, - am37, - am38, - padding_left, - padding_right, - default_assembly, - useColor, - showLegend, - infer_hgvs_c, - all, - show_reverse_strand, - alt_aln_method, - reverse_display + hdp=hdp, + assembly_mapper=assembly_mapper, + padding_left=padding_left, + padding_right=padding_right, + useColor=useColor, + showLegend=showLegend, + infer_hgvs_c=infer_hgvs_c, + all=all, + show_reverse_strand=show_reverse_strand, + alt_aln_method=alt_aln_method, + reverse_display=reverse_display ) - def _get_assembly_mapper(self) -> AssemblyMapper: - if self.config.default_assembly == "GRCh37": - am = self.config.am37 - else: - am = self.config.am38 - - return am - def _get_all_transcripts(self, var_g) -> List[str]: - am = self._get_assembly_mapper() - - transcripts = am.relevant_transcripts(var_g) + + transcripts = self.config.assembly_mapper.relevant_transcripts(var_g) return transcripts @@ -87,7 +74,7 @@ def _infer_hgvs_c(self, var_g: SequenceVariant, tx_ac: str = None) -> SequenceVa else: return None - am = self._get_assembly_mapper() + am = self.config.assembly_mapper if tx_ac.startswith("NR_"): var_n = am.g_to_n(var_g, tx_ac) @@ -97,10 +84,7 @@ def _infer_hgvs_c(self, var_g: SequenceVariant, tx_ac: str = None) -> SequenceVa def _map_to_chrom(self, sv: SequenceVariant) -> SequenceVariant: """maps a variant to chromosomal coords, if needed.""" - if self.config.default_assembly == "GRCh37": - am = self.config.am37 - else: - am = self.config.am38 + am = self.config.assembly_mapper if sv.type == "c": return am.c_to_g(sv) diff --git a/src/hgvs/shell.py b/src/hgvs/shell.py index d4d571c6..9fa50023 100755 --- a/src/hgvs/shell.py +++ b/src/hgvs/shell.py @@ -72,7 +72,8 @@ def shell(): normalizer, parse, parser, - pretty, + pretty37, + pretty38, projector, t_to_g, t_to_p, diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index cbd3dc46..d6201309 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -4,6 +4,7 @@ import pytest import hgvs +from hgvs.assemblymapper import AssemblyMapper from hgvs.pretty.pretty_print import PrettyPrint @@ -15,8 +16,11 @@ class Test_SimplePosition(unittest.TestCase): def setUpClass(cls): cls.hp = hgvs.parser.Parser() cls.hdp = hgvs.dataproviders.uta.connect(mode=None, cache=None) + cls.assembly_mapper37 = AssemblyMapper(cls.hdp, assembly_name="GRCh37") + cls.assembly_mapper38 = AssemblyMapper(cls.hdp, assembly_name="GRCh38") cls.pp = PrettyPrint( cls.hdp, + cls.assembly_mapper37, reverse_display=False ) cls.pp.useColor = False @@ -93,7 +97,7 @@ def test_var_c1_reverse_flipped_display(self): hgvs_c = "NM_001177507.2:c.1=" var_c = self.hp.parse(hgvs_c) - pp = PrettyPrint(self.hdp, show_reverse_strand=True, reverse_display=True) + pp = PrettyPrint(self.hdp, self.assembly_mapper37, show_reverse_strand=True, reverse_display=True) result = pp.display(var_c) print(result) result = result.split("\n") @@ -120,7 +124,7 @@ def test_var_g_substitution(self): hgvs_g = "NC_000007.13:g.36561662C>T" var_g = self.hp.parse(hgvs_g) - pp = PrettyPrint(self.hdp, show_reverse_strand=True, reverse_display=False) + pp = PrettyPrint(self.hdp, self.assembly_mapper38, show_reverse_strand=True, reverse_display=False, useColor=False) result = pp.display(var_g) print(result) @@ -166,7 +170,7 @@ def test_atta_forward(self): hgvs_g = "NC_000005.10:g.123346517_123346518insATTA" var_g = self.hp.parse(hgvs_g) - pp = PrettyPrint(self.hdp, show_reverse_strand=True, reverse_display=True) + pp = PrettyPrint(self.hdp, self.assembly_mapper37, show_reverse_strand=True, reverse_display=True) result = pp.display(var_g) print(result) result = result.split("\n") @@ -285,6 +289,51 @@ def test_del_2bp(self): for r, e in zip(result, expected_str): self.assertEqual(e, r) + def test_first_base(self): + hgvs_n = 'NM_198689.2:n.1C>G' + var_n = self.hp.parse(hgvs_n) + + result = self.pp.display(var_n) + print(result) + result = result.split("\n") + expected_str = ( + "hgvs_g : NC_000021.8:g.46020497C>G\n" + + "hgvs_n : NM_198689.2:n.1C>G\n" + + " : 46,020,480 46,020,500\n" + + "chrom pos : | . | . | . | . \n" + + "seq -> : CTCACTCACCCACTCACTCCCATCTCCTCCAGTTCAATCCC\n" + + "region : G \n" + + "tx seq -> : CATCTCCTCCAGTTCAATCCC\n" + + "tx pos : . | . | \n" + + "aa seq -> : \n" + + "aa pos : \n" + + " : \n" + ).split("\n") + + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_intergenic(self): + hgvs_g = "NC_000021.8:g.29894C>A" + var_g = self.hp.parse(hgvs_g) + + result = self.pp.display(var_g) + print(result) + result = result.split("\n") + expected_str = ( + "hgvs_g : NC_000021.8:g.29894C>A\n" + + " : 29,880 29,890 29,900 29,910\n" + + "chrom pos : . | . | . | . | \n" + + "seq -> : NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN\n" + + "region : A \n" + + "tx pos : \n" + + "aa pos : \n" + + " : " + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + def test_del_1bp_shuffleable(self): hgvs_g = "NC_000007.13:g.36561662del" var_g = self.hp.parse(hgvs_g) @@ -424,7 +473,7 @@ def test_tiny(self): hgvs_g = "NC_000005.10:g.123346517_123346518insATTA" var_g = self.hp.parse(hgvs_g) - tiny_pp = PrettyPrint(self.hdp, padding_left=0, padding_right=0, reverse_display=False) + tiny_pp = PrettyPrint(self.hdp, self.assembly_mapper37, padding_left=0, padding_right=0, reverse_display=False) result = tiny_pp.display(var_g) print(result) @@ -466,7 +515,7 @@ def test_hgvs_c(self): hgvs_c = "NM_004572.3:c.-9_12dup" var_c = self.hp.parse(hgvs_c) pp = PrettyPrint( - self.hdp, padding_left=10, padding_right=110, useColor=False, showLegend=False, reverse_display=False + self.hdp,self.assembly_mapper37, padding_left=10, padding_right=110, useColor=False, showLegend=False, reverse_display=False ) result = pp.display(var_c) @@ -533,7 +582,7 @@ def test_ref_disagree_ref_ins(self): hgvs_c = "NM_198689.2:c.124_135=" # this would match chromosome: "NM_198689.2:c.124_135insCTGCTGCGCCCCCAG" var_c = self.hp.parse(hgvs_c) - pp = PrettyPrint(self.hdp, infer_hgvs_c=True, padding_left=30, padding_right=40) + pp = PrettyPrint(self.hdp, self.assembly_mapper37, infer_hgvs_c=True, padding_left=30, padding_right=40) result = pp.display(var_c) print(result) result = result.split("\n") @@ -564,9 +613,8 @@ def test_ref_disagree_del(self): # hgvs_c = "NM_020469.2:c.188_189=" is NC_000009.11:g.136135237_136135238delinsGC in ref hgvs_c = "NM_000682.6:c.901_911del" # a del variant - var_c = self.hp.parse(hgvs_c) - pp = PrettyPrint(self.hdp, infer_hgvs_c=True, padding_left=30, padding_right=40, reverse_display=False) + pp = PrettyPrint(self.hdp, self.assembly_mapper37,infer_hgvs_c=True, padding_left=30, padding_right=40, reverse_display=False) result = pp.display(var_c) print(result) result = result.split("\n") @@ -577,7 +625,7 @@ def test_ref_disagree_del(self): + "hgvs_p : NP_000673.2:p.(Glu301GlyfsTer7)\n" + " : 96,780,960 96,780,980 96,781,000 96,781,020\n" + "chrom pos : | . | . | . | . _________ | . | . | . | . \n" - + "seq -> : TGCCTGGGGTTCACACTCTTCCTCCTCCTCCTCCTCCTCTTC.........GGCTTCATCCTCTGGAGATGCCCCACAAACACCCTCCTTC\n" + + "seq -> : TGCCTGGGGTTCACACTCTTCCTCCTCCTCCTCCTCCTCTTC.........AGCTTCATCCTCTGGAGATGCCCCACAAACACCCTCCTTC\n" + "tx ref dif: DDDDDDDDD \n" + "region : x----------x \n" + "tx seq <- : ACGGACCCCAAGTGTGAGAAGGAGGAGGAGGAGGAGGAGAAGGAGGAGAAGTCGAAGTAGGAGACCTCTACGGGGTGTTTGTGGGAGGAAG\n" @@ -591,21 +639,52 @@ def test_ref_disagree_del(self): for r, e in zip(result, expected_str): self.assertEqual(e, r) - @pytest.mark.skip(reason="actually not that special, but still a nice variant.") + def test_ref_disagree_del_reverse(self): + hgvs_c = "NM_000682.6:c.901_911del" # a del variant + + var_c = self.hp.parse(hgvs_c) + pp = PrettyPrint(self.hdp, self.assembly_mapper37,infer_hgvs_c=True, padding_left=30, padding_right=40, reverse_display=True) + result = pp.display(var_c) + print(result) + result = result.split("\n") + + expected_str = ( + "hgvs_g : NC_000002.11:g.96780987_96780997del\n" + "hgvs_c : NM_000682.6:c.901_911del\n" + "hgvs_p : NP_000673.2:p.(Glu301GlyfsTer7)\n" + " : 96,781,030 96,781,010 96,780,990 96,780,970\n" + "chrom pos : . | . | . | . | _________ . | . | . | . | \n" + "seq <- : CTTCCTCCCACAAACACCCCGTAGAGGTCTCCTACTTCGA.........CTTCTCCTCCTCCTCCTCCTCCTTCTCACACTTGGGGTCCGT\n" + "seq -> : GAAGGAGGGTGTTTGTGGGGCATCTCCAGAGGATGAAGCT.........GAAGAGGAGGAGGAGGAGGAGGAAGAGTGTGAACCCCAGGCA\n" + "tx ref dif: DDDDDDDDD \n" + "region : x----------x \n" + "tx seq -> : GAAGGAGGGTGTTTGTGGGGCATCTCCAGAGGATGAAGCTGAAGAGGAGGAAGAGGAGGAGGAGGAGGAGGAAGAGTGTGAACCCCAGGCA\n" + "tx pos : . | . | . | . | . | . | . | . | . | \n" + " : 860 870 880 890 900 910 920 930 940\n" + "aa seq -> : nLysGluGlyValCysGlyAlaSerProGluAspGluAlaGluGluGluGluGluGluGluGluGluGluGluGluCysGluProGlnAla\n" + "aa pos : ... ||| ... ||| ... ||| \n" + " : 290 300 310 \n" + "ref>alt : CTCCTCCTCTTC>C" + ).split("\n") + for r, e in zip(result, expected_str): + self.assertEqual(e, r) + + @pytest.mark.skip(reason="actually not that special, but still a nice variant since there is a large shuffle-able sequence on both ends.") def test_exon_boundary_overlap_forward_strand(self): hgvs_c = "NM_001283009.2:c.1228_1266+39del" var_c = self.hp.parse(hgvs_c) - pp = PrettyPrint(self.hdp, showLegend=True, useColor=False) + pp = PrettyPrint(self.hdp, self.assembly_mapper37, showLegend=True, useColor=False) result = pp.display(var_c) print(result) + def test_ruler(self): """Test the ruler display option turned on.""" hgvs_c = "NM_001111.4:c.298G>A" var_c = self.hp.parse(hgvs_c) - pp = PrettyPrint(self.hdp, showLegend=False, reverse_display=False) + pp = PrettyPrint(self.hdp, self.assembly_mapper37, showLegend=False, reverse_display=False) result = pp.display(var_c) @@ -638,7 +717,7 @@ def test_rna_coding(self): hgvs_n = "NR_146230.2:n.10G>A" var_n = self.hp.parse(hgvs_n) - pp = PrettyPrint(self.hdp, show_reverse_strand=True) + pp = PrettyPrint(self.hdp, self.assembly_mapper37, show_reverse_strand=True) result = pp.display(var_n) print(result) expected_str = ( @@ -649,7 +728,7 @@ def test_rna_coding(self): + "seq -> : TGCTGATCTTTGGATGTTCTGGTTAGTCTAAGAAGGAGAGT\n" + "seq <- : ACGACTAGAAACCTACAAGACCAATCAGATTCTTCCTCTCA\n" + "region : A \n" - + "tx seq -> : GATGTTCTGGTTAGTCTAAGAAGGAGAGT\n" + + "tx seq -> : GGATGTTCTGGTTAGTCTAAGAAGGAGAGT\n" + "tx pos : . | . | . |\n" + " : 10 20 30\n" + "aa seq -> : \n" From 57ee0c0694efcdda73e7f858834822f6a9a07e5d Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Tue, 24 Dec 2024 11:03:39 -0800 Subject: [PATCH 31/31] WIP --- misc/docker-compose.yml | 4 +++- src/hgvs/pretty/pretty_print.py | 5 +++++ tests/test_repeats.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/misc/docker-compose.yml b/misc/docker-compose.yml index acea92e3..71a59fe1 100644 --- a/misc/docker-compose.yml +++ b/misc/docker-compose.yml @@ -33,7 +33,9 @@ services: volumes: - uta_vol:/var/lib/postgresql/data network_mode: host - + environment: + - POSTGRES_HOST_AUTH_METHOD=trust + # vr-rest-interface: volumes: diff --git a/src/hgvs/pretty/pretty_print.py b/src/hgvs/pretty/pretty_print.py index 238fa9b0..1dc0e2c3 100644 --- a/src/hgvs/pretty/pretty_print.py +++ b/src/hgvs/pretty/pretty_print.py @@ -92,6 +92,8 @@ def _map_to_chrom(self, sv: SequenceVariant) -> SequenceVariant: return am.n_to_g(sv) elif sv.type == "t": return am.t_to_g(sv) + elif sv.type == 'r': + return am.r def get_hgvs_names( @@ -106,6 +108,9 @@ def get_hgvs_names( elif sv.type == "c": var_g = self._map_to_chrom(sv) var_c_or_n = sv + elif sv.type == 'r': + var_g = self._map_to_chrom(sv) + var_c_or_n = sv elif sv.type == "n": # map back to genome var_g = self._map_to_chrom(sv) diff --git a/tests/test_repeats.py b/tests/test_repeats.py index 8ad48965..63bcaddb 100644 --- a/tests/test_repeats.py +++ b/tests/test_repeats.py @@ -182,7 +182,7 @@ def test_homopolymer(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, ("NC_000019.10:g.45770210_45770212del", True, "CAG", 20, 19, "CAG[20]C[1]A[1]>CAG[19]C[1]A[1]"), # note this one can be shuffled CAG/GCA ("NC_000007.14:g.117548628_117548629insTTTT", True, "T", 7 , 11, "T[7]>T[11]"), ("NC_000009.11:g.35079521_35079523del", True, 'TGG', 2, 1, "TGG[2]>TGG[1]" ), - ("NC_000001.11:g.6490477_6490484del", True, "TCTAAGGC", 2, 1, "TCTAAGGC[2]T[1]C[1]>TCTAAGGC[1]T[1]C[1]") + ("NC_000001.11:g.6490477_6490484del", True, "TCTAAGGC", 2, 1, "TCTAAGGC[2]T[1]C[1]>TCTAAGGC[1]T[1]C[1]") ] ) def test_repeats(self, hgvs_g, is_repeat, repeat_unit, ref_count, alt_count, s):