def convert(the_map, model, map_name=None, map_description=None, reaction_id_mapping=None, metabolite_id_mapping=None, gene_id_mapping=None, debug=False): """Convert an Escher map to the latest format using the COBRA model to update content. Returns a new map. Arguments --------- the_map: An Escher map loaded as a Python object (e.g. json.load('my_map.json')). model: A COBRA model. map_name: A name for the map. If a name is already present, this name overrides it. map_description: A description for the map. If a name is already present, this name overrides it. reaction_id_mapping: A dictionary with existing reaction IDs as keys and the new reaction IDs as values. metabolite_id_mapping: A dictionary with existing metabolite IDs as keys and the new metabolite IDs as values. gene_id_mapping: A dictionary with existing gene IDs as keys and the new gene IDs as values. debug: Check the map against the schema at some intermediate steps. """ # make sure everything is up-to-date new_map = old_map_to_new_schema(the_map, map_name=map_name, map_description=map_description) if debug: validate_map(new_map) # apply the ids mappings apply_id_mappings(new_map, reaction_id_mapping, metabolite_id_mapping, gene_id_mapping) if debug: validate_map(new_map) # apply the new model apply_cobra_model_to_map(new_map, model) validate_map(new_map) return new_map
def test_validate_map(): the_map = [{ 'map_name': 'carbohydrate metabolism', 'map_id': 'h_sapiens_carb', 'map_description': 'A map of central carbon→ metabolism', 'homepage': 'https://escher.github.io', 'schema': 'https://escher.github.io/escher/jsonschema/1-0-0#' }, { 'reactions': { '1': {'name': 'glyceraldehyde-3-phosphate dehydrogenase', 'bigg_id': 'GAPD', 'reversibility': True, 'label_x': 0, 'label_y': 0, 'gene_reaction_rule': 'b1779', 'genes': [{ 'bigg_id': 'b1779', 'name': 'gapA' }], 'metabolites': [{ 'bigg_id': 'g3p_c', 'coefficient': -1 }], 'segments': { '2': { 'from_node_id': '0', 'to_node_id': '1', 'b1': None, 'b2': None } } } }, 'nodes': { '0': { 'node_type': 'metabolite', 'x': 1, 'y': 2, 'bigg_id': 'g3p_c', 'name': 'glyceraldehyde-3-phosphate cytosolic', 'label_x': 3, 'label_y': 3, 'node_is_primary': True }, '1': { 'node_type': 'multimarker', 'x': 10, 'y': 12.3 } }, 'text_labels': {}, 'canvas': {'x': 1208.24, 'y':794.55, 'width':10402.35, 'height':13224.91} }] validate_map(the_map) # missing node new_map = deepcopy(the_map) del new_map[1]['nodes']['1'] with raises(Exception) as e: validate_map(new_map) print(e) assert 'No nodes for segments' in str(e.value) # zero coefficient new_map = deepcopy(the_map) new_map[1]['reactions']['1']['metabolites'][0]['coefficient'] = 0 with raises(Exception) as e: validate_map(new_map) print(e) assert 'No non-zero stoichiometry for a connected metabolite node' in str(e.value) # # midmarker connected to metabolite # new_map = deepcopy(the_map) # new_map[1]['nodes']['1']['node_type'] = 'midmarker' # with raises(Exception) as e: # validate_map(new_map) # print(e) # assert 'Segments connect midmarkers to metabolites' in str(e.value) # No gene name for gene in gene_reaction_rule new_map = deepcopy(the_map) del new_map[1]['reactions']['1']['genes'][0]['name'] with raises(Exception) as e: validate_map(new_map) print(e) assert 'No gene name for gene in gene_reaction_rule' in str(e.value)
def convert(map, model): # check for new, 2-level maps has_head = isinstance(map, list) and len(map) == 2 if has_head: map_body = map[1] else: map_body = map # add missing elements for k in ['nodes', 'reactions', 'text_labels']: if k not in map_body or len(map_body[k]) == 0: map_body[k] = {} # default canvas if 'canvas' not in map_body: map_body['canvas'] = { 'x': -1440, 'y': -775, 'width': 4320, 'height': 2325 } # keep track of deleted nodes and reactions nodes_to_delete = set() reactions_to_delete = set() nodes_with_segments = set() # nodes for id, node in map_body['nodes'].items(): # follow rules for type # metabolites if node['node_type'] == 'metabolite': # no bigg_id if not 'bigg_id' in node: print('No bigg_id for node %s. Deleting.' % id) nodes_to_delete.add(id) # unsupported attributes for key in list(node.keys()): if not key in ["node_type", "x", "y", "bigg_id", "name", "label_x", "label_y", "node_is_primary"]: del node[key] # find the metabolite try: cobra_metabolite = model.metabolites.get_by_id(node['bigg_id']) except KeyError: print('Could not find metabolite %s in model. Deleting.' % node['bigg_id']) nodes_to_delete.add(id) continue # apply new display names node['name'] = cobra_metabolite.name # node_is_primary defaults to False if not 'node_is_primary' in node or node['node_is_primary'] not in [True, False]: node['node_is_primary'] = False # markers elif node['node_type'] in ['multimarker', 'midmarker']: # unsupported attributes for key in list(node.keys()): if not key in ["node_type", "x", "y"]: del node[key] # invalid node_type else: nodes_to_delete.add(id) # update reactions for id, reaction in map_body['reactions'].items(): # missing attributes will_delete = False for k in ["bigg_id", "segments", "label_x", "label_y"]: if not k in reaction: print('No %s for reaction %s. Deleting.' % (k, id)) reactions_to_delete.add(id) will_delete = True if will_delete: continue # unsupported attributes for key in list(reaction.keys()): if not key in ["name", "bigg_id","reversibility", "label_x", "label_y", "gene_reaction_rule", "genes", "metabolites", "segments"]: del reaction[key] # cast attributes for k in ['label_x', 'label_y']: reaction[k] = float(reaction[k]) # unsupported attributes in segments for s_id, segment in reaction['segments'].items(): for key in list(segment.keys()): if not key in ["from_node_id", "to_node_id", "b1", "b2"]: del segment[key] # cast attributes for k in ['b1', 'b2']: if segment[k] is not None and (segment[k]['x'] is None or segment[k]['y'] is None): segment[k] = None # keep track of nodes that have appeared here for key in ['from_node_id', 'to_node_id']: try: nodes_with_segments.add(segment[key]) except KeyError: pass # get reaction try: cobra_reaction = model.reactions.get_by_id(reaction['bigg_id']) except KeyError: print('Could not find reaction %s in model. Deleting.' % reaction['bigg_id']) reactions_to_delete.add(id) continue reaction['gene_reaction_rule'] = cobra_reaction.gene_reaction_rule reaction['reversibility'] = (cobra_reaction.lower_bound < 0 and cobra_reaction.upper_bound > 0) # reverse metabolites if reaction runs in reverse rev_mult = (-1 if (cobra_reaction.lower_bound < 0 and cobra_reaction.upper_bound <= 0) else 1) # use metabolites from reaction reaction['metabolites'] = [{'bigg_id': met.id, 'coefficient': coeff * rev_mult} for met, coeff in cobra_reaction.metabolites.items()] reaction['name'] = cobra_reaction.name reaction['genes'] = [] for gene in genes_for_gene_reaction_rule(reaction['gene_reaction_rule']): try: cobra_gene = model.genes.get_by_id(gene) except KeyError: print('Could not find gene %s in model. Setting name to ID.') reaction['genes'].append({'bigg_id': gene, 'name': gene}) continue reaction['genes'].append({'bigg_id': gene, 'name': cobra_gene.name}) # remove any lost segments reaction['segments'] = {id: seg for id, seg in reaction['segments'].items() if ((seg['to_node_id'] in map_body['nodes'] and seg['to_node_id'] not in nodes_to_delete) and (seg['from_node_id'] in map_body['nodes'] and seg['from_node_id'] not in nodes_to_delete))} # delete those reactions for reaction_id in reactions_to_delete: del map_body['reactions'][reaction_id] # delete those nodes for node_id in nodes_to_delete: del map_body['nodes'][node_id] # delete any nodes with no segment for node_id in list(map_body['nodes'].keys()): if node_id not in nodes_with_segments: # may not be there, if previously deleted try: del map_body['nodes'][node_id] print('No segments for node %s. Deleting' % node_id) except KeyError: pass # text labels for id, text_label in map_body['text_labels'].items(): # unsupported attributes for key in list(text_label.keys()): if not key in ["x", "y", "text"]: del text_label[key] # canvas # unsupported attributes for key in list(map_body['canvas'].keys()): if not key in ["x", "y", "width", "height"]: del map_body['canvas'][key] # delete unsupported elements for key in list(map_body.keys()): if not key in ["nodes", "reactions", "text_labels", "canvas"]: del map_body[key] header = { "schema": "https://escher.github.io/escher/jsonschema/1-0-0#", "homepage": "https://escher.github.io", "map_name": "", "map_id": "", "map_description": "" } if has_head: for key, value in map[0].items(): if key in ['schema', 'homepage']: continue header[key] = value the_map = [header, map_body] validate_map(the_map) return the_map
def convert(map, model): # check for new, 2-level maps has_head = isinstance(map, list) and len(map) == 2 if has_head: map_body = map[1] else: map_body = map # add missing elements for k in ['nodes', 'reactions', 'text_labels']: if k not in map_body or len(map_body[k]) == 0: map_body[k] = {} # default canvas if 'canvas' not in map_body: map_body['canvas'] = { 'x': -1440, 'y': -775, 'width': 4320, 'height': 2325 } # keep track of deleted nodes and reactions nodes_to_delete = set() reactions_to_delete = set() nodes_with_segments = set() # nodes for id, node in map_body['nodes'].items(): # follow rules for type # metabolites if node['node_type'] == 'metabolite': # no bigg_id if not 'bigg_id' in node: print('No bigg_id for node %s. Deleting.' % id) nodes_to_delete.add(id) # unsupported attributes for key in list(node.keys()): if not key in [ "node_type", "x", "y", "bigg_id", "name", "label_x", "label_y", "node_is_primary" ]: del node[key] # find the metabolite try: cobra_metabolite = model.metabolites.get_by_id(node['bigg_id']) except KeyError: print('Could not find metabolite %s in model. Deleting.' % node['bigg_id']) nodes_to_delete.add(id) continue # apply new display names node['name'] = cobra_metabolite.name # node_is_primary defaults to False if not 'node_is_primary' in node or node[ 'node_is_primary'] not in [True, False]: node['node_is_primary'] = False # markers elif node['node_type'] in ['multimarker', 'midmarker']: # unsupported attributes for key in list(node.keys()): if not key in ["node_type", "x", "y"]: del node[key] # invalid node_type else: nodes_to_delete.add(id) # update reactions for id, reaction in map_body['reactions'].items(): # missing attributes will_delete = False for k in ["bigg_id", "segments", "label_x", "label_y"]: if not k in reaction: print('No %s for reaction %s. Deleting.' % (k, id)) reactions_to_delete.add(id) will_delete = True if will_delete: continue # unsupported attributes for key in list(reaction.keys()): if not key in [ "name", "bigg_id", "reversibility", "label_x", "label_y", "gene_reaction_rule", "genes", "metabolites", "segments" ]: del reaction[key] # cast attributes for k in ['label_x', 'label_y']: reaction[k] = float(reaction[k]) # unsupported attributes in segments for s_id, segment in reaction['segments'].items(): for key in list(segment.keys()): if not key in ["from_node_id", "to_node_id", "b1", "b2"]: del segment[key] # cast attributes for k in ['b1', 'b2']: if segment[k] is not None and (segment[k]['x'] is None or segment[k]['y'] is None): segment[k] = None # keep track of nodes that have appeared here for key in ['from_node_id', 'to_node_id']: try: nodes_with_segments.add(segment[key]) except KeyError: pass # get reaction try: cobra_reaction = model.reactions.get_by_id(reaction['bigg_id']) except KeyError: print('Could not find reaction %s in model. Deleting.' % reaction['bigg_id']) reactions_to_delete.add(id) continue reaction['gene_reaction_rule'] = cobra_reaction.gene_reaction_rule reaction['reversibility'] = (cobra_reaction.lower_bound < 0 and cobra_reaction.upper_bound > 0) # reverse metabolites if reaction runs in reverse rev_mult = (-1 if (cobra_reaction.lower_bound < 0 and cobra_reaction.upper_bound <= 0) else 1) # use metabolites from reaction reaction['metabolites'] = [{ 'bigg_id': met.id, 'coefficient': coeff * rev_mult } for met, coeff in cobra_reaction.metabolites.items()] reaction['name'] = cobra_reaction.name reaction['genes'] = [] for gene in genes_for_gene_reaction_rule( reaction['gene_reaction_rule']): try: cobra_gene = model.genes.get_by_id(gene) except KeyError: print('Could not find gene %s in model. Setting name to ID.') reaction['genes'].append({'bigg_id': gene, 'name': gene}) continue reaction['genes'].append({ 'bigg_id': gene, 'name': cobra_gene.name }) # remove any lost segments reaction['segments'] = { id: seg for id, seg in reaction['segments'].items() if ((seg['to_node_id'] in map_body['nodes'] and seg['to_node_id'] not in nodes_to_delete) and ( seg['from_node_id'] in map_body['nodes'] and seg['from_node_id'] not in nodes_to_delete)) } # delete those reactions for reaction_id in reactions_to_delete: del map_body['reactions'][reaction_id] # delete those nodes for node_id in nodes_to_delete: del map_body['nodes'][node_id] # delete any nodes with no segment for node_id in list(map_body['nodes'].keys()): if node_id not in nodes_with_segments: # may not be there, if previously deleted try: del map_body['nodes'][node_id] print('No segments for node %s. Deleting' % node_id) except KeyError: pass # text labels for id, text_label in map_body['text_labels'].items(): # unsupported attributes for key in list(text_label.keys()): if not key in ["x", "y", "text"]: del text_label[key] # canvas # unsupported attributes for key in list(map_body['canvas'].keys()): if not key in ["x", "y", "width", "height"]: del map_body['canvas'][key] # delete unsupported elements for key in list(map_body.keys()): if not key in ["nodes", "reactions", "text_labels", "canvas"]: del map_body[key] header = { "schema": "https://escher.github.io/escher/jsonschema/1-0-0#", "homepage": "https://escher.github.io", "map_name": "", "map_id": "", "map_description": "" } if has_head: for key, value in map[0].items(): if key in ['schema', 'homepage']: continue header[key] = value the_map = [header, map_body] validate_map(the_map) return the_map