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)