tlogo 0.1.2.dev0__tar.gz → 0.1.3.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/PKG-INFO +2 -1
  2. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/pyproject.toml +1 -0
  3. tlogo-0.1.3.dev0/src/tlogo/__init__.py +23 -0
  4. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/_version.py +3 -3
  5. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/logo.py +65 -43
  6. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/window.py +9 -21
  7. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo.egg-info/PKG-INFO +2 -1
  8. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo.egg-info/SOURCES.txt +1 -0
  9. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo.egg-info/requires.txt +1 -0
  10. tlogo-0.1.3.dev0/tests/test_draw_logo.py +143 -0
  11. tlogo-0.1.2.dev0/src/tlogo/__init__.py +0 -8
  12. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/.github/workflows/ci.yml +0 -0
  13. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/.github/workflows/publish.yml +0 -0
  14. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/.gitignore +0 -0
  15. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/LICENSE +0 -0
  16. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/README.md +0 -0
  17. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/examples/example.fasta +0 -0
  18. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/examples/window_logo.png +0 -0
  19. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/setup.cfg +0 -0
  20. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/cli.py +0 -0
  21. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/constants.py +0 -0
  22. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/hxb2.py +0 -0
  23. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/io.py +0 -0
  24. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/matrix.py +0 -0
  25. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo.egg-info/dependency_links.txt +0 -0
  26. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo.egg-info/entry_points.txt +0 -0
  27. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo.egg-info/top_level.txt +0 -0
  28. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/tests/conftest.py +0 -0
  29. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/tests/test_cli.py +0 -0
  30. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/tests/test_hxb2.py +0 -0
  31. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/tests/test_io.py +0 -0
  32. {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/tests/test_matrix.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tlogo
3
- Version: 0.1.2.dev0
3
+ Version: 0.1.3.dev0
4
4
  Summary: Sequence logo generator for aligned FASTA files
5
5
  Author-email: Troy Sincomb <troysincomb@gmail.com>
6
6
  License: MIT
@@ -24,6 +24,7 @@ Requires-Dist: biopython>=1.80
24
24
  Requires-Dist: matplotlib>=3.5
25
25
  Requires-Dist: logomaker>=0.8
26
26
  Requires-Dist: pandas>=1.3
27
+ Requires-Dist: patchworklib>=0.6
27
28
  Provides-Extra: dev
28
29
  Requires-Dist: pytest>=7.0; extra == "dev"
29
30
  Requires-Dist: hypothesis>=6.0; extra == "dev"
@@ -32,6 +32,7 @@ dependencies = [
32
32
  "matplotlib>=3.5",
33
33
  "logomaker>=0.8",
34
34
  "pandas>=1.3",
35
+ "patchworklib>=0.6",
35
36
  ]
36
37
 
37
38
  [project.optional-dependencies]
@@ -0,0 +1,23 @@
1
+ """tlogo — Sequence logo generator for aligned FASTA files."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ from tlogo.hxb2 import build_hxb2_map, build_reverse_hxb2_map
6
+ from tlogo.io import parse_alignment
7
+ from tlogo.logo import apply_nature_style, draw_logo
8
+ from tlogo.matrix import build_logo_matrix
9
+
10
+ try:
11
+ __version__ = version("tlogo")
12
+ except PackageNotFoundError:
13
+ __version__ = "0.0.0"
14
+
15
+ __all__ = [
16
+ "__version__",
17
+ "apply_nature_style",
18
+ "build_hxb2_map",
19
+ "build_logo_matrix",
20
+ "build_reverse_hxb2_map",
21
+ "draw_logo",
22
+ "parse_alignment",
23
+ ]
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.1.2.dev0'
32
- __version_tuple__ = version_tuple = (0, 1, 2, 'dev0')
31
+ __version__ = version = '0.1.3.dev0'
32
+ __version_tuple__ = version_tuple = (0, 1, 3, 'dev0')
33
33
 
34
- __commit_id__ = commit_id = 'g8a81ed2c2'
34
+ __commit_id__ = commit_id = 'g302e8747d'
@@ -60,6 +60,63 @@ def apply_nature_style() -> None:
60
60
  })
61
61
 
62
62
 
63
+ def draw_logo(
64
+ ax: plt.Axes,
65
+ matrix: pd.DataFrame,
66
+ labels: list[str],
67
+ title: str = "",
68
+ color_scheme: str = "chemistry",
69
+ matrix_type: str = "information",
70
+ ) -> logomaker.Logo:
71
+ """Draw a sequence logo onto any matplotlib-compatible Axes.
72
+
73
+ This is the primary composable API for tlogo. It draws onto whatever
74
+ Axes it receives — including a ``patchworklib.Brick`` — without creating
75
+ a figure, calling ``tight_layout()``, saving, or closing. Those are the
76
+ caller's responsibility.
77
+
78
+ Args:
79
+ ax: Target matplotlib Axes (or patchworklib Brick).
80
+ matrix: Logomaker-compatible DataFrame (information, probability,
81
+ or counts).
82
+ labels: X-axis tick labels (e.g. HxB2 positions).
83
+ title: Panel title string. Pass ``""`` for no title.
84
+ color_scheme: Amino acid color scheme name for logomaker.
85
+ matrix_type: One of ``"information"``, ``"probability"``, or
86
+ ``"counts"``. Controls the y-axis label.
87
+
88
+ Returns:
89
+ The ``logomaker.Logo`` instance for further customization.
90
+ """
91
+ logo = logomaker.Logo(
92
+ matrix,
93
+ ax=ax,
94
+ color_scheme=color_scheme,
95
+ font_name="Arial",
96
+ stack_order="big_on_top",
97
+ vpad=0.04,
98
+ baseline_width=0.4,
99
+ )
100
+ logo.style_spines(spines=["top", "right"], visible=False)
101
+ logo.style_spines(spines=["left", "bottom"], visible=True)
102
+
103
+ n_positions = len(labels)
104
+ ax.set_xticks(range(n_positions))
105
+ ax.set_xticklabels(labels, rotation=90, fontsize=FONT_TICK_LABEL)
106
+ ax.set_ylabel(
107
+ _y_axis_label(matrix_type), fontsize=FONT_AXIS_LABEL, labelpad=4
108
+ )
109
+ if title:
110
+ ax.set_title(
111
+ title, fontsize=FONT_TITLE, fontweight="bold", pad=4, loc="left"
112
+ )
113
+ ax.yaxis.set_major_locator(
114
+ ticker.MaxNLocator(nbins=3, prune="both")
115
+ )
116
+
117
+ return logo
118
+
119
+
63
120
  def _y_axis_label(matrix_type: str) -> str:
64
121
  """Return y-axis label for the given matrix type.
65
122
 
@@ -120,7 +177,6 @@ def render_logo_plot(
120
177
  """
121
178
  apply_nature_style()
122
179
  output_path.parent.mkdir(parents=True, exist_ok=True)
123
- y_label = _y_axis_label(matrix_type)
124
180
  n_positions = len(labels)
125
181
  fmt = output_path.suffix.lstrip(".") or "pdf"
126
182
 
@@ -136,27 +192,11 @@ def render_logo_plot(
136
192
  )
137
193
  fig, ax = plt.subplots(1, 1, figsize=(fig_width, PANEL_HEIGHT))
138
194
 
139
- logo = logomaker.Logo(
140
- matrix,
141
- ax=ax,
195
+ draw_logo(
196
+ ax, matrix, labels,
197
+ title=f"{title} \u2014 {n_positions} selected positions",
142
198
  color_scheme=color_scheme,
143
- font_name="Arial",
144
- stack_order="big_on_top",
145
- vpad=0.04,
146
- baseline_width=0.4,
147
- )
148
- logo.style_spines(spines=["top", "right"], visible=False)
149
- logo.style_spines(spines=["left", "bottom"], visible=True)
150
-
151
- ax.set_xticks(range(n_positions))
152
- ax.set_xticklabels(labels, rotation=90, fontsize=FONT_TICK_LABEL)
153
- ax.set_ylabel(y_label, fontsize=FONT_AXIS_LABEL, labelpad=4)
154
- ax.set_title(
155
- f"{title} \u2014 {n_positions} selected positions",
156
- fontsize=FONT_TITLE, fontweight="bold", pad=4, loc="left",
157
- )
158
- ax.yaxis.set_major_locator(
159
- ticker.MaxNLocator(nbins=3, prune="both")
199
+ matrix_type=matrix_type,
160
200
  )
161
201
 
162
202
  plt.tight_layout(pad=0.4)
@@ -184,29 +224,11 @@ def render_logo_plot(
184
224
  sub_matrix = matrix.iloc[row_indices, :].copy()
185
225
  sub_matrix.index = range(len(row_indices))
186
226
 
187
- logo = logomaker.Logo(
188
- sub_matrix,
189
- ax=ax,
227
+ draw_logo(
228
+ ax, sub_matrix, sub_labels,
229
+ title=f"{title} \u2014 {region_name}",
190
230
  color_scheme=color_scheme,
191
- font_name="Arial",
192
- stack_order="big_on_top",
193
- vpad=0.04,
194
- baseline_width=0.4,
195
- )
196
- logo.style_spines(spines=["top", "right"], visible=False)
197
- logo.style_spines(spines=["left", "bottom"], visible=True)
198
-
199
- ax.set_xticks(range(len(row_indices)))
200
- ax.set_xticklabels(
201
- sub_labels, rotation=90, fontsize=FONT_TICK_LABEL
202
- )
203
- ax.set_ylabel(y_label, fontsize=FONT_AXIS_LABEL, labelpad=4)
204
- ax.set_title(
205
- f"{title} \u2014 {region_name}",
206
- fontsize=FONT_TITLE, fontweight="bold", pad=4, loc="left",
207
- )
208
- ax.yaxis.set_major_locator(
209
- ticker.MaxNLocator(nbins=3, prune="both")
231
+ matrix_type=matrix_type,
210
232
  )
211
233
 
212
234
  plt.tight_layout(pad=0.4, h_pad=0.6)
@@ -5,21 +5,18 @@ from __future__ import annotations
5
5
  from pathlib import Path
6
6
 
7
7
  import matplotlib.pyplot as plt
8
- import matplotlib.ticker as ticker
9
8
  import logomaker
10
9
 
11
10
  from tlogo.constants import (
12
11
  FONT_ANIMAL_LABEL,
13
- FONT_AXIS_LABEL,
14
12
  FONT_SUPTITLE,
15
- FONT_TICK_LABEL,
16
13
  NATURE_DOUBLE_COL,
17
14
  NATURE_SINGLE_COL,
18
15
  PANEL_HEIGHT,
19
16
  )
20
17
  from tlogo.hxb2 import HxB2Position, build_reverse_hxb2_map, get_env_region
21
18
  from tlogo.io import sort_animal_groups
22
- from tlogo.logo import _save_figure, _y_axis_label, apply_nature_style
19
+ from tlogo.logo import _save_figure, _y_axis_label, apply_nature_style, draw_logo
23
20
  from tlogo.matrix import find_variant_positions, resolve_window_cols
24
21
 
25
22
 
@@ -95,24 +92,15 @@ def render_window_logo(
95
92
  )
96
93
  matrix.index = range(len(matrix))
97
94
 
98
- logo = logomaker.Logo(
99
- matrix, ax=ax, color_scheme=color_scheme,
100
- font_name="Arial", stack_order="big_on_top",
101
- vpad=0.04, baseline_width=0.4,
102
- )
103
- logo.style_spines(spines=["top", "right"], visible=False)
104
- logo.style_spines(spines=["left", "bottom"], visible=True)
105
-
106
- ax.set_xticks(range(len(selected_cols)))
107
- ax.set_xticklabels(labels, rotation=90, fontsize=FONT_TICK_LABEL)
108
- ax.set_ylabel(y_label, fontsize=FONT_AXIS_LABEL, labelpad=4)
109
- ax.set_title(
110
- f"{animal_name} (n={n_seqs})",
111
- fontsize=FONT_ANIMAL_LABEL, loc="left",
112
- )
113
- ax.yaxis.set_major_locator(
114
- ticker.MaxNLocator(nbins=3, prune="both")
95
+ draw_logo(
96
+ ax, matrix, labels,
97
+ title=f"{animal_name} (n={n_seqs})",
98
+ color_scheme=color_scheme,
99
+ matrix_type=matrix_type,
115
100
  )
101
+ # Window mode uses a smaller title font than the standard logo
102
+ ax.title.set_fontsize(FONT_ANIMAL_LABEL)
103
+ ax.title.set_fontweight("normal")
116
104
 
117
105
  fig.tight_layout(rect=[0, 0, 1, 0.95])
118
106
  _save_figure(fig, output_path, fmt)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tlogo
3
- Version: 0.1.2.dev0
3
+ Version: 0.1.3.dev0
4
4
  Summary: Sequence logo generator for aligned FASTA files
5
5
  Author-email: Troy Sincomb <troysincomb@gmail.com>
6
6
  License: MIT
@@ -24,6 +24,7 @@ Requires-Dist: biopython>=1.80
24
24
  Requires-Dist: matplotlib>=3.5
25
25
  Requires-Dist: logomaker>=0.8
26
26
  Requires-Dist: pandas>=1.3
27
+ Requires-Dist: patchworklib>=0.6
27
28
  Provides-Extra: dev
28
29
  Requires-Dist: pytest>=7.0; extra == "dev"
29
30
  Requires-Dist: hypothesis>=6.0; extra == "dev"
@@ -23,6 +23,7 @@ src/tlogo.egg-info/requires.txt
23
23
  src/tlogo.egg-info/top_level.txt
24
24
  tests/conftest.py
25
25
  tests/test_cli.py
26
+ tests/test_draw_logo.py
26
27
  tests/test_hxb2.py
27
28
  tests/test_io.py
28
29
  tests/test_matrix.py
@@ -3,6 +3,7 @@ biopython>=1.80
3
3
  matplotlib>=3.5
4
4
  logomaker>=0.8
5
5
  pandas>=1.3
6
+ patchworklib>=0.6
6
7
 
7
8
  [dev]
8
9
  pytest>=7.0
@@ -0,0 +1,143 @@
1
+ """Tests for the draw_logo() composable API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import matplotlib
6
+ import matplotlib.pyplot as plt
7
+ import logomaker
8
+ import pytest
9
+
10
+ from tlogo.logo import apply_nature_style, draw_logo
11
+
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # Helpers
15
+ # ---------------------------------------------------------------------------
16
+
17
+ def _make_matrix() -> tuple:
18
+ """Build a small information matrix for testing."""
19
+ subsequences = ["ACDEF", "ACDEY", "ACDEW", "ACDEK", "ACDEF"]
20
+ matrix = logomaker.alignment_to_matrix(
21
+ subsequences,
22
+ to_type="information",
23
+ characters_to_ignore=".-",
24
+ pseudocount=1.0,
25
+ )
26
+ matrix.index = range(len(matrix))
27
+ labels = [str(i + 1) for i in range(len(matrix))]
28
+ return matrix, labels
29
+
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Tests
33
+ # ---------------------------------------------------------------------------
34
+
35
+
36
+ class TestDrawLogo:
37
+ """Tests for draw_logo on plain matplotlib Axes."""
38
+
39
+ def setup_method(self) -> None:
40
+ matplotlib.use("Agg")
41
+
42
+ def test_returns_logomaker_logo(self) -> None:
43
+ matrix, labels = _make_matrix()
44
+ _fig, ax = plt.subplots()
45
+ result = draw_logo(ax, matrix, labels)
46
+ assert isinstance(result, logomaker.Logo)
47
+ plt.close("all")
48
+
49
+ def test_sets_xtick_labels(self) -> None:
50
+ matrix, labels = _make_matrix()
51
+ _fig, ax = plt.subplots()
52
+ draw_logo(ax, matrix, labels)
53
+ tick_labels = [t.get_text() for t in ax.get_xticklabels()]
54
+ assert tick_labels == labels
55
+ plt.close("all")
56
+
57
+ def test_sets_title(self) -> None:
58
+ matrix, labels = _make_matrix()
59
+ _fig, ax = plt.subplots()
60
+ draw_logo(ax, matrix, labels, title="My Title")
61
+ # draw_logo sets title with loc="left"
62
+ assert ax.get_title(loc="left") == "My Title"
63
+ plt.close("all")
64
+
65
+ def test_empty_title_produces_no_title(self) -> None:
66
+ matrix, labels = _make_matrix()
67
+ _fig, ax = plt.subplots()
68
+ draw_logo(ax, matrix, labels, title="")
69
+ assert ax.get_title(loc="left") == ""
70
+ plt.close("all")
71
+
72
+ @pytest.mark.parametrize(
73
+ "matrix_type, expected_ylabel",
74
+ [
75
+ ("information", "Information (bits)"),
76
+ ("probability", "Frequency"),
77
+ ("counts", "Count"),
78
+ ],
79
+ )
80
+ def test_ylabel_matches_matrix_type(
81
+ self, matrix_type: str, expected_ylabel: str
82
+ ) -> None:
83
+ matrix, labels = _make_matrix()
84
+ _fig, ax = plt.subplots()
85
+ draw_logo(ax, matrix, labels, matrix_type=matrix_type)
86
+ assert ax.get_ylabel() == expected_ylabel
87
+ plt.close("all")
88
+
89
+ def test_does_not_create_or_close_figure(self) -> None:
90
+ """draw_logo must not call plt.close or create its own figure."""
91
+ fig, ax = plt.subplots()
92
+ matrix, labels = _make_matrix()
93
+ draw_logo(ax, matrix, labels)
94
+ # Figure should still be open
95
+ assert fig.number in plt.get_fignums()
96
+ plt.close("all")
97
+
98
+ def test_nature_style_applied(self) -> None:
99
+ apply_nature_style()
100
+ assert matplotlib.rcParams["axes.spines.top"] is False
101
+ assert matplotlib.rcParams["axes.spines.right"] is False
102
+
103
+
104
+ # ---------------------------------------------------------------------------
105
+ # Patchworklib integration (skip if not installed)
106
+ # ---------------------------------------------------------------------------
107
+
108
+ try:
109
+ import patchworklib as pw
110
+ _has_pw = True
111
+ except ImportError:
112
+ _has_pw = False
113
+
114
+
115
+ @pytest.mark.skipif(not _has_pw, reason="patchworklib not installed")
116
+ class TestPatchworkIntegration:
117
+ """Verify draw_logo works with patchworklib Bricks."""
118
+
119
+ def setup_method(self) -> None:
120
+ matplotlib.use("Agg")
121
+
122
+ def test_draw_on_brick(self) -> None:
123
+ matrix, labels = _make_matrix()
124
+ brick = pw.Brick("test_logo", figsize=(4, 2))
125
+ result = draw_logo(brick, matrix, labels, title="Brick Logo")
126
+ assert isinstance(result, logomaker.Logo)
127
+ assert brick.get_title(loc="left") == "Brick Logo"
128
+
129
+ def test_compose_two_bricks(self, output_dir) -> None:
130
+ m1, l1 = _make_matrix()
131
+ m2, l2 = _make_matrix()
132
+
133
+ b1 = pw.Brick("logo_left", figsize=(4, 2))
134
+ b2 = pw.Brick("logo_right", figsize=(4, 2))
135
+
136
+ draw_logo(b1, m1, l1, title="Left")
137
+ draw_logo(b2, m2, l2, title="Right")
138
+
139
+ composed = b1 | b2
140
+ out = output_dir / "patchwork_composed.png"
141
+ composed.savefig(str(out))
142
+ assert out.exists()
143
+ assert out.stat().st_size > 0
@@ -1,8 +0,0 @@
1
- """tlogo — Sequence logo generator for aligned FASTA files."""
2
-
3
- from importlib.metadata import PackageNotFoundError, version
4
-
5
- try:
6
- __version__ = version("tlogo")
7
- except PackageNotFoundError:
8
- __version__ = "0.0.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes