Skip to content

Commit

Permalink
Add an Arrow element to genome coverage diagrams, as part of #442.
Browse files Browse the repository at this point in the history
Add an image comparison to the unit tests.
  • Loading branch information
donkirkby committed Oct 25, 2019
1 parent 6f6ebbe commit 77754f3
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 3 deletions.
35 changes: 35 additions & 0 deletions micall/core/plot_contigs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from genetracks import Figure, Track, Multitrack, Coverage
# noinspection PyPep8Naming
import drawSvg as draw
from genetracks.elements import Element
from matplotlib import cm, colors
from matplotlib.colors import Normalize

Expand Down Expand Up @@ -67,6 +68,40 @@ def get_color(self, coverage):
return colors.to_hex(rgba)


class Arrow(Element):
def __init__(self, start, end, h=30, label=None):
super().__init__(x=start, y=0, w=end-start, h=h)
self.label = label

def draw(self, x=0, y=0, xscale=1.0):
h = self.h
a = self.x * xscale
b = (self.x + self.w) * xscale
x = x * xscale
direction = 1
r = 10 * xscale
arrow_size = 7 * xscale
arrow_end = b
arrow_start = arrow_end - arrow_size*direction
centre = (a + b - direction*arrow_size)/2
centre_start = centre - direction*r
centre_end = centre + direction*r
group = draw.Group(transform="translate({} {})".format(x, y))
group.append(draw.Circle(centre, h/2, r, fill='ivory', stroke='black'))
group.append(draw.Line(a, h/2, centre_start, h/2, stroke='black'))
group.append(draw.Line(centre_end, h/2, arrow_start, h/2, stroke='black'))
group.append(draw.Lines(arrow_end, h/2,
arrow_start, (h + arrow_size)/2,
arrow_start, (h - arrow_size)/2,
fill='black'))
group.append(draw.Text(self.label,
15,
centre, h/2,
text_anchor='middle',
dy="0.3em"))
return group


def plot_genome_coverage(genome_coverage_csv, genome_coverage_svg_path):
f = build_coverage_figure(genome_coverage_csv)
f.show(w=970).saveSvg(genome_coverage_svg_path)
Expand Down
2 changes: 2 additions & 0 deletions micall/tests/svg_diffs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.svg
*.png
79 changes: 77 additions & 2 deletions micall/tests/test_plot_contigs.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from io import StringIO
from base64 import standard_b64encode
from io import StringIO, BytesIO
from pathlib import Path
from turtle import Turtle

import pytest
from PIL import Image, ImageChops
from drawSvg import Drawing, Line, Lines, Circle, Text
from genetracks import Figure, Track, Multitrack, Label, Coverage

from micall.core.plot_contigs import summarize_figure, \
build_coverage_figure, SmoothCoverage, add_partial_banner
build_coverage_figure, SmoothCoverage, add_partial_banner, Arrow

HCV_HEADER = ('C[342-915], E1[915-1491], E2[1491-2580], P7[2580-2769], '
'NS2[2769-3420], NS3[3420-5313], NS4A[5313-5475], '
Expand All @@ -15,6 +20,54 @@
pol[2085-5096], vpr[5559-5850], rev[5970-6045], env[6225-8795]'''


class SvgDiffer:
def __init__(self):
self.work_dir: Path = Path(__file__).parent / 'svg_diffs'
self.work_dir.mkdir(exist_ok=True)
for work_file in self.work_dir.iterdir():
if work_file.name == '.gitignore':
continue
assert work_file.suffix in ('.svg', '.png')
work_file.unlink()

def assert_equal(self,
svg_actual: Drawing,
svg_expected: Drawing,
name: str):
# Display image when in live turtle mode.
display_image = getattr(Turtle, 'display_image', None)
if display_image is not None:
encoded = standard_b64encode(svg_actual.rasterize().pngData)
display_image(0, 0, image=encoded.decode('UTF-8'))

png_actual = drawing_to_image(svg_actual)
png_expected = drawing_to_image(svg_expected)
png_diff = ImageChops.difference(png_actual, png_expected)

extrema = png_diff.getextrema()
if extrema == ((0, 0), (0, 0), (0, 0), (0, 0)):
return
text_actual = svg_actual.asSvg()
(self.work_dir / (name+'_actual.svg')).write_text(text_actual)
text_expected = svg_expected.asSvg()
(self.work_dir / (name+'_expected.svg')).write_text(text_expected)
with (self.work_dir / (name+'_diff.png')) as f:
png_diff.save(f)
assert text_actual == text_expected


def drawing_to_image(drawing: Drawing) -> Image:
png = drawing.rasterize()
png_bytes = BytesIO(png.pngData)
image = Image.open(png_bytes)
return image


@pytest.fixture(scope='session')
def svg_differ():
return SvgDiffer()


def test_summarize_labels():
figure = Figure()
figure.add(Track(1, 200, label="Foo"))
Expand Down Expand Up @@ -398,3 +451,25 @@ def test_plot_genome_coverage_insertion():
figure = build_coverage_figure(genome_coverage_csv)

assert expected_figure == summarize_figure(figure)


def test_arrow(svg_differ):
expected_svg = Drawing(200.0, 60.0, origin=(0, 0))
expected_svg.append(Circle(168/2, 40, 10, stroke='black', fill='ivory'))
expected_svg.append(Text('X',
15,
168/2, 40,
text_anchor='middle',
dy="0.3em"))
expected_svg.append(Line(0, 40, 74, 40, stroke='black'))
expected_svg.append(Line(94, 40, 168, 40, stroke='black'))
expected_svg.append(Lines(175, 40,
168, 43.5,
168, 36.5,
175, 40,
fill='black'))
f = Figure()
f.add(Arrow(0, 175, label='X'))
svg = f.show()

svg_differ.assert_equal(svg, expected_svg, 'test_arrow')
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ python-Levenshtein==0.12.0
PyYAML==5.1
reportlab==3.4.0
pysam==0.15.2
git+https://github.com/jeff-k/genetracks.git
git+https://github.com/cfe-lab/genetracks.git@v0.2.dev0

0 comments on commit 77754f3

Please sign in to comment.