Transforms#

The manipulation of raw data to interpretable insights typically involves a series of computational steps, each of which transforms inputs of specific data types to outputs of other data types. Metasmith can be taught to use computational tools and perform standard protocols by defining transforms.

Similar to how metasmith manages data, a Transform specifies the input and output Endpoints for a TransformInstance, which contains the explicit computational protocol. TransformInstances are then stored together in a TransformInstanceLibrary.

Transform Instance Library#

This section will show how to define transforms for 2 commonly used bioinformatics tools, Prodigal[1] and BLAST[2]. Prodigal is a gene prediction tool that identifies open reading frames (ORFs) in a given nucleotide sequence. BLAST is a sequence alignment tool that compares a query sequence to a database of sequences. Together, they can be chained to predict genes within a nucleotide sequence and annotate them with functional information.

Start by creating a TransformInstanceLibrary called “simple_genomics”. This will create a folder with the same name in the current directory.

1from metasmith import TransformInstanceLibrary
2transforms = TransformInstanceLibrary("./simple_genomics")

At least one DataTypeLibrary must be added to define the types of the inputs and outputs of each transform. The data types provided by the “minimal_genomics” example types library is sufficient for this section.

1from metasmith import examples
2dtypes = examples.DataTypeLibraries("minimal_genomics")
3transforms.AddTypeLibrary("genomics", dtypes)

Next, a template is created for each transform.

1transforms.AddStub("prodigal")
2transforms.AddStub("blast")
3transforms.Save()

At this point, the directory structure of transforms should look like this…

example.xgdb/
├── _metasmith/
│   ├── index.yml
│   └── types/
│       ├── genomics.yml
│       └── transforms.yml
├── blast.py
└── prodigal.py

… and the contents of prodigal.py and blast.py should look like this:

 1from pathlib import Path
 2from metasmith.python_api import *
 3
 4lib = TransformInstanceLibrary.ResolveParentLibrary(__file__)
 5model = Transform()
 6dep     = model.AddRequirement(node=lib.GetType("transforms::example_input"))
 7out     = model.AddProduct(lib.GetType("transforms::example_output"))
 8
 9def protocol(context: ExecutionContext):
10    out_path = context.Get(out)
11    context.external_shell.Exec(f"touch {out_path.external}")
12    return ExecutionResult(success=out_path.local.exists())
13
14TransformInstance(
15    protocol = protocol,
16    model = model,
17    output_signature = {
18        out: "output.txt",
19    },
20    resources = Resources(
21        cpus = 4,
22        memory = Size.GB(16),
23        duration = Duration(days=1, hours=12, minutes=30),
24    )
25)
  • Data types are provided by the parent library, which is obtained on line 4

  • lines 5-7 define the input and output data types for the transform.

  • Data types are referred to by “namespace:type”

  • The computational protocol where either prodigal or blast will be executed starts on line 9

  • This definition is published to the library by creating a TransformInstance on line 14

  • Note that the output file name is specified on line 18

Execution context#

A ExecutionContext object will be provided to the protocol at runtime. It will contain:

  • the paths to inputs and expected outputs context.Get(lib.GetType("namespace::type"))

  • access to a bash terminal environment context.external_shell

  • a utility function to run containers context.ExecWithContainer(image, command)

  • and additional parameters like CPU and memory limits context.params

Prodigal Example#

Prodigal accepts a contiguous nucleotide sequence or “contig” and produces a list of predicted ORFs as amino acid sequences in fasta format. Additionally, the container providing the prodigal itself is specified as an input. The syntax for running prodigal can be obtained from studying its documentation.

 1from pathlib import Path
 2from metasmith.python_api import *
 3
 4lib = TransformInstanceLibrary.ResolveParentLibrary(__file__)
 5model = Transform()
 6contigs = model.AddRequirement(node=lib.GetType("genomics::contigs"))
 7image   = model.AddRequirement(node=lib.GetType("genomics::oci_image_prodigal"))
 8orfs    = model.AddProduct(lib.GetType("genomics::aa_sequences"))
 9
10def protocol(context: ExecutionContext):
11    out_path = context.Get(orfs)
12    context.ExecWithContainer(
13        image = image,
14        cmd = f"""\
15            prodigal \
16                -i {context.Get(contigs).container} \
17                -a {out_path.container} \
18                -f gff \
19                -o {out_path.container.with_suffix('.gff')} \
20            """,
21    )
22    return ExecutionResult(success=out_path.local.exists())
23
24TransformInstance(
25    protocol = protocol,
26    model = model,
27    output_signature = {
28        orfs: "orfs.faa",
29    },
30)

BLAST Example#

Similarly, BLAST requires a query sequence and a database of sequences to compare against. For simplicity, another fasta file will be used instead of a true BLAST database. The syntax for running BLAST can also be obtained from studying its documentation.

 1from pathlib import Path
 2from metasmith.python_api import *
 3
 4lib = TransformInstanceLibrary.ResolveParentLibrary(__file__)
 5model = Transform()
 6orfs    = model.AddRequirement(node=lib.GetType("genomics::aa_sequences"))
 7refdb   = model.AddRequirement(node=lib.GetType("genomics::protein_reference_fasta"))
 8image   = model.AddRequirement(node=lib.GetType("genomics::oci_image_blast"))
 9annot   = model.AddProduct(lib.GetType("genomics::orf_annotations"))
10
11def protocol(context: ExecutionContext):
12    out_path = context.Get(annot)
13    COLUMNS = f"qseqid sseqid bitscore evalue pident"
14    context.ExecWithContainer(
15        image = image,
16        cmd = f"""\
17            blastp \
18                -query {context.Get(orfs).container} \
19                -subject {context.Get(refdb).container} \
20                -outfmt "6 {COLUMNS}" \
21                -out {out_path.container} \
22            """,
23    )
24    return ExecutionResult(success=out_path.local.exists())
25
26TransformInstance(
27    protocol = protocol,
28    model = model,
29    output_signature = {
30        annot: "annotations.csv",
31    },
32)

References#

  1. Hyatt D, Chen GL, LoCascio PF, Land ML, Larimer FW, Hauser LJ. Prodigal: prokaryotic gene recognition and translation initiation site identification. BMC Bioinformatics. 2010;11(1):119. doi:10.1186/1471-2105-11-119

  2. Altschul SF, Gish W, Miller W, Myers EW, Lipman DJ. Basic local alignment search tool. J Mol Biol. 1990;215(3):403–10. doi:10.1016/S0022-2836(05)80360-2