Annotations¶
An element named glc means nothing to a machine. MIRIAM annotations attach a qualifier - what is the relation? - and a resource - which database entry? - to a model element: "this species is CHEBI:17234".
sbmlutils uses the annotation data structures of pymetadata and offers two ways to apply them: in the model definition, or from a spreadsheet onto an existing model.
In the model definition¶
Every element accepts annotations as a list of (qualifier, resource) tuples and an sboTerm:
from sbmlutils.factory import Compartment, Species
from sbmlutils.metadata import BQB, SBO
Compartment(
sid="cyto",
value=1.0,
name="cytosol",
sboTerm=SBO.PHYSICAL_COMPARTMENT,
annotations=[
(BQB.IS, "go/GO:0005829"), # cytosol
(BQB.IS, "https://en.wikipedia.org/wiki/Cytosol"),
],
)
Species(
sid="glc",
compartment="cyto",
initialConcentration=5.0,
sboTerm=SBO.SIMPLE_CHEMICAL,
annotations=[
(BQB.IS, "chebi/CHEBI:17234"), # glucose
(BQB.IS, "vmhmetabolite/glc_D"),
],
)
A resource is written as collection/term (chebi/CHEBI:17234), as an identifiers.org URL, as a urn:miriam:* URN or as an arbitrary URL. pymetadata normalizes it to an identifiers.org compact identifier and validates the term against the identifiers.org registry.
Resources which are written as given¶
The normalization keeps the collection and the term of a resource and only changes how they are spelled, so urn:miriam:chebi:CHEBI%3A17234 and http://identifiers.org/chebi/CHEBI:17234 are both written as https://identifiers.org/CHEBI:17234. A collection which is not in the registry cannot be written as a compact identifier and keeps the classic form: http://identifiers.org/sabiork/1406 is written as https://identifiers.org/sabiork/1406.
What cannot be normalized is a resource which is malformed to begin with. urn:miriam:chebi and chebi/ name a collection and no term, so there is nothing to write a canonical resource from, and urn:miriam::1406 names a term and no collection.
Such a resource is written exactly as it was given instead. A large model can hold tens of thousands of resources of one collection, so this is reported once per collection when the document is written, with the count and one example:
2 annotation resource(s) of the collection 'chebi' are written as given, e.g. 'urn:miriam:chebi': pymetadata parses no term from the resource.
Qualifiers¶
BQB (biological) and BQM (model) are the MIRIAM qualifiers, re-exported from pymetadata:
from sbmlutils.metadata import BQB, BQM
BQB.IS, BQB.IS_VERSION_OF, BQB.HAS_PART, BQB.IS_PART_OF, BQB.OCCURS_IN
BQM.IS, BQM.IS_DESCRIBED_BY, BQM.IS_DERIVED_FROM
Use BQB for what a thing is in biology and BQM for what the model is, e.g. (BQM.IS_DESCRIBED_BY, "pubmed/12345678") on the model itself.
SBO terms¶
The systems biology ontology says what role an element plays. The terms come from pymetadata and carry their label and definition, so an editor shows what a term means while it is completed:
from sbmlutils.metadata import SBO
SBO.SIMPLE_CHEMICAL # 'SBO_0000247'
SBO.SIMPLE_CHEMICAL.label # 'simple chemical'
SBO.SIMPLE_CHEMICAL.curie # 'SBO:0000247'
An sboTerm is written both as the sboTerm attribute and as an RDF annotation of the element.
Creators¶
The people behind a model are recorded on the model:
from sbmlutils.factory import Creator, Model
model = Model(
sid="example",
creators=[
Creator(
familyName="König",
givenName="Matthias",
email="koenigmx@hu-berlin.de",
organization="Humboldt-University Berlin",
site="https://livermetabolism.com",
orcid="0000-0003-1725-179X",
)
],
)
From a spreadsheet¶
Annotating an existing model, or keeping the annotations of a large model outside the code, is done with an annotation file - an Excel sheet, a csv or a tsv - with one annotation per row:
| pattern | sbml_type | annotation_type | qualifier | resource | name |
|---|---|---|---|---|---|
| document | rdf | BQM_IS | sbo/SBO:0000293 | non-spatial continuous framework | |
^demo_\d+$ |
model | rdf | BQM_IS | go/GO:0008152 | metabolic process |
^glc$ |
species | rdf | BQB_IS | chebi/CHEBI:17234 | glucose |
^glc$ |
species | formula | C6H12O6 | ||
^atp$ |
species | charge | -4 |
patternis a regular expression matched against the ids of the elements ofsbml_type, so one row annotates many elements. It is empty for the document.sbml_typeisdocument,model,unit,reaction,transporter,species,compartment,parameter,ruleorfbc:geneproduct.annotation_typeisrdffor a MIRIAM annotation, orformulaandcharge, which write the chemical formula and the charge through the fbc species plugin.nameis a comment for the reader, it is not written into the model.
The file is applied to a model with annotate_sbml:
from pathlib import Path
from sbmlutils.metadata.annotator import annotate_sbml
annotate_sbml(
source=Path("model.xml"),
annotations_path=Path("annotations.xlsx"),
filepath=Path("model_annotated.xml"),
)
create_model takes the same file directly, so a model is annotated while it is created:
from sbmlutils.factory import create_model
create_model(
model=model, filepath=Path("model.xml"), annotations=Path("annotations.xlsx")
)
Validating annotations¶
validate_sbml_annotations checks every annotation of a model against the identifiers.org registry and returns the ones which do not resolve:
from sbmlutils.metadata.validator import validate_sbml_annotations
df = validate_sbml_annotations("model.xml")
print(df)
This queries the registry, so it needs network access on the first run; pymetadata caches the responses.