Skip to content

Commit

Permalink
Making output nicer and improving workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
the-code-magician committed Jun 3, 2015
1 parent d48eabe commit a6e767f
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 30 deletions.
117 changes: 101 additions & 16 deletions cameo/api/designer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@
# limitations under the License.

from __future__ import absolute_import, print_function
from IPython.core.display import display
from IPython.core.display import HTML
from pandas import DataFrame

import six
import numpy as np

from functools import partial
from cameo import Metabolite, Model
from cameo import config
from cameo import Metabolite, Model, phenotypic_phase_plane
from cameo import config, util
from cameo.api.output import notice
from cameo.core.result import Result
from cameo.api.hosts import hosts, Host
from cameo.api.products import products
Expand All @@ -27,6 +32,8 @@
from cameo.data import universal_models

import logging
from cameo.visualization import visualization

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

Expand All @@ -52,7 +59,7 @@ def __init__(self):
""""""
pass

def __call__(self, product='L-glutamate', hosts=hosts):
def __call__(self, product='L-glutamate', hosts=hosts, database=None):
"""The works.
The following workflow will be followed to determine suitable
Expand All @@ -76,11 +83,15 @@ def __call__(self, product='L-glutamate', hosts=hosts):
-------
Designs
"""
product = self.__translate_product_to_universal_reactions_model_metabolite(product)
pathways = self.predict_pathways(product, hosts=hosts)
if database is None:
database = universal_models.metanetx_universal_model_bigg_rhea_kegg_brenda

notice("Starting searching for compound %s" % product)
product = self.__translate_product_to_universal_reactions_model_metabolite(product, database)
pathways = self.predict_pathways(product, hosts=hosts, database=database)
return pathways

def predict_pathways(self, product, hosts=hosts): #TODO: make this work with a single host or model
def predict_pathways(self, product, hosts=None, database=None): #TODO: make this work with a single host or model
"""Predict production routes for a desired product and host spectrum.
Parameters
----------
Expand All @@ -95,40 +106,114 @@ def predict_pathways(self, product, hosts=hosts): #TODO: make this work with a
...
"""
pathways = dict()
product = self.__translate_product_to_universal_reactions_model_metabolite(product)
product = self.__translate_product_to_universal_reactions_model_metabolite(product, database)
for host in hosts:
if isinstance(host, Model):
host = Host(name='UNKNOWN_HOST', models=[host])
for model in list(host.models):
print('Predicting pathways for product {} and host {} using model {}.'.format(product.name, host, model.id))
notice('Predicting pathways for product %s and host %s using model %s.'
% (product.name, host, model.id))
try:
logger.debug('Trying to set solver to cplex for pathway predictions.')
model.solver = 'cplex' # CPLEX is better predicting pathways
except ValueError:
logger.debug('Could not set solver to cplex for pathway predictions.')
pass
pathway_predictor = PathwayPredictor(model, universal_model=universal_models.metanetx_universal_model_bigg_rhea_kegg_brenda)
pathway_predictor = PathwayPredictor(model, universal_model=database)
predicted_pathways = pathway_predictor.run(product, max_predictions=5, timeout=3*60) # TODO adjust these numbers to something reasonable
pathways[(host, model)] = predicted_pathways
self.__display_pathways_information(predicted_pathways, host, model, product)
return pathways

def calculate_maximum_yields(self, pathways):
""""""
"""
"""
for (host, model), pathway in six.iteritems(pathways):
tm = TimeMachine()
tm(do=partial(model.add_reactions, pathway), undo=partial(model.remove_reactions, pathway))
maximum_theoretical_yield()


def __translate_product_to_universal_reactions_model_metabolite(self, product):
def __translate_product_to_universal_reactions_model_metabolite(self, product, database):
if isinstance(product, Metabolite):
return product
elif isinstance(product, str):
search_result = products.search(product)
print("Found %d compounds that match query '%s'" % (len(search_result), product))
print(repr(search_result))
print("Choosing best match (%s) ... please interrupt if this is not the desired compound." % search_result.name[0])
return universal_models.metanetx_universal_model_bigg_rhea_kegg_brenda.metabolites.get_by_id(search_result.index[0])
notice("Found %d compounds that match query '%s'" % (len(search_result), product))
self.__display_product_search_result(search_result)
notice("Choosing best match (%s) ... please interrupt if this is not the desired compound."
% search_result.name[0])
return database.metabolites.get_by_id(search_result.index[0])

@staticmethod
def __display_product_search_result(search_result):
if util.in_ipnb():
Designer.__display_product_search_results_html(search_result)
else:
Designer.__display_product_search_results_text(search_result)

@staticmethod
def __display_product_search_results_html(search_result):
rows = []
for index, row in search_result.iterrows():
name = row["name"]
formula = row["formula"]
inchi = Designer.__generate_svg(row["InChI"])
rows.append("<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>" % (index, name, formula, inchi))

display(HTML(
"""
<table>
<thead>
<th>Id</th>
<th>Name</th>
<th>Formula</th>
<th></th>
</thead>
<tbody>
%s
</tbody>
</table>
""" % "\n".join(rows)
))

@staticmethod
def __display_product_search_results_text(search_result):
rows = np.ndarray((len(search_result), 4), dtype=object)
for i, index in enumerate(search_result.index):
row = search_result.loc[index]
name = row["name"]
formula = row["formula"]
inchi = Designer.__generate_ascii(row["InChI"])
rows[i, ] = [index, name, formula, inchi]
i += 1

display(DataFrame(rows, columns=["Id", "Name", "Formula", "Structure"]))



@staticmethod
def __generate_svg(inchi):
if isinstance(inchi, float) or inchi is None:
return ""
else:
return visualization.inchi_to_svg(inchi, three_d=True)

@staticmethod
def __generate_ascii(inchi):
if isinstance(inchi, float) or inchi is None:
return ""
else:
return visualization.inchi_to_ascii(inchi)

@staticmethod
def __display_pathways_information(predicted_pathways, host, model, product):
for i, pathway in enumerate(predicted_pathways):
notice("Pathway %i for %s using model %s" % ((i+1), host.name, model.id))
with TimeMachine() as tm:
tm(do=partial(model.add_reactions, pathway), undo=partial(model.remove_reactions, pathway))
production_enveope = phenotypic_phase_plane(model, variables=[product])
production_enveope.plot


design = Designer()

Expand Down
25 changes: 25 additions & 0 deletions cameo/api/output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2015 Novo Nordisk Foundation Center for Biosustainability, DTU.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import, print_function
from IPython.core.display import HTML
from IPython.core.display import display

from cameo import util


def notice(message):
if util.in_ipnb():
display(HTML("<span> %s </span>" % message))
else:
print(message)
1 change: 0 additions & 1 deletion cameo/api/products.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ def _search_by_inchi_fuzzy(self, inchi):
selection = self.data_frame[self.data_frame.InChI.isin(matches)]
selection['search_rank'] = selection.name.map(ranks)
return selection.sort('search_rank')
return self.data_frame[self.data_frame.InChI == inchi]


products = Products()
4 changes: 3 additions & 1 deletion cameo/strain_design/pathway_prediction/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@

class PathwayPredictions(Result):

def data_frame(self):
raise NotImplementedError

def __init__(self, pathways, *args, **kwargs):
super(PathwayPredictions, self).__init__(*args, **kwargs)
# TODO: sort the pathways to make them easier to read
self.pathways = pathways
print(self.pathways)

def _repr_html_(self):
raise NotImplementedError
Expand Down
77 changes: 65 additions & 12 deletions cameo/visualization/visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def draw_knockout_result(model, map_name, simulation_method, knockouts, *args, *
tm.reset()
raise e

def inchi_to_svg(inchi, file=None):
def inchi_to_svg(inchi, file=None, debug=False, three_d=False):
"""Generate an SVG drawing from an InChI string.
Parameters
Expand All @@ -169,18 +169,71 @@ def inchi_to_svg(inchi, file=None):
>>> inchi_to_svg('InChI=1S/H2O/h1H2')
'<?xml version="1.0"?>\n<svg version="1.1" id="topsvg"\nxmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"\nxmlns:cml="http://www.xml-cml.org/schema" x="0" y="0" width="200px" height="200px" viewBox="0 0 100 100">\n<title>OBDepict</title>\n<rect x="0" y="0" width="100" height="100" fill="white"/>\n<text text-anchor="middle" font-size="6" fill ="black" font-family="sans-serif"\nx="50" y="98" ></text>\n<g transform="translate(0,0)">\n<svg width="100" height="100" x="0" y="0" viewBox="0 0 80 80"\nfont-family="sans-serif" stroke="rgb(0,0,0)" stroke-width="2" stroke-linecap="round">\n<text x="36" y="48" fill="rgb(255,12,12)" stroke="rgb(255,12,12)" stroke-width="1" font-size="16" >OH</text>\n<text x="60" y="51.68" fill="rgb(255,12,12)" stroke="rgb(255,12,12)" stroke-width="1" font-size="13" >2</text>\n</svg>\n</g>\n</svg>\n\n'
"""

in_file = tempfile.NamedTemporaryFile()
in_file.write(inchi)
in_file.flush()

out_file = None
gen = "--gen3d" if three_d else "--gen2d"
error_level = 5 if debug else 1
try:
if file is not None:
os.system("obabel -iinchi %s -osvg -O %s %s -xh 50 ---errorlevel %d" %
(in_file.name, file.name, gen, error_level))
return file.name
else:
out_file = tempfile.NamedTemporaryFile()
os.system("obabel -iinchi %s -osvg -O %s %s -xh 50 ---errorlevel %d"
% (in_file.name, out_file.name, gen, error_level))
return out_file.read()
finally:
in_file.close()
if out_file is not None:
out_file.close()


def inchi_to_ascii(inchi, file=None, debug=False):
"""Generate an ASCII drawing from an InChI string.
Parameters
----------
inchi : str
An InChI string.
Returns
-------
str
A vector graphics of the compound represented as SVG.
Examples
--------
Draw water
>>> inchi_to_ascii('InChI=1S/H2O/h1H2')
"""

in_file = tempfile.NamedTemporaryFile()
in_file.write(inchi)
in_file.flush()

out_file = None

error_level = 5 if debug else 1
try:
import openbabel
except ImportError as e:
print(e)
raise ImportError("OpenBabel seems to be not installed.")
convert = openbabel.OBConversion()
convert.SetInFormat("inchi")
convert.SetOutFormat("svg")
mol = openbabel.OBMol()
if not convert.ReadString(mol, inchi):
raise Exception("%s could not be parsed as an inchi string.")
return convert.WriteString(mol)
if file is not None:
os.system("obabel -iinchi %s -oascii -O %s --gen3d -xh 50 ---errorlevel %d"
% (in_file.name, file.name, error_level))
return file.name
else:
out_file = tempfile.NamedTemporaryFile()
os.system("obabel -iinchi %s -oascii -O %s --gen3d -xh 50 ---errorlevel %d"
% (in_file.name, out_file.name, error_level))
return out_file.read()
finally:
in_file.close()
if out_file is not None:
out_file.close()


def graph_to_svg(g, layout=nx.spring_layout):
"""return the SVG of a matplotlib figure generated from a graph"""
Expand Down

0 comments on commit a6e767f

Please sign in to comment.