Source code for caravagene.caravagene

import os
import re
import subprocess as sp

from jinja2 import Template

try:
    import pandas

    PANDAS_INSTALLED = True
except:
    PANDAS_INSTALLED = False

THIS_DIR = os.path.dirname(os.path.realpath(__file__))
DEFAULT_SYMBOLS_DIR = os.path.join(THIS_DIR, "symbols")
with open(os.path.join(THIS_DIR, "template.html"), "r") as f:
    TEMPLATE = template = Template(f.read())
SYMBOL_FILES = {
    s.lower().split(".")[0]: os.path.join(DEFAULT_SYMBOLS_DIR, s)
    for s in os.listdir(DEFAULT_SYMBOLS_DIR)
}


[docs]class Part: """Represent a genetic part. Parameters ---------- category Either 'promoter', 'CDS'... Defines the symbol displayed for this part. label String that will be displayed on top of the part. sublabel Note that will be written in grey below the label. subscript Note that will be written below the part. reversed True/False. Whether the part is in direct or indirect sense. bg_color String representing any html color, which will be used as background to highlight this part. """ def __init__( self, category, label="", sublabel="", subscript="", reversed=False, bg_color="none", ): """Initialize.""" self.label = label self.category = category.lower() if self.category not in SYMBOL_FILES: raise ValueError("Unkown category " + category) self.url = SYMBOL_FILES[self.category] self.bg_color = bg_color self.subscript = subscript self.reversed = reversed self.sublabel = sublabel @property def style(self): """Define the CSS style from the part's parameters.""" return "; ".join( [ "background-color: %s" % self.bg_color, "background-image: url(%s)" % self.url, ] )
[docs] @staticmethod def from_dict(part_dict): """Create a part from a dictionary. This is intended for Schema imports from JSON in web applications. This dictionary should have the same parameters as the __init__ function (other parameters will be ignored). """ return Part( **dict( (d, part_dict[d]) for d in [ "category", "label", "subscript", "reversed", "sublabel", "bg_color", ] if d in part_dict ) )
[docs]class Construct: """Represent a genetic construct with several parts. Parameters ---------- parts A list of Parts, in the order in which they appear in the construct. Alternatively, a pandas dataframe can be provided, with columns 'label', 'category', 'sublabel', 'subscript', 'style'. The last column, 'style' can be for instance "bg:green bold". name Name of the construct. Will be displayed on top of the construct's plot. note Some text that will be displayed between the construct's name and plot. """ def __init__(self, parts, name="", note=""): """Initialize.""" if isinstance(parts, pandas.DataFrame): def get_attr(row, attr, default=""): if not hasattr(row, attr): return default value = getattr(row, attr, "") return "" if (str(value) == "nan") else value def row_to_part(row): bg_color = get_attr(row, "bg_color", "none") bg_color = { "blue": "#ECF3FF", "green": "#DFFFE3", "red": "#FFEBE9", }.get(bg_color, bg_color) part = Part( label=get_attr(row, "label"), category=row.category, reversed=get_attr(row, "reversed", False), bg_color=bg_color, subscript=get_attr(row, "subscript"), sublabel=get_attr(row, "sublabel"), ) style = get_attr(row, "style") if style != "": style = str(row.style) if "bold" in style: part.label = "<b>%s</b>" % part.label return part parts = [row_to_part(row) for i, row in parts.iterrows()] self.parts = parts self.name = name self.note = note
[docs] @staticmethod def from_dict(cst_dict): """Create a construct from a dictionary. This is intended for Schema imports from JSON in web applications. This dictionary should have the same parameters as the __init__ function (other parameters will be ignored). """ return Construct( parts=[Part.from_dict(part) for part in cst_dict["parts"]], name=cst_dict["name"], note=cst_dict["note"], )
[docs]class ConstructList: """Represent a genetic construct will several parts. Parameters ---------- constructs A list of Constructss, in the order in which they appear in the plot. Alternatively, a path to an excel spreadsheet can be provided (see docs for explanations on the spreadsheet format). title Title for this list of constructs. Will be displayed on top of the plots. note Some text that will be displayed between the title and the plots. size Size of the font for labels, which also scales the size of sublabel, subscript, and the symbol itself. orientation 'portrait' or 'landscape'. Orientation of the page when exporting to PDF. page_size Page format when exporting to PDF. width Page width when exporting to an image. font Name of the font to use for all texts. use_google_fonts Whether the font should be obtained from Google Fonts (will only work with an Internet access). """ def __init__( self, constructs, title="auto", note="", size=13, font="Helvetica", orientation="portrait", width=600, page_size="A4", use_google_fonts=False, ): """Initialize.""" self.note = note self.size = size self.font = font self.orientation = orientation self.width = width self.page_size = page_size self.title = title self.use_google_fonts = use_google_fonts if isinstance(constructs, str): if not PANDAS_INSTALLED: raise ImportError("Install Pandas to read from spreadsheets.") if title == "auto": self.title = os.path.splitext(os.path.basename(constructs))[0] self.title = self.title.replace("_", " ") sheet_names = pandas.ExcelFile(constructs).sheet_names if "options" in sheet_names: df = pandas.read_excel(constructs, sheet_name="options") self.__dict__.update( { row.field: row.value for i, row in df.iterrows() if row.field in [ "title", "note", "size", "font", "width", "orientation", "page_size", ] } ) constructs = [ Construct(pandas.read_excel(constructs, sheet_name=sheet), name=sheet) for sheet in sheet_names if sheet != "options" ] if self.title == "auto": self.title = "" self.constructs = constructs
[docs] @staticmethod def from_dict(csts_dict): """Create a constructs list from a dictionary. This is intended for Schema imports from JSON in web applications. This dictionary should have the same parameters as the __init__ function (other parameters will be ignored). """ return ConstructList( constructs=[Construct.from_dict(cst) for cst in csts_dict["constructs"]], **dict( (d, csts_dict[d]) for d in [ "title", "note", "size", "font", "orientation", "width", "page_size", ] if d in csts_dict ) )
[docs] def to_html(self, outfile=None): """Return a HTML page ready for browser rendering of the plots.""" result = template.render( constructs=self.constructs, title=self.title, note=self.note, size=self.size, font=self.font, google_font=self.use_google_fonts, ) if outfile is not None: with open(outfile, "w+") as f: f.write(result) else: return result
[docs] def to_pdf(self, outfile=None): """Return a PDF with all the construct plots. If ``outfile`` is not provided, the function returns raw binary PDF data as a string. """ if outfile is None: outfile = "-" process = sp.Popen( [ "wkhtmltopdf", "--quiet", "--page-size", self.page_size, "--orientation", self.orientation, "-", outfile, ], stdin=sp.PIPE, stderr=sp.PIPE, stdout=sp.PIPE, ) out, err = process.communicate(self.to_html().encode("utf-8")) err = err.decode() if (len(err) > 0) and not ("libpng warning" in err): raise IOError("Something went wrong while generating the PDF: %s" % err) if outfile == "-": return out
[docs] def to_image(self, outfile=None, extension=None): """Return an image with all the construct plots. If ``outfile`` is not provided, the function returns raw binary PDF data as a string. In that case the extension ('png', 'jpeg') must be provided. """ if outfile is None: outfile = "-" else: extension = os.path.splitext(outfile)[1][1:] process = sp.Popen( [ "wkhtmltoimage", "--format", extension, "--width", "%d" % self.width, "--disable-smart-width", "-", outfile, ], stdin=sp.PIPE, stderr=sp.PIPE, stdout=sp.PIPE, ) out, err = process.communicate(self.to_html().encode("utf-8")) if outfile == "-": return out