diff --git a/.gitignore b/.gitignore index 289276f3..7b8bdb22 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ container-ontology .coverage cov.xml site/* +**/artifacts/* diff --git a/challenging-interlab-growth-curve.py b/challenging-interlab-growth-curve.py index a68db89b..78d012de 100644 --- a/challenging-interlab-growth-curve.py +++ b/challenging-interlab-growth-curve.py @@ -9,7 +9,7 @@ import labop import uml -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop_convert.markdown.markdown_specialization import MarkdownSpecialization doc = sbol3.Document() diff --git a/docs/reference/library.md b/docs/reference/library.md index 90f97193..e8614584 100644 --- a/docs/reference/library.md +++ b/docs/reference/library.md @@ -4,8 +4,8 @@ ::: labop.data ::: labop.lab_interface ::: labop.library -::: labop.execution_engine -::: labop.execution_engine_utils +::: labop.execution.execution_engine +::: labop.execution.execution_engine_utils ::: labop.execution_context ::: uml.uml_graphviz ::: uml.utils \ No newline at end of file diff --git a/examples/protocols/calibration/multicolor-particle-calibration/multicolor-particle-calibration.py b/examples/protocols/calibration/multicolor-particle-calibration/multicolor-particle-calibration.py index 8382c3c6..8c56329d 100644 --- a/examples/protocols/calibration/multicolor-particle-calibration/multicolor-particle-calibration.py +++ b/examples/protocols/calibration/multicolor-particle-calibration/multicolor-particle-calibration.py @@ -9,19 +9,10 @@ import sys import sbol3 -from pint import Measurement from tyto import OM import labop -from labop.utils.harness import ( - ProtocolArtifact, - ProtocolDiagram, - ProtocolExecutionDiagram, - ProtocolHarness, - ProtocolNTuples, - ProtocolSampleTrace, - ProtocolSpecialization, -) +from labop.execution.harness import ProtocolHarness, ProtocolSpecialization from labop_convert.markdown.markdown_specialization import MarkdownSpecialization NAMESPACE = "http://igem.org/engineering/" @@ -126,38 +117,14 @@ def generate_prepare_reagents_subprotocol(doc: sbol3.Document): import labop solution_subprotocol = generate_solution_subprotocol(doc) - - # Define buffers - ddh2o = sbol3.Component( - "ddH2O", "https://identifiers.org/pubchem.substance:24901740" - ) - ddh2o.name = "Water, sterile-filtered, BioReagent, suitable for cell culture" - - pbs = sbol3.Component("pbs", "https://pubchem.ncbi.nlm.nih.gov/compound/24978514") - pbs.name = "Phosphate Buffered Saline" - - # Define calibrants - silica_beads = sbol3.Component( - "silica_beads", - "https://nanocym.com/wp-content/uploads/2018/07/NanoCym-All-Datasheets-.pdf", - ) - silica_beads.name = "NanoCym 950 nm monodisperse silica nanoparticles" - silica_beads.description = "3e9 NanoCym microspheres" # where does this go? - - fluorescein = sbol3.Component( - "fluorescein", "https://pubchem.ncbi.nlm.nih.gov/substance/329753341" - ) - fluorescein.name = "Fluorescein" - - cascade_blue = sbol3.Component( - "cascade_blue", "https://pubchem.ncbi.nlm.nih.gov/substance/57269662" + from labop.constants import ( + cascade_blue, + ddh2o, + fluorescein, + pbs, + silica_beads, + sulforhodamine, ) - cascade_blue.name = "Cascade Blue" - - sulforhodamine = sbol3.Component( - "sulforhodamine", "https://pubchem.ncbi.nlm.nih.gov/compound/139216224" - ) - sulforhodamine.name = "Sulforhodamine" doc.add(ddh2o) doc.add(silica_beads) @@ -332,8 +299,9 @@ def generate_prepare_reagents_subprotocol(doc: sbol3.Document): return subprotocol -def generate_protocol(doc: sbol3.Document, protocol: labop.Protocol): +def generate_protocol(doc: sbol3.Document, protocol: labop.Protocol) -> labop.Protocol: import labop + from labop.constants import ddh2o, pbs prepare_reagents_subprotocol = generate_prepare_reagents_subprotocol(doc) prepare_reagents = protocol.primitive_step(prepare_reagents_subprotocol) @@ -561,6 +529,8 @@ def generate_protocol(doc: sbol3.Document, protocol: labop.Protocol): samples=dilution_series1.output_pin("samples"), amount=sbol3.Measure(100, OM.microlitre), direction=labop.Strings.ROW_DIRECTION, + diluent=pbs, + dilution_factor=2, ) serial_dilution1.description = "For each 100.0 microliter transfer, pipette up and down 3X to ensure the dilution is mixed homogeneously." @@ -580,6 +550,8 @@ def generate_protocol(doc: sbol3.Document, protocol: labop.Protocol): samples=dilution_series2.output_pin("samples"), amount=sbol3.Measure(100, OM.microlitre), direction=labop.Strings.ROW_DIRECTION, + diluent=pbs, + dilution_factor=2, ) serial_dilution2.description = "For each 100.0 microliter transfer, pipette up and down 3X to ensure the dilution is mixed homogeneously." @@ -588,6 +560,8 @@ def generate_protocol(doc: sbol3.Document, protocol: labop.Protocol): samples=dilution_series3.output_pin("samples"), amount=sbol3.Measure(100, OM.microlitre), direction=labop.Strings.ROW_DIRECTION, + diluent=pbs, + dilution_factor=2, ) serial_dilution3.description = "For each 100.0 microliter transfer, pipette up and down 3X to ensure the dilution is mixed homogeneously." @@ -596,6 +570,8 @@ def generate_protocol(doc: sbol3.Document, protocol: labop.Protocol): samples=dilution_series4.output_pin("samples"), amount=sbol3.Measure(100, OM.microlitre), direction=labop.Strings.ROW_DIRECTION, + diluent=pbs, + dilution_factor=2, ) serial_dilution4.description = "For each 100.0 microliter transfer, pipette up and down 3X to ensure the dilution is mixed homogeneously." @@ -604,6 +580,8 @@ def generate_protocol(doc: sbol3.Document, protocol: labop.Protocol): samples=dilution_series5.output_pin("samples"), amount=sbol3.Measure(100, OM.microlitre), direction=labop.Strings.ROW_DIRECTION, + diluent=ddh2o, + dilution_factor=2, ) serial_dilution5.description = "For each 100.0 microliter transfer, pipette up and down 3X to ensure the dilution is mixed homogeneously." @@ -612,6 +590,8 @@ def generate_protocol(doc: sbol3.Document, protocol: labop.Protocol): samples=dilution_series6.output_pin("samples"), amount=sbol3.Measure(100, OM.microlitre), direction=labop.Strings.ROW_DIRECTION, + diluent=ddh2o, + dilution_factor=2, ) serial_dilution6.description = "For each 100.0 microliter transfer, pipette up and down 3X to ensure the dilution is mixed homogeneously." @@ -620,6 +600,8 @@ def generate_protocol(doc: sbol3.Document, protocol: labop.Protocol): samples=dilution_series7.output_pin("samples"), amount=sbol3.Measure(100, OM.microlitre), direction=labop.Strings.ROW_DIRECTION, + diluent=ddh2o, + dilution_factor=2, ) serial_dilution7.description = "For each 100.0 microliter transfer, pipette up and down 3X to ensure the dilution is mixed homogeneously." @@ -628,6 +610,8 @@ def generate_protocol(doc: sbol3.Document, protocol: labop.Protocol): samples=dilution_series8.output_pin("samples"), amount=sbol3.Measure(100, OM.microlitre), direction=labop.Strings.ROW_DIRECTION, + diluent=ddh2o, + dilution_factor=2, ) serial_dilution8.description = "For each 100.0 microliter transfer, pipette up and down 3X to ensure the dilution is mixed homogeneously." @@ -812,12 +796,12 @@ def generate_protocol(doc: sbol3.Document, protocol: labop.Protocol): print(f"Saving protocol [{protocol_file}].") f.write(doc.write_string(sbol3.SORTED_NTRIPLES).strip()) - return protocol, doc + return protocol def compute_sample_trajectory(protocol, doc): import labop - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop.strings import Strings from labop_convert import DefaultBehaviorSpecialization @@ -844,7 +828,7 @@ def compute_sample_trajectory(protocol, doc): def generate_markdown_specialization(protocol, doc): import labop - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop.strings import Strings from labop_convert import MarkdownSpecialization @@ -894,7 +878,7 @@ def generate_markdown_specialization(protocol, doc): def generate_ecl_specialization(protocol, doc): import labop - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop.strings import Strings from labop_convert import ECLSpecialization @@ -925,7 +909,7 @@ def generate_ecl_specialization(protocol, doc): def generate_autoprotocol_specialization(protocol, doc): blockPrint() import labop - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop_convert.autoprotocol.autoprotocol_specialization import ( AutoprotocolSpecialization, ) @@ -1028,7 +1012,7 @@ def generate_autoprotocol_specialization(protocol, doc): def generate_emeraldcloud_specialization(protocol, doc, stock_solutions=None): blockPrint() import labop - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop_convert.emeraldcloud.ecl_specialization import ECLSpecialization ecl_output = os.path.join(OUT_DIR, f"{filename}-emeraldcloud.nb") @@ -1121,34 +1105,30 @@ def enablePrint(): sys.stdout = sys.__stdout__ -harness = ProtocolHarness( - entry_point=generate_protocol, - artifacts=[ - ProtocolNTuples(), - ProtocolDiagram(), - ProtocolExecutionDiagram(), - ProtocolSampleTrace(), - ProtocolSpecialization(specialization=MarkdownSpecialization()), - ], - namespace="http://igem.org/engineering/", - protocol_name="interlab", - protocol_long_name="Multicolor fluorescence per bacterial particle calibration", - protocol_version="1.2", - protocol_description=""" -Plate readers report fluorescence values in arbitrary units that vary widely from instrument to instrument. Therefore absolute fluorescence values cannot be directly compared from one instrument to another. In order to compare fluorescence output of biological devices, it is necessary to create a standard fluorescence curve. This variant of the protocol uses two replicates of three colors of dye, plus beads. -Adapted from [https://dx.doi.org/10.17504/protocols.io.bht7j6rn](https://dx.doi.org/10.17504/protocols.io.bht7j6r) and [https://dx.doi.org/10.17504/protocols.io.6zrhf56](https://dx.doi.org/10.17504/protocols.io.6zrhf56) - """, - output_dir="".join(__file__.split(".py")[0].split("/")[-1:]), - libraries=[ - "liquid_handling", - "plate_handling", - "spectrophotometry", - "sample_arrays", - ], -) - - if __name__ == "__main__": + harness = ProtocolHarness( + entry_point=generate_protocol, + artifacts=[ + ProtocolSpecialization( + specialization=MarkdownSpecialization(filename + ".md") + ), + ], + namespace="http://igem.org/engineering/", + protocol_name="interlab", + protocol_long_name="Multicolor fluorescence per bacterial particle calibration", + protocol_version="1.2", + protocol_description=""" + Plate readers report fluorescence values in arbitrary units that vary widely from instrument to instrument. Therefore absolute fluorescence values cannot be directly compared from one instrument to another. In order to compare fluorescence output of biological devices, it is necessary to create a standard fluorescence curve. This variant of the protocol uses two replicates of three colors of dye, plus beads. + Adapted from [https://dx.doi.org/10.17504/protocols.io.bht7j6rn](https://dx.doi.org/10.17504/protocols.io.bht7j6r) and [https://dx.doi.org/10.17504/protocols.io.6zrhf56](https://dx.doi.org/10.17504/protocols.io.6zrhf56) + """, + output_dir="".join(__file__.split(".py")[0].split("/")[-1:]), + libraries=[ + "liquid_handling", + "plate_handling", + "spectrophotometry", + "sample_arrays", + ], + ) harness.run() parser = argparse.ArgumentParser() parser.add_argument( diff --git a/examples/protocols/calibration/multicolor-protocol-calibration-small/multicolor-particle-calibration-small.py b/examples/protocols/calibration/multicolor-protocol-calibration-small/multicolor-particle-calibration-small.py index 1bd71012..728a28d7 100644 --- a/examples/protocols/calibration/multicolor-protocol-calibration-small/multicolor-particle-calibration-small.py +++ b/examples/protocols/calibration/multicolor-protocol-calibration-small/multicolor-particle-calibration-small.py @@ -16,7 +16,7 @@ import labop from labop import SampleArray, SampleMask -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop.strings import Strings from labop_convert.emeraldcloud.ecl_specialization import ECLSpecialization from labop_convert.markdown.markdown_specialization import MarkdownSpecialization @@ -196,6 +196,7 @@ samples=dilution_series1.output_pin("samples"), direction=labop.Strings.COLUMN_DIRECTION, amount=sbol3.Measure(100, OM.microlitre), + diluent=pbs, ) serial_dilution1.description = "For each 100.0 microliter transfer, pipette up and down 3X to ensure the dilution is mixed homogeneously." diff --git a/examples/protocols/calibration/single-particle-calibration/single-particle-calibration.py b/examples/protocols/calibration/single-particle-calibration/single-particle-calibration.py index 5b0d760c..c698c999 100644 --- a/examples/protocols/calibration/single-particle-calibration/single-particle-calibration.py +++ b/examples/protocols/calibration/single-particle-calibration/single-particle-calibration.py @@ -373,7 +373,7 @@ def generate_protocol(): def compute_sample_trajectory(protocol, doc): import labop - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop.strings import Strings from labop_convert import DefaultBehaviorSpecialization @@ -400,12 +400,11 @@ def compute_sample_trajectory(protocol, doc): def generate_markdown_specialization(protocol, doc): import labop - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop.strings import Strings from labop_convert import MarkdownSpecialization if REGENERATE_ARTIFACTS: - dataset_file = f"{filename}_template" # name of xlsx md_file = filename + ".md" else: @@ -450,7 +449,7 @@ def generate_markdown_specialization(protocol, doc): def generate_ecl_specialization(protocol, doc): import labop - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop.strings import Strings from labop_convert import ECLSpecialization @@ -481,7 +480,7 @@ def generate_ecl_specialization(protocol, doc): def generate_autoprotocol_specialization(protocol, doc): blockPrint() import labop - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop_convert.autoprotocol.autoprotocol_specialization import ( AutoprotocolSpecialization, ) @@ -553,7 +552,9 @@ def generate_autoprotocol_specialization(protocol, doc): ) ee = ExecutionEngine( - specializations=[autoprotocol_specialization], out_dir=OUT_DIR, failsafe=False + specializations=[autoprotocol_specialization], + out_dir=OUT_DIR, + failsafe=False, ) execution = ee.execute( protocol, @@ -587,7 +588,7 @@ def generate_emeraldcloud_specialization(protocol, doc, stock_solutions=None): blockPrint() import labop import uml - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop_convert.emeraldcloud.ecl_specialization import ECLSpecialization ddh2o = doc.find(f"{NAMESPACE}ddH2O") diff --git a/examples/protocols/calibration/singlecolor-particle-calibration/singlecolor-particle-calibration.py b/examples/protocols/calibration/singlecolor-particle-calibration/singlecolor-particle-calibration.py index 04c10d17..49632138 100644 --- a/examples/protocols/calibration/singlecolor-particle-calibration/singlecolor-particle-calibration.py +++ b/examples/protocols/calibration/singlecolor-particle-calibration/singlecolor-particle-calibration.py @@ -673,7 +673,7 @@ def generate_protocol(): def compute_sample_trajectory(protocol, doc): import labop - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop.strings import Strings from labop_convert import DefaultBehaviorSpecialization @@ -700,12 +700,11 @@ def compute_sample_trajectory(protocol, doc): def generate_markdown_specialization(protocol, doc): import labop - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop.strings import Strings from labop_convert import MarkdownSpecialization if REGENERATE_ARTIFACTS: - dataset_file = f"{filename}_template" # name of xlsx md_file = filename + ".md" else: @@ -750,7 +749,7 @@ def generate_markdown_specialization(protocol, doc): def generate_ecl_specialization(protocol, doc): import labop - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop.strings import Strings from labop_convert import ECLSpecialization @@ -781,7 +780,7 @@ def generate_ecl_specialization(protocol, doc): def generate_autoprotocol_specialization(protocol, doc): blockPrint() import labop - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop_convert.autoprotocol.autoprotocol_specialization import ( AutoprotocolSpecialization, ) @@ -853,7 +852,9 @@ def generate_autoprotocol_specialization(protocol, doc): ) ee = ExecutionEngine( - specializations=[autoprotocol_specialization], out_dir=OUT_DIR, failsafe=False + specializations=[autoprotocol_specialization], + out_dir=OUT_DIR, + failsafe=False, ) execution = ee.execute( protocol, @@ -887,7 +888,7 @@ def generate_emeraldcloud_specialization(protocol, doc, stock_solutions=None): blockPrint() import labop import uml - from labop.execution_engine import ExecutionEngine + from labop.execution.execution_engine import ExecutionEngine from labop_convert.emeraldcloud.ecl_specialization import ECLSpecialization ddh2o = doc.find(f"{NAMESPACE}ddH2O") diff --git a/examples/protocols/golden-gate-assembly/golden_gate_assembly.py b/examples/protocols/golden-gate-assembly/golden_gate_assembly.py index 713f7dad..f94dc945 100644 --- a/examples/protocols/golden-gate-assembly/golden_gate_assembly.py +++ b/examples/protocols/golden-gate-assembly/golden_gate_assembly.py @@ -1,5 +1,4 @@ import os -import tempfile import sbol3 import tyto @@ -8,149 +7,134 @@ # import labop_md import uml - -############################################# -# set up the document -print("Setting up document") -doc = sbol3.Document() -sbol3.set_namespace("https://bbn.com/scratch/") - -############################################# -# Import the primitive libraries -print("Importing libraries") -labop.import_library("liquid_handling") -print("... Imported liquid handling") -labop.import_library("plate_handling") -print("... Imported plate handling") -labop.import_library("spectrophotometry") -print("... Imported spectrophotometry") -labop.import_library("sample_arrays") -print("... Imported sample arrays") - -############################################# -# Create the protocol -print("Creating protocol") -activity = labop.Protocol("GoldenGate_assembly") -activity.name = "Golden Gate Assembly" -activity.description = """ -This protocol is for Golden Gate Assembly of pairs of DNA fragments into plasmids using the New England Biolabs -Golden Gate Assembly Kit (BsaI-HFv2), product ID NEB #E1601. -Protocol implements the specific case of two part assembly for the NEB-provided protocol: -https://www.neb.com/protocols/2018/10/02/golden-gate-assembly-protocol-for-using-neb-golden-gate-assembly-mix-e1601 -""" -doc.add(activity) - -# create the materials to be provisioned -nf_h2o = sbol3.Component( - "nuclease_free_H2O", "https://identifiers.org/pubchem.compound:962" -) -nf_h2o.name = "Nuclease-free Water" -doc.add(nf_h2o) - -gg_buf = sbol3.Component("NEB_GoldenGate_Buffer", tyto.SBO.functional_entity) -gg_buf.name = "NEB T4 DNA Ligase Buffer (10X)" -gg_buf.derived_from.append( - "https://www.neb.com/products/e1601-neb-golden-gate-assembly-mix" -) -doc.add(gg_buf) - -gg_mix = sbol3.Component("NEB_GoldenGate_AssemblyMix", tyto.SBO.functional_entity) -gg_mix.name = "NEB Golden Gate Assembly Mix" -gg_mix.derived_from.append( - "https://www.neb.com/products/e1601-neb-golden-gate-assembly-mix" -) -doc.add(gg_mix) - -# add an parameters for specifying the layout of the DNA source plate and build plate -dna_sources = activity.input_value( - "source_samples", "http://bioprotocols.org/labop#SampleCollection" -) -# TODO: add_input should be returning a usable ActivityNode! -dna_build_layout = activity.input_value( - "build_layout", "http://bioprotocols.org/labop#SampleData" -) - -# actual steps of the protocol -# get a plate space for building -build_wells = activity.primitive_step("DuplicateCollection", source=dna_build_layout) - -# put DNA into the selected wells following the build plan -activity.primitive_step( - "TransferByMap", - source=dna_sources, - destination=build_wells.output_pin("samples"), - plan=dna_build_layout, -) - -# put buffer, assembly mix, and water into build wells too -activity.primitive_step( - "Provision", - resource=gg_buf, - destination=build_wells.output_pin("samples"), - amount=sbol3.Measure(2, tyto.OM.microliter), -) -activity.primitive_step( - "Provision", - resource=gg_mix, - destination=build_wells.output_pin("samples"), - amount=sbol3.Measure(1, tyto.OM.microliter), -) -activity.primitive_step( - "Provision", - resource=nf_h2o, - destination=build_wells.output_pin("samples"), - amount=sbol3.Measure(15, tyto.OM.microliter), -) - -# seal and spin to mix -activity.primitive_step( - "Seal", location=build_wells.output_pin("samples") -) # TODO: add type -activity.primitive_step( - "Spin", - acceleration=sbol3.Measure( - 300, "http://bioprotocols.org/temporary/unit/g" - ), # TODO: replace with OM-2 unit on resolution of https://github.com/HajoRijgersberg/OM/issues/54 - duration=sbol3.Measure(3, tyto.OM.minute), -) -activity.primitive_step("Unseal", location=build_wells.output_pin("samples")) - -# incubation steps -activity.primitive_step( - "Incubate", - location=build_wells.output_pin("samples"), - duration=sbol3.Measure(60, tyto.OM.minute), - temperature=sbol3.Measure(37, tyto.OM.get_uri_by_term("degree Celsius")), -) # TODO: replace after resolution of https://github.com/SynBioDex/tyto/issues/29 -activity.primitive_step( - "Incubate", - location=build_wells.output_pin("samples"), - duration=sbol3.Measure(5, tyto.OM.minute), - temperature=sbol3.Measure(60, tyto.OM.get_uri_by_term("degree Celsius")), -) # TODO: replace after resolution of https://github.com/SynBioDex/tyto/issues/29 - - -output = activity.designate_output( - "constructs", - "http://bioprotocols.org/labop#SampleCollection", - build_wells.output_pin("samples"), -) -activity.order( - activity.get_last_step(), output -) # don't return until all else is complete - - -######################################## -# Validate and write the document -print("Validating and writing protocol") -v = doc.validate() -assert len(v) == 0, "".join(f"\n {e}" for e in v) - -temp_name = os.path.join(tempfile.gettempdir(), "golden_gate_assembly.nt") -doc.write(temp_name, sbol3.SORTED_NTRIPLES) -print(f"Wrote file as {temp_name}") - -# render and view the dot -dot = activity.to_dot() -dot.render(f"{activity.name}.gv") -# dot.view() +from labop.execution.harness import ProtocolHarness, ProtocolSpecialization +from labop.strings import Strings +from labop_convert.markdown.markdown_specialization import MarkdownSpecialization + + +def generate_protocol(doc: sbol3.Document, activity: labop.Protocol) -> labop.Protocol: + # create the materials to be provisioned + nf_h2o = sbol3.Component( + "nuclease_free_H2O", "https://identifiers.org/pubchem.compound:962" + ) + nf_h2o.name = "Nuclease-free Water" + doc.add(nf_h2o) + + gg_buf = sbol3.Component("NEB_GoldenGate_Buffer", tyto.SBO.functional_entity) + gg_buf.name = "NEB T4 DNA Ligase Buffer (10X)" + gg_buf.derived_from.append( + "https://www.neb.com/products/e1601-neb-golden-gate-assembly-mix" + ) + doc.add(gg_buf) + + gg_mix = sbol3.Component("NEB_GoldenGate_AssemblyMix", tyto.SBO.functional_entity) + gg_mix.name = "NEB Golden Gate Assembly Mix" + gg_mix.derived_from.append( + "https://www.neb.com/products/e1601-neb-golden-gate-assembly-mix" + ) + doc.add(gg_mix) + + # add an parameters for specifying the layout of the DNA source plate and build plate + dna_sources = activity.input_value( + "source_samples", "http://bioprotocols.org/labop#SampleCollection" + ) + # TODO: add_input should be returning a usable ActivityNode! + dna_build_layout = activity.input_value( + "build_layout", "http://bioprotocols.org/labop#SampleData" + ) + + # actual steps of the protocol + # get a plate space for building + build_wells = activity.primitive_step( + "DuplicateCollection", source=dna_build_layout + ) + + # put DNA into the selected wells following the build plan + activity.primitive_step( + "TransferByMap", + source=dna_sources, + destination=build_wells.output_pin("samples"), + plan=dna_build_layout, + ) + + # put buffer, assembly mix, and water into build wells too + activity.primitive_step( + "Provision", + resource=gg_buf, + destination=build_wells.output_pin("samples"), + amount=sbol3.Measure(2, tyto.OM.microliter), + ) + activity.primitive_step( + "Provision", + resource=gg_mix, + destination=build_wells.output_pin("samples"), + amount=sbol3.Measure(1, tyto.OM.microliter), + ) + activity.primitive_step( + "Provision", + resource=nf_h2o, + destination=build_wells.output_pin("samples"), + amount=sbol3.Measure(15, tyto.OM.microliter), + ) + + # seal and spin to mix + activity.primitive_step( + "Seal", location=build_wells.output_pin("samples") + ) # TODO: add type + activity.primitive_step( + "Spin", + acceleration=sbol3.Measure( + 300, "http://bioprotocols.org/temporary/unit/g" + ), # TODO: replace with OM-2 unit on resolution of https://github.com/HajoRijgersberg/OM/issues/54 + duration=sbol3.Measure(3, tyto.OM.minute), + ) + activity.primitive_step("Unseal", location=build_wells.output_pin("samples")) + + # incubation steps + activity.primitive_step( + "Incubate", + location=build_wells.output_pin("samples"), + duration=sbol3.Measure(60, tyto.OM.minute), + temperature=sbol3.Measure(37, tyto.OM.get_uri_by_term("degree Celsius")), + ) # TODO: replace after resolution of https://github.com/SynBioDex/tyto/issues/29 + activity.primitive_step( + "Incubate", + location=build_wells.output_pin("samples"), + duration=sbol3.Measure(5, tyto.OM.minute), + temperature=sbol3.Measure(60, tyto.OM.get_uri_by_term("degree Celsius")), + ) # TODO: replace after resolution of https://github.com/SynBioDex/tyto/issues/29 + + output = activity.designate_output( + "constructs", + "http://bioprotocols.org/labop#SampleCollection", + build_wells.output_pin("samples"), + ) + activity.order( + activity.get_last_step(), output + ) # don't return until all else is complete + return activity + + +if __name__ == "__main__": + harness = ProtocolHarness( + entry_point=generate_protocol, + artifacts=[ + ProtocolSpecialization( + specialization=MarkdownSpecialization( + "test_golden_gate_markdown.md", + sample_format=Strings.XARRAY, + ) + ) + ], + namespace="https://labop.io/examples/protocols/golden-gate-assembly/", + protocol_name="GoldenGate_assembly", + protocol_long_name="Golden Gate Assembly", + protocol_version="1.0", + protocol_description=""" + This protocol is for Golden Gate Assembly of pairs of DNA fragments into plasmids using the New England Biolabs + Golden Gate Assembly Kit (BsaI-HFv2), product ID NEB #E1601. + Protocol implements the specific case of two part assembly for the NEB-provided protocol: + https://www.neb.com/protocols/2018/10/02/golden-gate-assembly-protocol-for-using-neb-golden-gate-assembly-mix-e1601 + """, + ) + harness.run(base_dir=os.path.dirname(__file__)) diff --git a/examples/protocols/growth-curve/growth_curve.py b/examples/protocols/growth-curve/growth_curve.py index 40f2e562..9d2b0afe 100644 --- a/examples/protocols/growth-curve/growth_curve.py +++ b/examples/protocols/growth-curve/growth_curve.py @@ -2,413 +2,420 @@ import tyto import labop +from labop.constants import rpm +from labop.execution.harness import ProtocolHarness, ProtocolSpecialization +from labop.strings import Strings +from labop_convert.markdown.markdown_specialization import MarkdownSpecialization -############################################# -# Helper functions - -# set up the document -doc = sbol3.Document() -sbol3.set_namespace("https://sd2e.org/LabOP/") - -############################################# -# Import the primitive libraries -print("Importing libraries") -labop.import_library("liquid_handling") -labop.import_library("plate_handling") -labop.import_library("spectrophotometry") - -# this should really get pulled into a common library somewhere -rpm = sbol3.UnitDivision( - "rpm", - name="rpm", - symbol="rpm", - label="revolutions per minute", - numerator=tyto.OM.revolution, - denominator=tyto.OM.minute, -) -doc.add(rpm) - - -############################################# -# Create the protocols - -print("Constructing measurement sub-protocols") -# This will be used 10 times generating "OD_Plate_1" .. "OD_Plate_9" - -split_and_measure = labop.Protocol( - "SplitAndMeasure", name="Split samples, dilute, and measure" -) -split_and_measure.description = """ -Subprotocol to split a portion of each sample in a plate into another plate, diluting -with PBS, then measure OD and fluorescence from that other plate -""" -doc.add(split_and_measure) - -# plate for split-and-measure subroutine -od_plate = labop.Container( - name="OD Plate", type=tyto.NCIT.Microplate, max_coordinate="H12" -) -split_and_measure.locations = {od_plate} - -# Inputs: collection of samples, pbs_source -samples = split_and_measure.add_input( - name="samples", - description="Samples to measure", - type="http://bioprotocols.org/labop#LocatedSamples", -) -pbs_source = split_and_measure.add_input( - name="pbs", - description="Source for PBS", - type="http://bioprotocols.org/labop#LocatedSamples", -) - -# subprotocol steps -s_p = split_and_measure.execute_primitive( - "Dispense", - source=pbs_source, - destination=od_plate, - amount=sbol3.Measure(90, tyto.OM.microliter), -) -split_and_measure.add_flow( - split_and_measure.initial(), s_p -) # dispensing OD can be a first action -s_u = split_and_measure.execute_primitive("Unseal", location=samples) -split_and_measure.add_flow( - split_and_measure.initial(), s_u -) # unsealing the growth plate can be a first action -s_t = split_and_measure.execute_primitive( - "TransferInto", - source=samples, - destination=s_p.output_pin("samples"), - amount=sbol3.Measure(10, tyto.OM.microliter), - mixCycles=sbol3.Measure(10, tyto.OM.number), -) -split_and_measure.add_flow( - s_u, s_t -) # transfer can't happen until growth plate is unsealed - -# add the measurements, in parallel -ready_to_measure = labop.Fork() -split_and_measure.activities.append(ready_to_measure) -split_and_measure.add_flow(s_t.output_pin("samples"), ready_to_measure) -measurement_complete = labop.Join() -split_and_measure.activities.append(measurement_complete) - -s_a = split_and_measure.execute_primitive( - "MeasureAbsorbance", - samples=ready_to_measure, - wavelength=sbol3.Measure(600, tyto.OM.nanometer), - numFlashes=sbol3.Measure(25, tyto.OM.number), -) -v_a = split_and_measure.add_output("absorbance", s_a.output_pin("measurements")) -split_and_measure.add_flow(v_a, measurement_complete) - -gains = {0.1, 0.2, 0.16} -for g in gains: - s_f = split_and_measure.execute_primitive( - "MeasureFluorescence", + +def generate_protocol(doc, protocol: labop.Protocol) -> labop.Protocol: + doc.add(rpm) + ############################################# + # Create the protocols + + print("Constructing measurement sub-protocols") + # This will be used 10 times generating "OD_Plate_1" .. "OD_Plate_9" + + split_and_measure = labop.Protocol( + "SplitAndMeasure", name="Split samples, dilute, and measure" + ) + split_and_measure.description = """ + Subprotocol to split a portion of each sample in a plate into another plate, diluting + with PBS, then measure OD and fluorescence from that other plate + """ + doc.add(split_and_measure) + + # plate for split-and-measure subroutine + od_plate = labop.Container( + name="OD Plate", type=tyto.NCIT.Microplate, max_coordinate="H12" + ) + split_and_measure.locations = {od_plate} + + # Inputs: collection of samples, pbs_source + samples = split_and_measure.add_input( + name="samples", + description="Samples to measure", + type="http://bioprotocols.org/labop#LocatedSamples", + ) + pbs_source = split_and_measure.add_input( + name="pbs", + description="Source for PBS", + type="http://bioprotocols.org/labop#LocatedSamples", + ) + + # subprotocol steps + s_p = split_and_measure.execute_primitive( + "Dispense", + source=pbs_source, + destination=od_plate, + amount=sbol3.Measure(90, tyto.OM.microliter), + ) + split_and_measure.add_flow( + split_and_measure.initial(), s_p + ) # dispensing OD can be a first action + s_u = split_and_measure.execute_primitive("Unseal", location=samples) + split_and_measure.add_flow( + split_and_measure.initial(), s_u + ) # unsealing the growth plate can be a first action + s_t = split_and_measure.execute_primitive( + "TransferInto", + source=samples, + destination=s_p.output_pin("samples"), + amount=sbol3.Measure(10, tyto.OM.microliter), + mixCycles=sbol3.Measure(10, tyto.OM.number), + ) + split_and_measure.add_flow( + s_u, s_t + ) # transfer can't happen until growth plate is unsealed + + # add the measurements, in parallel + ready_to_measure = labop.Fork() + split_and_measure.activities.append(ready_to_measure) + split_and_measure.add_flow(s_t.output_pin("samples"), ready_to_measure) + measurement_complete = labop.Join() + split_and_measure.activities.append(measurement_complete) + + s_a = split_and_measure.execute_primitive( + "MeasureAbsorbance", samples=ready_to_measure, - excitationWavelength=sbol3.Measure(488, tyto.OM.nanometer), - emissionBandpassWavelength=sbol3.Measure(530, tyto.OM.nanometer), + wavelength=sbol3.Measure(600, tyto.OM.nanometer), numFlashes=sbol3.Measure(25, tyto.OM.number), - gain=sbol3.Measure(g, tyto.OM.number), ) - v_f = split_and_measure.add_output( - "fluorescence_" + str(g), s_f.output_pin("measurements") + v_a = split_and_measure.add_output("absorbance", s_a.output_pin("measurements")) + split_and_measure.add_flow(v_a, measurement_complete) + + gains = {0.1, 0.2, 0.16} + for g in gains: + s_f = split_and_measure.execute_primitive( + "MeasureFluorescence", + samples=ready_to_measure, + excitationWavelength=sbol3.Measure(488, tyto.OM.nanometer), + emissionBandpassWavelength=sbol3.Measure(530, tyto.OM.nanometer), + numFlashes=sbol3.Measure(25, tyto.OM.number), + gain=sbol3.Measure(g, tyto.OM.number), + ) + v_f = split_and_measure.add_output( + "fluorescence_" + str(g), s_f.output_pin("measurements") + ) + split_and_measure.add_flow(v_f, measurement_complete) + + s_c = split_and_measure.execute_primitive("Cover", location=od_plate) + split_and_measure.add_flow(measurement_complete, s_c) + split_and_measure.add_flow(s_c, split_and_measure.final()) + + s_s = split_and_measure.execute_primitive( + "Seal", location=samples, type="http://autoprotocol.org/lids/breathable" + ) # need to turn this into a proper ontology + split_and_measure.add_flow(measurement_complete, s_s) + split_and_measure.add_flow(s_s, split_and_measure.final()) + + print("Measurement sub-protocol construction complete") + + overnight_od_measure = labop.Protocol( + "OvernightODMeasure", name="Split samples and measure, without dilution" ) - split_and_measure.add_flow(v_f, measurement_complete) - -s_c = split_and_measure.execute_primitive("Cover", location=od_plate) -split_and_measure.add_flow(measurement_complete, s_c) -split_and_measure.add_flow(s_c, split_and_measure.final()) - -s_s = split_and_measure.execute_primitive( - "Seal", location=samples, type="http://autoprotocol.org/lids/breathable" -) # need to turn this into a proper ontology -split_and_measure.add_flow(measurement_complete, s_s) -split_and_measure.add_flow(s_s, split_and_measure.final()) - -print("Measurement sub-protocol construction complete") - - -overnight_od_measure = labop.Protocol( - "OvernightODMeasure", name="Split samples and measure, without dilution" -) -overnight_od_measure.description = """ -Subprotocol to split a portion of each sample in an unsealed plate into another plate, then measure OD and fluorescence from that other plate -""" -doc.add(overnight_od_measure) - -# plate for split-and-measure subroutine -od_plate = labop.Container( - name="OD Plate", type=tyto.NCIT.Microplate, max_coordinate="H12" -) -overnight_od_measure.locations = {od_plate} - -# Input: collection of samples -samples = overnight_od_measure.add_input( - name="samples", - description="Samples to measure", - type="http://bioprotocols.org/labop#LocatedSamples", -) - -# subprotocol steps -s_t = overnight_od_measure.execute_primitive( - "Transfer", - source=samples, - destination=od_plate, - amount=sbol3.Measure(200, tyto.OM.microliter), -) -overnight_od_measure.add_flow(overnight_od_measure.initial(), s_t) # first action - -# add the measurements, in parallel -ready_to_measure = labop.Fork() -overnight_od_measure.activities.append(ready_to_measure) -overnight_od_measure.add_flow(s_t.output_pin("samples"), ready_to_measure) -measurement_complete = labop.Join() -overnight_od_measure.activities.append(measurement_complete) - -s_a = overnight_od_measure.execute_primitive( - "MeasureAbsorbance", - samples=ready_to_measure, - wavelength=sbol3.Measure(600, tyto.OM.nanometer), - numFlashes=sbol3.Measure(25, tyto.OM.number), -) -v_a = overnight_od_measure.add_output("absorbance", s_a.output_pin("measurements")) -overnight_od_measure.add_flow(v_a, measurement_complete) - -gains = {0.1, 0.2, 0.16} -for g in gains: - s_f = overnight_od_measure.execute_primitive( - "MeasureFluorescence", + overnight_od_measure.description = """ + Subprotocol to split a portion of each sample in an unsealed plate into another plate, then measure OD and fluorescence from that other plate + """ + doc.add(overnight_od_measure) + + # plate for split-and-measure subroutine + od_plate = labop.Container( + name="OD Plate", type=tyto.NCIT.Microplate, max_coordinate="H12" + ) + overnight_od_measure.locations = {od_plate} + + # Input: collection of samples + samples = overnight_od_measure.add_input( + name="samples", + description="Samples to measure", + type="http://bioprotocols.org/labop#LocatedSamples", + ) + + # subprotocol steps + s_t = overnight_od_measure.execute_primitive( + "Transfer", + source=samples, + destination=od_plate, + amount=sbol3.Measure(200, tyto.OM.microliter), + ) + overnight_od_measure.add_flow(overnight_od_measure.initial(), s_t) # first action + + # add the measurements, in parallel + ready_to_measure = labop.Fork() + overnight_od_measure.activities.append(ready_to_measure) + overnight_od_measure.add_flow(s_t.output_pin("samples"), ready_to_measure) + measurement_complete = labop.Join() + overnight_od_measure.activities.append(measurement_complete) + + s_a = overnight_od_measure.execute_primitive( + "MeasureAbsorbance", samples=ready_to_measure, - excitationWavelength=sbol3.Measure(488, tyto.OM.nanometer), - emissionBandpassWavelength=sbol3.Measure(530, tyto.OM.nanometer), + wavelength=sbol3.Measure(600, tyto.OM.nanometer), numFlashes=sbol3.Measure(25, tyto.OM.number), - gain=sbol3.Measure(g, tyto.OM.number), ) - v_f = overnight_od_measure.add_output( - "fluorescence_" + str(g), s_f.output_pin("measurements") + v_a = overnight_od_measure.add_output("absorbance", s_a.output_pin("measurements")) + overnight_od_measure.add_flow(v_a, measurement_complete) + + gains = {0.1, 0.2, 0.16} + for g in gains: + s_f = overnight_od_measure.execute_primitive( + "MeasureFluorescence", + samples=ready_to_measure, + excitationWavelength=sbol3.Measure(488, tyto.OM.nanometer), + emissionBandpassWavelength=sbol3.Measure(530, tyto.OM.nanometer), + numFlashes=sbol3.Measure(25, tyto.OM.number), + gain=sbol3.Measure(g, tyto.OM.number), + ) + v_f = overnight_od_measure.add_output( + "fluorescence_" + str(g), s_f.output_pin("measurements") + ) + overnight_od_measure.add_flow(v_f, measurement_complete) + + s_c = overnight_od_measure.execute_primitive("Cover", location=od_plate) + overnight_od_measure.add_flow(measurement_complete, s_c) + overnight_od_measure.add_flow(s_c, overnight_od_measure.final()) + + overnight_od_measure.add_flow(measurement_complete, overnight_od_measure.final()) + + print("Overnight measurement sub-protocol construction complete") + ############################################# + # Now the full protocol + + print("Making protocol") + + activity = labop.Protocol("GrowthCurve", name="SD2 Yeast growth curve protocol") + activity.description = """ + Protocol from SD2 Yeast States working group for studying growth curves: + Grow up cells and read with plate reader at n-hour intervals + """ + doc.add(activity) + + # Create the materials to be provisioned + PBS = sbol3.Component("PBS", "https://identifiers.org/pubchem.compound:24978514") + PBS.name = ( + "Phosphate-Buffered Saline" # I'd like to get this name from PubChem with tyto + ) + doc.add(PBS) + # need to retrieve and convert this one + SC_media = sbol3.Component("SC_Media", "TBD", name="Synthetic Complete Media") + doc.add(SC_media) + SC_plus_dox = sbol3.Component( + "SC_Media_plus_dox", + "TBD", + name="Synthetic Complete Media plus 40nM Doxycycline", + ) + doc.add(SC_plus_dox) + activity.material += {PBS, SC_media, SC_plus_dox} + + ## create the containers + # provisioning sources + pbs_source = labop.Container(name="PBS Source", type=tyto.NCIT.Bottle) + sc_source = labop.Container( + name="SC Media + 40nM Doxycycline Source", type=tyto.NCIT.Bottle + ) + om_source = labop.Container(name="Overnight SC Media Source", type=tyto.NCIT.Bottle) + # plates for the general protocol + overnight_plate = labop.Container( + name="Overnight Growth Plate", + type=tyto.NCIT.Microplate, + max_coordinate="H12", + ) + overnight_od_plate = labop.Container( + name="Overnight Growth Plate", + type=tyto.NCIT.Microplate, + max_coordinate="H12", ) - overnight_od_measure.add_flow(v_f, measurement_complete) - -s_c = overnight_od_measure.execute_primitive("Cover", location=od_plate) -overnight_od_measure.add_flow(measurement_complete, s_c) -overnight_od_measure.add_flow(s_c, overnight_od_measure.final()) - -overnight_od_measure.add_flow(measurement_complete, overnight_od_measure.final()) - -print("Overnight measurement sub-protocol construction complete") -############################################# -# Now the full protocol - -print("Making protocol") - -activity = labop.Protocol("GrowthCurve", name="SD2 Yeast growth curve protocol") -activity.description = """ -Protocol from SD2 Yeast States working group for studying growth curves: -Grow up cells and read with plate reader at n-hour intervals -""" -doc.add(activity) - -# Create the materials to be provisioned -PBS = sbol3.Component("PBS", "https://identifiers.org/pubchem.compound:24978514") -PBS.name = ( - "Phosphate-Buffered Saline" # I'd like to get this name from PubChem with tyto -) -doc.add(PBS) -# need to retrieve and convert this one -SC_media = sbol3.Component("SC_Media", "TBD", name="Synthetic Complete Media") -doc.add(SC_media) -SC_plus_dox = sbol3.Component( - "SC_Media_plus_dox", - "TBD", - name="Synthetic Complete Media plus 40nM Doxycycline", -) -doc.add(SC_plus_dox) -activity.material += {PBS, SC_media, SC_plus_dox} - -## create the containers -# provisioning sources -pbs_source = labop.Container(name="PBS Source", type=tyto.NCIT.Bottle) -sc_source = labop.Container( - name="SC Media + 40nM Doxycycline Source", type=tyto.NCIT.Bottle -) -om_source = labop.Container(name="Overnight SC Media Source", type=tyto.NCIT.Bottle) -# plates for the general protocol -overnight_plate = labop.Container( - name="Overnight Growth Plate", - type=tyto.NCIT.Microplate, - max_coordinate="H12", -) -overnight_od_plate = labop.Container( - name="Overnight Growth Plate", - type=tyto.NCIT.Microplate, - max_coordinate="H12", -) -growth_plate = labop.Container( - name="Growth Curve Plate", type=tyto.NCIT.Microplate, max_coordinate="H12" -) -activity.locations = { - pbs_source, - sc_source, - om_source, - overnight_plate, - growth_plate, -} - -# One input: a microplate full of strains -# TODO: change this to allow alternative places -strain_plate = activity.add_input( - name="strain_plate", - description="Plate of strains to grow", - type="http://bioprotocols.org/labop#LocatedSamples", -) -# input_plate = labop.Container(name='497943_4_UWBF_to_stratoes', type=tyto.NCIT.Microplate, max_coordinate='H12') - -print("Constructing protocol steps") - -# set up the sources -p_pbs = activity.execute_primitive( - "Provision", - resource=PBS, - destination=pbs_source, - amount=sbol3.Measure(117760, tyto.OM.microliter), -) -activity.add_flow(activity.initial(), p_pbs) # start with provisioning -p_om = activity.execute_primitive( - "Provision", - resource=SC_media, - destination=om_source, - amount=sbol3.Measure(98, tyto.OM.milliliter), -) -activity.add_flow(activity.initial(), p_om) # start with provisioning -p_scm = activity.execute_primitive( - "Provision", - resource=SC_plus_dox, - destination=sc_source, - amount=sbol3.Measure(117200, tyto.OM.microliter), -) -activity.add_flow(activity.initial(), p_scm) # start with provisioning - -# prep the overnight culture, then seal away the source plate again -s_d = activity.execute_primitive( - "Dispense", - source=p_om.output_pin("samples"), - destination=overnight_plate, - amount=sbol3.Measure(500, tyto.OM.microliter), -) -s_u = activity.execute_primitive("Unseal", location=strain_plate) -s_t = activity.execute_primitive( - "TransferInto", - source=strain_plate, - destination=s_d.output_pin("samples"), - amount=sbol3.Measure(5, tyto.OM.microliter), - mixCycles=sbol3.Measure(10, tyto.OM.number), -) -s_s = activity.execute_primitive( - "Seal", - location=strain_plate, - type="http://autoprotocol.org/lids/breathable", -) # need to turn this into a proper ontology -activity.add_flow(s_u, s_t) # transfer can't happen until strain plate is unsealed ... -activity.add_flow(s_t, s_s) # ... and must complete before we re-seal it - -# run the overnight culture -overnight_samples = s_t.output_pin("samples") -s_s = activity.execute_primitive( - "Seal", - location=overnight_samples, - type="http://autoprotocol.org/lids/breathable", -) # need to turn this into a proper ontology -s_i = activity.execute_primitive( - "Incubate", - location=overnight_samples, - temperature=sbol3.Measure(30, tyto.OM.get_uri_by_term("degree Celsius")), - duration=sbol3.Measure(16, tyto.OM.hour), - shakingFrequency=sbol3.Measure(350, rpm.identity), -) -activity.add_flow(s_t, s_s) # sealing after transfer -activity.add_flow(s_s, s_i) # incubation after sealing - -# Check the OD after running overnight; note that this is NOT the same measurement process as for the during-growth measurements -s_u = activity.execute_primitive( - "Unseal", location=overnight_samples -) # added because using the subprotocol leaves a sealed plate -activity.add_flow(s_i, s_u) # growth plate after measurement -s_m = activity.execute_subprotocol(overnight_od_measure, samples=overnight_samples) -activity.add_flow(s_u, s_m) # measurement after incubation and unsealing - -# Set up the growth plate -s_d = activity.execute_primitive( - "Dispense", - source=p_scm.output_pin("samples"), - destination=growth_plate, - amount=sbol3.Measure(700, tyto.OM.microliter), -) -s_t = activity.execute_primitive( - doc.find("TransferInto"), - source=overnight_samples, - destination=s_d.output_pin("samples"), - amount=sbol3.Measure(2, tyto.OM.microliter), - mixCycles=sbol3.Measure(10, tyto.OM.number), -) -s_s = activity.execute_primitive( - "Seal", - location=overnight_samples, - type="http://autoprotocol.org/lids/breathable", -) # need to turn this into a proper ontology -activity.add_flow( - s_u, s_t -) # transfer can't happen until overnight plate is unsealed ... -activity.add_flow(s_t, s_s) # ... and must complete before we re-seal it -activity.add_flow(s_m, s_s) # ... as must its measurement - -# run the step-by-step culture -growth_samples = s_t.output_pin("samples") -last_round = None -# sample_hours = [1, 3, 6, 9, 12, 15, 18, 21, 24] # Original: modified to be friendly to human execution -sample_hours = [1, 3, 6, 9, 18, 21, 24] -for i in range(0, len(sample_hours)): - incubation_hours = sample_hours[i] - (sample_hours[i - 1] if i > 0 else 0) + growth_plate = labop.Container( + name="Growth Curve Plate", + type=tyto.NCIT.Microplate, + max_coordinate="H12", + ) + activity.locations = { + pbs_source, + sc_source, + om_source, + overnight_plate, + growth_plate, + } + + # One input: a microplate full of strains + # TODO: change this to allow alternative places + strain_plate = activity.add_input( + name="strain_plate", + description="Plate of strains to grow", + type="http://bioprotocols.org/labop#LocatedSamples", + ) + # input_plate = labop.Container(name='497943_4_UWBF_to_stratoes', type=tyto.NCIT.Microplate, max_coordinate='H12') + + print("Constructing protocol steps") + + # set up the sources + p_pbs = activity.execute_primitive( + "Provision", + resource=PBS, + destination=pbs_source, + amount=sbol3.Measure(117760, tyto.OM.microliter), + ) + activity.add_flow(activity.initial(), p_pbs) # start with provisioning + p_om = activity.execute_primitive( + "Provision", + resource=SC_media, + destination=om_source, + amount=sbol3.Measure(98, tyto.OM.milliliter), + ) + activity.add_flow(activity.initial(), p_om) # start with provisioning + p_scm = activity.execute_primitive( + "Provision", + resource=SC_plus_dox, + destination=sc_source, + amount=sbol3.Measure(117200, tyto.OM.microliter), + ) + activity.add_flow(activity.initial(), p_scm) # start with provisioning + + # prep the overnight culture, then seal away the source plate again + s_d = activity.execute_primitive( + "Dispense", + source=p_om.output_pin("samples"), + destination=overnight_plate, + amount=sbol3.Measure(500, tyto.OM.microliter), + ) + s_u = activity.execute_primitive("Unseal", location=strain_plate) + s_t = activity.execute_primitive( + "TransferInto", + source=strain_plate, + destination=s_d.output_pin("samples"), + amount=sbol3.Measure(5, tyto.OM.microliter), + mixCycles=sbol3.Measure(10, tyto.OM.number), + ) + s_s = activity.execute_primitive( + "Seal", + location=strain_plate, + type="http://autoprotocol.org/lids/breathable", + ) # need to turn this into a proper ontology + activity.add_flow( + s_u, s_t + ) # transfer can't happen until strain plate is unsealed ... + activity.add_flow(s_t, s_s) # ... and must complete before we re-seal it + + # run the overnight culture + overnight_samples = s_t.output_pin("samples") + s_s = activity.execute_primitive( + "Seal", + location=overnight_samples, + type="http://autoprotocol.org/lids/breathable", + ) # need to turn this into a proper ontology s_i = activity.execute_primitive( "Incubate", - location=growth_samples, + location=overnight_samples, temperature=sbol3.Measure(30, tyto.OM.get_uri_by_term("degree Celsius")), - duration=sbol3.Measure(incubation_hours, tyto.OM.hour), + duration=sbol3.Measure(16, tyto.OM.hour), shakingFrequency=sbol3.Measure(350, rpm.identity), ) - s_m = activity.execute_subprotocol( - split_and_measure, - samples=growth_samples, - pbs=p_pbs.output_pin("samples"), + activity.add_flow(s_t, s_s) # sealing after transfer + activity.add_flow(s_s, s_i) # incubation after sealing + + # Check the OD after running overnight; note that this is NOT the same measurement process as for the during-growth measurements + s_u = activity.execute_primitive( + "Unseal", location=overnight_samples + ) # added because using the subprotocol leaves a sealed plate + activity.add_flow(s_i, s_u) # growth plate after measurement + s_m = activity.execute_subprotocol(overnight_od_measure, samples=overnight_samples) + activity.add_flow(s_u, s_m) # measurement after incubation and unsealing + + # Set up the growth plate + s_d = activity.execute_primitive( + "Dispense", + source=p_scm.output_pin("samples"), + destination=growth_plate, + amount=sbol3.Measure(700, tyto.OM.microliter), ) - if last_round: - activity.add_flow(last_round, s_i) # measurement after incubation - activity.add_flow(s_i, s_m) # measurement after incubation - last_round = s_m - -activity.add_flow(last_round, activity.final()) - -print("Protocol construction complete") - - -###################### -# Invocation of protocol on a plate:; - -# plate for invoking the protocol -# input_plate = labop.Container(name='497943_4_UWBF_to_stratoes', type=tyto.NCIT.Microplate, max_coordinate='H12') - - -print("Validating document") -for e in doc.validate().errors: - print(e) -for w in doc.validate().warnings: - print(w) - -print("Writing document") - -doc.write("test/testfiles/growth_curve.json", "json-ld") -doc.write("test/testfiles/growth_curve.ttl", "turtle") - -print("Complete") + s_t = activity.execute_primitive( + doc.find("TransferInto"), + source=overnight_samples, + destination=s_d.output_pin("samples"), + amount=sbol3.Measure(2, tyto.OM.microliter), + mixCycles=sbol3.Measure(10, tyto.OM.number), + ) + s_s = activity.execute_primitive( + "Seal", + location=overnight_samples, + type="http://autoprotocol.org/lids/breathable", + ) # need to turn this into a proper ontology + activity.add_flow( + s_u, s_t + ) # transfer can't happen until overnight plate is unsealed ... + activity.add_flow(s_t, s_s) # ... and must complete before we re-seal it + activity.add_flow(s_m, s_s) # ... as must its measurement + + # run the step-by-step culture + growth_samples = s_t.output_pin("samples") + last_round = None + # sample_hours = [1, 3, 6, 9, 12, 15, 18, 21, 24] # Original: modified to be friendly to human execution + sample_hours = [1, 3, 6, 9, 18, 21, 24] + for i in range(0, len(sample_hours)): + incubation_hours = sample_hours[i] - (sample_hours[i - 1] if i > 0 else 0) + s_i = activity.execute_primitive( + "Incubate", + location=growth_samples, + temperature=sbol3.Measure(30, tyto.OM.get_uri_by_term("degree Celsius")), + duration=sbol3.Measure(incubation_hours, tyto.OM.hour), + shakingFrequency=sbol3.Measure(350, rpm.identity), + ) + s_m = activity.execute_subprotocol( + split_and_measure, + samples=growth_samples, + pbs=p_pbs.output_pin("samples"), + ) + if last_round: + activity.add_flow(last_round, s_i) # measurement after incubation + activity.add_flow(s_i, s_m) # measurement after incubation + last_round = s_m + + activity.add_flow(last_round, activity.final()) + + print("Protocol construction complete") + + ###################### + # Invocation of protocol on a plate:; + + # plate for invoking the protocol + # input_plate = labop.Container(name='497943_4_UWBF_to_stratoes', type=tyto.NCIT.Microplate, max_coordinate='H12') + + print("Validating document") + for e in doc.validate().errors: + print(e) + for w in doc.validate().warnings: + print(w) + + print("Writing document") + + doc.write("test/testfiles/growth_curve.json", "json-ld") + doc.write("test/testfiles/growth_curve.ttl", "turtle") + + print("Complete") + + +if __name__ == "__main__": + harness = ProtocolHarness( + entry_point=generate_protocol, + artifacts=[ + ProtocolSpecialization( + specialization=MarkdownSpecialization( + "test_golden_gate_markdown.md", + sample_format=Strings.XARRAY, + ) + ) + ], + namespace="https://labop.io/examples/protocols/golden-gate-assembly/", + protocol_name="GoldenGate_assembly", + protocol_long_name="Golden Gate Assembly", + protocol_version="1.0", + protocol_description=""" + This protocol is for Golden Gate Assembly of pairs of DNA fragments into plasmids using the New England Biolabs + Golden Gate Assembly Kit (BsaI-HFv2), product ID NEB #E1601. + Protocol implements the specific case of two part assembly for the NEB-provided protocol: + https://www.neb.com/protocols/2018/10/02/golden-gate-assembly-protocol-for-using-neb-golden-gate-assembly-mix-e1601 + """, + ) + harness.run() diff --git a/examples/protocols/iGEM/challenging-interlab-growth-curve.py b/examples/protocols/iGEM/challenging-interlab-growth-curve.py index a68db89b..2649e520 100644 --- a/examples/protocols/iGEM/challenging-interlab-growth-curve.py +++ b/examples/protocols/iGEM/challenging-interlab-growth-curve.py @@ -9,7 +9,7 @@ import labop import uml -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop_convert.markdown.markdown_specialization import MarkdownSpecialization doc = sbol3.Document() @@ -118,6 +118,7 @@ "ContainerSet", quantity=2 * len(plasmids), specification=labop.ContainerSpec( + "culture_day_1", name=f"culture (day 1)", queryString="cont:CultureTube", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -141,6 +142,7 @@ "ContainerSet", quantity=2 * len(plasmids), specification=labop.ContainerSpec( + "culture_day_2", name=f"culture (day 2)", queryString="cont:CultureTube", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -169,6 +171,7 @@ "ContainerSet", quantity=2 * len(plasmids), specification=labop.ContainerSpec( + "back_diluted_culture", name=f"back-diluted culture", queryString="cont:50mlConicalTube", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -193,6 +196,7 @@ quantity=2 * len(plasmids), replicates=3, specification=labop.ContainerSpec( + "replicate_subcultures", name=f"replicate subcultures", queryString="cont:50mlConicalTube", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -221,6 +225,7 @@ "ContainerSet", quantity=2 * len(plasmids) * 3, specification=labop.ContainerSpec( + "cultures_0_hr_timepoint", name="cultures (0 hr timepoint)", queryString="cont:MicrofugeTube", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -245,6 +250,7 @@ plate1 = activity.primitive_step( "EmptyContainer", specification=labop.ContainerSpec( + "plate_1", name="plate 1", queryString="cont:Plate96Well", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -253,6 +259,7 @@ plate2 = activity.primitive_step( "EmptyContainer", specification=labop.ContainerSpec( + "plate_2", name="plate 2", queryString="cont:Plate96Well", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -261,6 +268,7 @@ plate3 = activity.primitive_step( "EmptyContainer", specification=labop.ContainerSpec( + "plate_3", name="plate 3", queryString="cont:Plate96Well", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -436,15 +444,9 @@ fluorescence_0hrs_plate3.name = "0 hr fluorescence timepoint" ## Cover plate -seal = activity.primitive_step( - "EvaporativeSeal", location=plate1.output_pin("samples"), type="foo" -) -seal = activity.primitive_step( - "EvaporativeSeal", location=plate2.output_pin("samples"), type="foo" -) -seal = activity.primitive_step( - "EvaporativeSeal", location=plate3.output_pin("samples"), type="foo" -) +seal = activity.primitive_step("EvaporativeSeal", location=plate1.output_pin("samples")) +seal = activity.primitive_step("EvaporativeSeal", location=plate2.output_pin("samples")) +seal = activity.primitive_step("EvaporativeSeal", location=plate3.output_pin("samples")) ## Begin outgrowth @@ -502,6 +504,7 @@ "ContainerSet", quantity=2 * len(plasmids) * 3, specification=labop.ContainerSpec( + "cultures_2_hr_timepoint", name="cultures (0 hr timepoint)", queryString="cont:MicrofugeTube", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -526,6 +529,7 @@ plate4 = activity.primitive_step( "EmptyContainer", specification=labop.ContainerSpec( + "plate_4", name="plate 4", queryString="cont:Plate96Well", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, diff --git a/examples/protocols/iGEM/interlab-endpoint.py b/examples/protocols/iGEM/interlab-endpoint.py index a423e1f8..7c5782c9 100644 --- a/examples/protocols/iGEM/interlab-endpoint.py +++ b/examples/protocols/iGEM/interlab-endpoint.py @@ -3,7 +3,6 @@ """ import json import os -import sys from urllib.parse import quote import sbol3 @@ -11,7 +10,6 @@ import labop import uml -from labop.execution_engine import ExecutionEngine from labop_convert import MarkdownSpecialization @@ -48,604 +46,630 @@ def render_kit_coordinates_table(ex: labop.ProtocolExecution): ex.markdown = ex.markdown[:insert_index] + table + ex.markdown[insert_index:] -if "unittest" in sys.modules: - REGENERATE_ARTIFACTS = False -else: - REGENERATE_ARTIFACTS = True - -filename = "".join(__file__.split(".py")[0].split("/")[-1:]) - -doc = sbol3.Document() -sbol3.set_namespace("http://igem.org/engineering/") - -############################################# -# Import the primitive libraries -print("Importing libraries") -labop.import_library("liquid_handling") -print("... Imported liquid handling") -labop.import_library("plate_handling") -# print('... Imported plate handling') -labop.import_library("spectrophotometry") -print("... Imported spectrophotometry") -labop.import_library("sample_arrays") -print("... Imported sample arrays") -labop.import_library("culturing") -############################################# - - -# Cells and test circuits -dh5alpha = sbol3.Component("dh5alpha", "https://identifiers.org/taxonomy:668369") -dh5alpha.name = "_E. coli_ DH5 alpha competent cells" -doc.add(dh5alpha) - -neg_control_plasmid = sbol3.Component( - "neg_control_plasmid", "http://parts.igem.org/Part:BBa_J428100" -) -neg_control_plasmid.name = "Negative control" -neg_control_plasmid.description = "BBa_J428100 Kit Plate 1 Well 12M" - -pos_control_plasmid = sbol3.Component( - "pos_control_plasmid", "http://parts.igem.org/Part:BBa_I20270" -) -pos_control_plasmid.name = "Positive control (I20270)" -pos_control_plasmid.description = "BBa_I20270 Kit Plate 1 Well 1A" - -test_device1 = sbol3.Component("test_device1", "http://parts.igem.org/Part:BBa_J364000") -test_device1.name = "Test Device 1 (J364000)" -test_device1.description = "BBa_J364000 Kit Plate 1 Well 1C" - -test_device2 = sbol3.Component("test_device2", "http://parts.igem.org/Part:BBa_J364001") -test_device2.name = "Test Device 2 (J364001)" -test_device2.description = "BBa_J364001 Kit Plate 1 Well 1E" - -test_device3 = sbol3.Component("test_device3", "http://parts.igem.org/Part:BBa_J364002") -test_device3.name = "Test Device 3 (J364002)" -test_device3.description = "BBa_J364002 Kit Plate 1 Well 1G" - -test_device4 = sbol3.Component("test_device4", "http://parts.igem.org/Part:BBa_J364007") -test_device4.name = "Test Device 4 (J364007)" -test_device4.description = "BBa_J364007 Kit Plate 1 Well 1I" - -test_device5 = sbol3.Component("test_device5", "http://parts.igem.org/Part:BBa_J364008") -test_device5.name = "Test Device 5 (J364008)" -test_device5.description = "BBa_J364008 Kit Plate 1 Well 1K" - -test_device6 = sbol3.Component("test_device6", "http://parts.igem.org/Part:BBa_J364009") -test_device6.name = "Test Device 6 (J364009)" -test_device6.description = "BBa_J364009 Kit Plate 1 Well 1M" - -doc.add(neg_control_plasmid) -doc.add(pos_control_plasmid) -doc.add(test_device1) -doc.add(test_device2) -doc.add(test_device3) -doc.add(test_device4) -doc.add(test_device5) -doc.add(test_device6) - -# Other reagents -lb_cam = sbol3.Component("lb_cam", "") -lb_cam.name = "LB Broth + Chloramphenicol (34 ug/mL)" - -lb_agar_cam = sbol3.Component("lb_agar_cam", "") -lb_agar_cam.name = "LB Agar + Chloramphenicol (34 ug/mL)" - -chloramphenicol = sbol3.Component( - "chloramphenicol", "https://pubchem.ncbi.nlm.nih.gov/compound/5959" -) -chloramphenicol.name = "Chloramphenicol stock solution (34 mg/mL)" - -ice = sbol3.Component("ice", "") -ice.name = "Ice" - -doc.add(lb_cam) -doc.add(lb_agar_cam) -doc.add(chloramphenicol) -doc.add(ice) - -# Instruments and laboratory equipment -# TODO: instruments should be represented by sbol3.Agent -plate_reader = sbol3.Component("plate_reader", "") -plate_reader.name = "Plate reader" - -shaking_incubator = sbol3.Component("shaking_incubator", "") -shaking_incubator.name = "Shaking incubator" - -doc.add(plate_reader) -doc.add(shaking_incubator) - - -activity = labop.Protocol("interlab") -activity.name = "Cell measurement protocol" -activity.version = sbol3.TextProperty( - activity, - "http://igem.org/interlab_working_group#Version", - 0, - 1, - [], - "1.2.2", -) -activity.description = """This year we plan to test protocols that will eventually be automated. For this reason, we will use 96-well plates instead of test tubes for culturing. Consequently, we want to evaluate how the performance of our plate culturing protocol compares to culturing in test tubes (e.g. 10 mL falcon tube) on a global scale. - -At the end of the experiment, you will have two plates to be measured. You will measure both fluorescence and absorbance in each plate. - -Before performing the cell measurements, you need to perform all the calibration measurements. Please do not proceed unless you have completed the calibration protocol. Completion of the calibrations will ensure that you understand the measurement process and that you can take the cell measurements under the same conditions. For consistency and reproducibility, we are requiring all teams to use E. coli K-12 DH5-alpha. If you do not have access to this strain, you can request streaks of the transformed devices from another team near you. If you are absolutely unable to obtain the DH5-alpha strain, you may still participate in the InterLab study by contacting the Engineering Committee (engineering [at] igem [dot] org) to discuss your situation. - -For all below indicated cell measurements, you must use the same type of plates and the same volumes that you used in your calibration protocol. You must also use the same settings (e.g., filters or excitation and emission wavelengths) that you used in your calibration measurements. If you do not use the same type of plates, volumes, and settings, the measurements will not be valid. - -Protocol summary: You will transform the eight devices listed in Table 1 into E. coli K-12 DH5-alpha cells. The next day you will pick two colonies from each transformation (16 total) and use them to inoculate 5 mL overnight cultures (this step is still in tubes). Each of these 16 overnight cultures will be used to inoculate four wells in a 96-well plate (200 microliter each, 4 replicates) and one test tube (12 mL). You will measure how fluorescence and optical density develops over 6 hours by taking measurements at time point 0 hour and at time point 6 hours. Follow the protocol below and the visual instructions in Figure 1 and Figure 2.""" - -doc.add(activity) -activity = doc.find(activity.identity) - -plasmids = [ - neg_control_plasmid, - pos_control_plasmid, - test_device1, - test_device2, - test_device3, - test_device4, - test_device5, - test_device6, -] - -# Day 1: Transformation -culture_plates = activity.primitive_step( - "CulturePlates", - quantity=len(plasmids), - specification=labop.ContainerSpec( - "transformant_strains", - name=f"transformant strains", - queryString="cont:PetriDish", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), - growth_medium=lb_agar_cam, -) - -transformation = activity.primitive_step( - f"Transform", - host=dh5alpha, - dna=plasmids, - selection_medium=lb_agar_cam, - destination=culture_plates.output_pin("samples"), -) -transformation.description = "Incubate overnight (for 16 hour) at 37.0 degree Celsius." - -# Day 2: Pick colonies and culture overnight -culture_container_day1 = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "culture_day_1", - name=f"culture (day 1)", - queryString="cont:CultureTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -pick_colonies = activity.primitive_step( - "PickColonies", - colonies=transformation.output_pin("transformants"), - quantity=2 * len(plasmids), - replicates=2, -) - -overnight_culture = activity.primitive_step( - "Culture", - inoculum=pick_colonies.output_pin("samples"), - replicates=2, - growth_medium=lb_cam, - volume=sbol3.Measure(12, OM.millilitre), # Actually 5-10 ml in the written protocol - duration=sbol3.Measure(16, OM.hour), # Actually 16-18 hours - orbital_shake_speed=sbol3.Measure( - 220, "None" - ), # No unit for RPM or inverse minutes - temperature=sbol3.Measure(37, OM.degree_Celsius), - container=culture_container_day1.output_pin("samples"), -) - -# Day 3 culture -culture_container_day2 = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "culture_day_2", - name=f"culture (day 2)", - queryString="cont:CultureTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - - -back_dilution = activity.primitive_step( - "Dilute", - source=culture_container_day1.output_pin("samples"), - destination=culture_container_day2.output_pin("samples"), - replicates=2, - diluent=lb_cam, - amount=sbol3.Measure(5.0, OM.millilitre), - dilution_factor=uml.LiteralInteger(value=10), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) -back_dilution.description = "(This can be also performed on ice)." - - -# Transfer cultures to a microplate baseline measurement and outgrowth -timepoint_0hrs = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "culture_0_hr_timepoint", - name="cultures (0 hr timepoint)", - queryString="cont:MicrofugeTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -hold = activity.primitive_step( - "HoldOnIce", location=timepoint_0hrs.output_pin("samples") -) -hold.description = "This will prevent cell growth while transferring samples." - -transfer = activity.primitive_step( - "Transfer", - source=culture_container_day2.output_pin("samples"), - destination=timepoint_0hrs.output_pin("samples"), - amount=sbol3.Measure(1, OM.milliliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) -transfer.description = "(This can be also performed on Ice)." - -# Abs measurement -baseline_absorbance = activity.primitive_step( - "MeasureAbsorbance", - samples=timepoint_0hrs.output_pin("samples"), - wavelength=sbol3.Measure(600, OM.nanometer), -) -baseline_absorbance.name = "baseline absorbance of culture (day 2)" - - -conical_tube = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "back_diluted_culture", - name=f"back-diluted culture", - queryString="cont:50mlConicalTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) -conical_tube.description = ( - "The conical tube should be opaque, amber-colored, or covered with foil." -) - -dilution = activity.primitive_step( - "DiluteToTargetOD", - source=culture_container_day2.output_pin("samples"), - destination=conical_tube.output_pin("samples"), - diluent=lb_cam, - amount=sbol3.Measure(12, OM.millilitre), - target_od=sbol3.Measure(0.02, "None"), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) # Dilute to a target OD of 0.2, opaque container -dilution.description = f"(This can be also performed on Ice)." - -embedded_image = activity.primitive_step( - "EmbeddedImage", - image=os.path.join( - os.path.dirname(os.path.realpath(__file__)), - "fig1_standard_protocol.png", - ), - caption="Fig 1: Visual representation of protocol", -) - - -temporary = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "back_diluted_culture_aliquots", - name="back-diluted culture aliquots", - queryString="cont:MicrofugeTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -hold = activity.primitive_step("HoldOnIce", location=temporary.output_pin("samples")) -hold.description = "This will prevent cell growth while transferring samples." - -transfer = activity.primitive_step( - "Transfer", - source=conical_tube.output_pin("samples"), - destination=temporary.output_pin("samples"), - amount=sbol3.Measure(1, OM.milliliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) -transfer.description = "(This can be also performed on Ice)." - -plate1 = activity.primitive_step( - "EmptyContainer", - specification=labop.ContainerSpec( - "plate_1", - name="plate 1", - queryString="cont:Plate96Well", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - - -hold = activity.primitive_step("HoldOnIce", location=plate1.output_pin("samples")) - - -plan = labop.SampleData( - from_samples=temporary.output_pin("samples"), - values=quote( - json.dumps( - { - "1": "A2:D2", - "2": "E2:H2", - "3": "A3:D3", - "4": "E3:H3", - "5": "A4:D4", - "6": "E4:H4", - "7": "A5:D5", - "8": "E5:H5", - "9": "A7:D7", - "10": "E7:H7", - "11": "A8:D8", - "12": "E8:H8", - "13": "A9:D9", - "14": "E9:H9", - "15": "A10:D10", - "16": "E10:H10", - } - ) - ), -) - - -transfer = activity.primitive_step( - "TransferByMap", - source=temporary.output_pin("samples"), - destination=plate1.output_pin("samples"), - amount=sbol3.Measure(200, OM.microliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), - plan=plan, -) -transfer.description = "See also the plate layout below." - -plate_blanks = activity.primitive_step( - "Transfer", - source=[lb_cam], - destination=plate1.output_pin("samples"), - coordinates="A1:H1, A10:H10, A12:H12", - temperature=sbol3.Measure(4, OM.degree_Celsius), - amount=sbol3.Measure(200, OM.microliter), -) -plate_blanks.description = "These samples are blanks." - -embedded_image = activity.primitive_step( - "EmbeddedImage", - image=os.path.join( - os.path.dirname(os.path.realpath(__file__)), "fig2_cell_calibration.png" - ), - caption="Fig 2: Plate layout", -) - -# Possibly display map here -absorbance_plate1 = activity.primitive_step( - "MeasureAbsorbance", - samples=plate1.output_pin("samples"), - wavelength=sbol3.Measure(600, OM.nanometer), -) -absorbance_plate1.name = "0 hr absorbance timepoint" -fluorescence_plate1 = activity.primitive_step( - "MeasureFluorescence", - samples=plate1.output_pin("samples"), - excitationWavelength=sbol3.Measure(488, OM.nanometer), - emissionWavelength=sbol3.Measure(530, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), -) -fluorescence_plate1.name = "0 hr fluorescence timepoint" - -# Cover plate -seal = activity.primitive_step( - "EvaporativeSeal", - location=plate1.output_pin("samples"), - specification=labop.ContainerSpec( - "seal", - queryString="cont:MicroplateAdhesiveSealingFilm", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -# Begin outgrowth -incubate = activity.primitive_step( - "Incubate", - location=conical_tube.output_pin("samples"), - duration=sbol3.Measure(6, OM.hour), - temperature=sbol3.Measure(37, OM.degree_Celsius), - shakingFrequency=sbol3.Measure(220, "None"), -) - -incubate = activity.primitive_step( - "Incubate", - location=plate1.output_pin("samples"), - duration=sbol3.Measure(6, OM.hour), - temperature=sbol3.Measure(37, OM.degree_Celsius), - shakingFrequency=sbol3.Measure(220, "None"), -) - -# Hold on ice to inhibit cell growth -hold = activity.primitive_step("HoldOnIce", location=conical_tube.output_pin("samples")) -hold.description = ( - "This will inhibit cell growth during the subsequent pipetting steps." -) - -hold = activity.primitive_step("HoldOnIce", location=plate1.output_pin("samples")) -hold.description = ( - "This will inhibit cell growth during the subsequent pipetting steps." -) - - -# Take a 6hr timepoint measurement - -plate2 = activity.primitive_step( - "EmptyContainer", - specification=labop.ContainerSpec( - "plate_2", - name="plate 2", - queryString="cont:Plate96Well", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -# Hold on ice - -hold = activity.primitive_step("HoldOnIce", location=plate2.output_pin("samples")) - - -plan = labop.SampleData( - from_samples=conical_tube.output_pin("samples"), - values=quote( - json.dumps( - { - "1": "A2:D2", - "2": "E2:H2", - "3": "A3:D3", - "4": "E3:H3", - "5": "A4:D4", - "6": "E4:H4", - "7": "A5:D5", - "8": "E5:H5", - "9": "A7:D7", - "10": "E7:H7", - "11": "A8:D8", - "12": "E8:H8", - "13": "A9:D9", - "14": "E9:H9", - "15": "A10:D10", - "16": "E10:H10", - } - ) - ), -) - -transfer = activity.primitive_step( - "TransferByMap", - source=conical_tube.output_pin("samples"), - destination=plate2.output_pin("samples"), - amount=sbol3.Measure(200, OM.microliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), - plan=plan, -) -transfer.description = "See the plate layout." - -# Plate the blanks -plate_blanks = activity.primitive_step( - "Transfer", - source=[lb_cam], - destination=plate2.output_pin("samples"), - coordinates="A1:H1, A10:H10, A12:H12", - temperature=sbol3.Measure(4, OM.degree_Celsius), - amount=sbol3.Measure(200, OM.microliter), -) -plate_blanks.description = "These are the blanks." - - -endpoint_absorbance_plate1 = activity.primitive_step( - "MeasureAbsorbance", - samples=plate1.output_pin("samples"), - wavelength=sbol3.Measure(600, OM.nanometer), -) -endpoint_absorbance_plate1.name = "6 hr absorbance timepoint" - -endpoint_fluorescence_plate1 = activity.primitive_step( - "MeasureFluorescence", - samples=plate1.output_pin("samples"), - excitationWavelength=sbol3.Measure(485, OM.nanometer), - emissionWavelength=sbol3.Measure(530, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), -) -endpoint_fluorescence_plate1.name = "6 hr fluorescence timepoint" - -endpoint_absorbance_plate2 = activity.primitive_step( - "MeasureAbsorbance", - samples=plate2.output_pin("samples"), - wavelength=sbol3.Measure(600, OM.nanometer), -) -endpoint_absorbance_plate2.name = "6 hr absorbance timepoint" - -endpoint_fluorescence_plate2 = activity.primitive_step( - "MeasureFluorescence", - samples=plate2.output_pin("samples"), - excitationWavelength=sbol3.Measure(485, OM.nanometer), - emissionWavelength=sbol3.Measure(530, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), -) -endpoint_fluorescence_plate2.name = "6 hr fluorescence timepoint" - -activity.designate_output( - "baseline_absorbance_measurements", - "http://bioprotocols.org/labop#SampleData", - source=baseline_absorbance.output_pin("measurements"), -) -activity.designate_output( - "absorbance_plate1_measurements", - "http://bioprotocols.org/labop#SampleData", - source=absorbance_plate1.output_pin("measurements"), -) -activity.designate_output( - "fluorescence_plate1_measurements", - "http://bioprotocols.org/labop#SampleData", - source=fluorescence_plate1.output_pin("measurements"), -) - -activity.designate_output( - "endpoint_absorbance_plate1_measurements", - "http://bioprotocols.org/labop#SampleData", - source=endpoint_absorbance_plate1.output_pin("measurements"), -) -activity.designate_output( - "endpoint_fluorescence_plate1_measurements", - "http://bioprotocols.org/labop#SampleData", - source=endpoint_fluorescence_plate1.output_pin("measurements"), -) - -activity.designate_output( - "endpoint_absorbance_plate2_measurements", - "http://bioprotocols.org/labop#SampleData", - source=endpoint_absorbance_plate2.output_pin("measurements"), -) -activity.designate_output( - "endpoint_fluorescence_plate2_measurements", - "http://bioprotocols.org/labop#SampleData", - source=endpoint_fluorescence_plate2.output_pin("measurements"), -) - - -agent = sbol3.Agent("test_agent") -ee = ExecutionEngine( - specializations=[MarkdownSpecialization("test_LUDOX_markdown.md")], - failsafe=False, - track_samples=False, - sample_format="json", -) -execution = ee.execute(activity, agent, id="test_execution", parameter_values=[]) -render_kit_coordinates_table(execution) -print(execution.markdown) - -# Dress up the markdown to make it pretty and more readable -execution.markdown = execution.markdown.replace("`_E. coli_", "_`E. coli`_ `") -execution.markdown = execution.markdown.replace(" milliliter", "mL") -execution.markdown = execution.markdown.replace( - " degree Celsius", "\u00B0C" -) # degree symbol -execution.markdown = execution.markdown.replace(" nanometer", "nm") -execution.markdown = execution.markdown.replace(" microliter", "uL") - -filename = "".join(__file__.split(".py")[0].split("/")[-1:]) - -if REGENERATE_ARTIFACTS: - with open(filename + ".md", "w", encoding="utf-8") as f: - f.write(execution.markdown) +def generate_protocol(doc: sbol3.Document, activity: labop.Protocol) -> labop.Protocol: + # Cells and test circuits + dh5alpha = sbol3.Component("dh5alpha", "https://identifiers.org/taxonomy:668369") + dh5alpha.name = "_E. coli_ DH5 alpha competent cells" + doc.add(dh5alpha) + + neg_control_plasmid = sbol3.Component( + "neg_control_plasmid", "http://parts.igem.org/Part:BBa_J428100" + ) + neg_control_plasmid.name = "Negative control" + neg_control_plasmid.description = "BBa_J428100 Kit Plate 1 Well 12M" + + pos_control_plasmid = sbol3.Component( + "pos_control_plasmid", "http://parts.igem.org/Part:BBa_I20270" + ) + pos_control_plasmid.name = "Positive control (I20270)" + pos_control_plasmid.description = "BBa_I20270 Kit Plate 1 Well 1A" + + test_device1 = sbol3.Component( + "test_device1", "http://parts.igem.org/Part:BBa_J364000" + ) + test_device1.name = "Test Device 1 (J364000)" + test_device1.description = "BBa_J364000 Kit Plate 1 Well 1C" + + test_device2 = sbol3.Component( + "test_device2", "http://parts.igem.org/Part:BBa_J364001" + ) + test_device2.name = "Test Device 2 (J364001)" + test_device2.description = "BBa_J364001 Kit Plate 1 Well 1E" + + test_device3 = sbol3.Component( + "test_device3", "http://parts.igem.org/Part:BBa_J364002" + ) + test_device3.name = "Test Device 3 (J364002)" + test_device3.description = "BBa_J364002 Kit Plate 1 Well 1G" + + test_device4 = sbol3.Component( + "test_device4", "http://parts.igem.org/Part:BBa_J364007" + ) + test_device4.name = "Test Device 4 (J364007)" + test_device4.description = "BBa_J364007 Kit Plate 1 Well 1I" + + test_device5 = sbol3.Component( + "test_device5", "http://parts.igem.org/Part:BBa_J364008" + ) + test_device5.name = "Test Device 5 (J364008)" + test_device5.description = "BBa_J364008 Kit Plate 1 Well 1K" + + test_device6 = sbol3.Component( + "test_device6", "http://parts.igem.org/Part:BBa_J364009" + ) + test_device6.name = "Test Device 6 (J364009)" + test_device6.description = "BBa_J364009 Kit Plate 1 Well 1M" + + doc.add(neg_control_plasmid) + doc.add(pos_control_plasmid) + doc.add(test_device1) + doc.add(test_device2) + doc.add(test_device3) + doc.add(test_device4) + doc.add(test_device5) + doc.add(test_device6) + + # Other reagents + lb_cam = sbol3.Component("lb_cam", "") + lb_cam.name = "LB Broth + Chloramphenicol (34 ug/mL)" + + lb_agar_cam = sbol3.Component("lb_agar_cam", "") + lb_agar_cam.name = "LB Agar + Chloramphenicol (34 ug/mL)" + + chloramphenicol = sbol3.Component( + "chloramphenicol", "https://pubchem.ncbi.nlm.nih.gov/compound/5959" + ) + chloramphenicol.name = "Chloramphenicol stock solution (34 mg/mL)" + + ice = sbol3.Component("ice", "") + ice.name = "Ice" + + doc.add(lb_cam) + doc.add(lb_agar_cam) + doc.add(chloramphenicol) + doc.add(ice) + + # Instruments and laboratory equipment + # TODO: instruments should be represented by sbol3.Agent + plate_reader = sbol3.Component("plate_reader", "") + plate_reader.name = "Plate reader" + + shaking_incubator = sbol3.Component("shaking_incubator", "") + shaking_incubator.name = "Shaking incubator" + + doc.add(plate_reader) + doc.add(shaking_incubator) + + activity.name = "Cell measurement protocol" + activity.version = sbol3.TextProperty( + activity, + "http://igem.org/interlab_working_group#Version", + 0, + 1, + [], + "1.2.2", + ) + activity.description = """This year we plan to test protocols that will eventually be automated. For this reason, we will use 96-well plates instead of test tubes for culturing. Consequently, we want to evaluate how the performance of our plate culturing protocol compares to culturing in test tubes (e.g. 10 mL falcon tube) on a global scale. + + At the end of the experiment, you will have two plates to be measured. You will measure both fluorescence and absorbance in each plate. + + Before performing the cell measurements, you need to perform all the calibration measurements. Please do not proceed unless you have completed the calibration protocol. Completion of the calibrations will ensure that you understand the measurement process and that you can take the cell measurements under the same conditions. For consistency and reproducibility, we are requiring all teams to use E. coli K-12 DH5-alpha. If you do not have access to this strain, you can request streaks of the transformed devices from another team near you. If you are absolutely unable to obtain the DH5-alpha strain, you may still participate in the InterLab study by contacting the Engineering Committee (engineering [at] igem [dot] org) to discuss your situation. + + For all below indicated cell measurements, you must use the same type of plates and the same volumes that you used in your calibration protocol. You must also use the same settings (e.g., filters or excitation and emission wavelengths) that you used in your calibration measurements. If you do not use the same type of plates, volumes, and settings, the measurements will not be valid. + + Protocol summary: You will transform the eight devices listed in Table 1 into E. coli K-12 DH5-alpha cells. The next day you will pick two colonies from each transformation (16 total) and use them to inoculate 5 mL overnight cultures (this step is still in tubes). Each of these 16 overnight cultures will be used to inoculate four wells in a 96-well plate (200 microliter each, 4 replicates) and one test tube (12 mL). You will measure how fluorescence and optical density develops over 6 hours by taking measurements at time point 0 hour and at time point 6 hours. Follow the protocol below and the visual instructions in Figure 1 and Figure 2.""" + + activity = doc.find(activity.identity) + + plasmids = [ + neg_control_plasmid, + pos_control_plasmid, + test_device1, + test_device2, + test_device3, + test_device4, + test_device5, + test_device6, + ] + + # Day 1: Transformation + culture_plates = activity.primitive_step( + "CulturePlates", + quantity=len(plasmids), + specification=labop.ContainerSpec( + "transformant_strains", + name=f"transformant strains", + queryString="cont:PetriDish", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + growth_medium=lb_agar_cam, + ) + + transformation = activity.primitive_step( + f"Transform", + host=dh5alpha, + dna=plasmids, + selection_medium=lb_agar_cam, + destination=culture_plates.output_pin("samples"), + ) + transformation.description = ( + "Incubate overnight (for 16 hour) at 37.0 degree Celsius." + ) + + # Day 2: Pick colonies and culture overnight + culture_container_day1 = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "culture_day_1", + name=f"culture (day 1)", + queryString="cont:CultureTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + pick_colonies = activity.primitive_step( + "PickColonies", + colonies=transformation.output_pin("transformants"), + quantity=2 * len(plasmids), + replicates=2, + ) + + overnight_culture = activity.primitive_step( + "Culture", + inoculum=pick_colonies.output_pin("samples"), + replicates=2, + growth_medium=lb_cam, + volume=sbol3.Measure( + 12, OM.millilitre + ), # Actually 5-10 ml in the written protocol + duration=sbol3.Measure(16, OM.hour), # Actually 16-18 hours + orbital_shake_speed=sbol3.Measure( + 220, "None" + ), # No unit for RPM or inverse minutes + temperature=sbol3.Measure(37, OM.degree_Celsius), + container=culture_container_day1.output_pin("samples"), + ) + + # Day 3 culture + culture_container_day2 = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "culture_day_2", + name=f"culture (day 2)", + queryString="cont:CultureTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + back_dilution = activity.primitive_step( + "Dilute", + source=culture_container_day1.output_pin("samples"), + destination=culture_container_day2.output_pin("samples"), + replicates=2, + diluent=lb_cam, + amount=sbol3.Measure(5.0, OM.millilitre), + dilution_factor=uml.LiteralInteger(value=10), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + back_dilution.description = "(This can be also performed on ice)." + + # Transfer cultures to a microplate baseline measurement and outgrowth + timepoint_0hrs = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "culture_0_hr_timepoint", + name="cultures (0 hr timepoint)", + queryString="cont:MicrofugeTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + hold = activity.primitive_step( + "HoldOnIce", location=timepoint_0hrs.output_pin("samples") + ) + hold.description = "This will prevent cell growth while transferring samples." + + transfer = activity.primitive_step( + "Transfer", + source=culture_container_day2.output_pin("samples"), + destination=timepoint_0hrs.output_pin("samples"), + amount=sbol3.Measure(1, OM.milliliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + transfer.description = "(This can be also performed on Ice)." + + # Abs measurement + baseline_absorbance = activity.primitive_step( + "MeasureAbsorbance", + samples=timepoint_0hrs.output_pin("samples"), + wavelength=sbol3.Measure(600, OM.nanometer), + ) + baseline_absorbance.name = "baseline absorbance of culture (day 2)" + + conical_tube = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "back_diluted_culture", + name=f"back-diluted culture", + queryString="cont:50mlConicalTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + conical_tube.description = ( + "The conical tube should be opaque, amber-colored, or covered with foil." + ) + + dilution = activity.primitive_step( + "DiluteToTargetOD", + source=culture_container_day2.output_pin("samples"), + destination=conical_tube.output_pin("samples"), + diluent=lb_cam, + amount=sbol3.Measure(12, OM.millilitre), + target_od=sbol3.Measure(0.02, "None"), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) # Dilute to a target OD of 0.2, opaque container + dilution.description = f"(This can be also performed on Ice)." + + embedded_image = activity.primitive_step( + "EmbeddedImage", + image=os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fig1_standard_protocol.png", + ), + caption="Fig 1: Visual representation of protocol", + ) + + temporary = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "back_diluted_culture_aliquots", + name="back-diluted culture aliquots", + queryString="cont:MicrofugeTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + hold = activity.primitive_step( + "HoldOnIce", location=temporary.output_pin("samples") + ) + hold.description = "This will prevent cell growth while transferring samples." + + transfer = activity.primitive_step( + "Transfer", + source=conical_tube.output_pin("samples"), + destination=temporary.output_pin("samples"), + amount=sbol3.Measure(1, OM.milliliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + transfer.description = "(This can be also performed on Ice)." + + plate1 = activity.primitive_step( + "EmptyContainer", + specification=labop.ContainerSpec( + "plate_1", + name="plate 1", + queryString="cont:Plate96Well", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + hold = activity.primitive_step("HoldOnIce", location=plate1.output_pin("samples")) + + plan = labop.SampleData( + from_samples=temporary.output_pin("samples"), + values=quote( + json.dumps( + { + "1": "A2:D2", + "2": "E2:H2", + "3": "A3:D3", + "4": "E3:H3", + "5": "A4:D4", + "6": "E4:H4", + "7": "A5:D5", + "8": "E5:H5", + "9": "A7:D7", + "10": "E7:H7", + "11": "A8:D8", + "12": "E8:H8", + "13": "A9:D9", + "14": "E9:H9", + "15": "A10:D10", + "16": "E10:H10", + } + ) + ), + ) + + transfer = activity.primitive_step( + "TransferByMap", + source=temporary.output_pin("samples"), + destination=plate1.output_pin("samples"), + amount=sbol3.Measure(200, OM.microliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + plan=plan, + ) + transfer.description = "See also the plate layout below." + + plate_blanks = activity.primitive_step( + "Transfer", + source=[lb_cam], + destination=plate1.output_pin("samples"), + coordinates="A1:H1, A10:H10, A12:H12", + temperature=sbol3.Measure(4, OM.degree_Celsius), + amount=sbol3.Measure(200, OM.microliter), + ) + plate_blanks.description = "These samples are blanks." + + embedded_image = activity.primitive_step( + "EmbeddedImage", + image=os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fig2_cell_calibration.png", + ), + caption="Fig 2: Plate layout", + ) + + # Possibly display map here + absorbance_plate1 = activity.primitive_step( + "MeasureAbsorbance", + samples=plate1.output_pin("samples"), + wavelength=sbol3.Measure(600, OM.nanometer), + ) + absorbance_plate1.name = "0 hr absorbance timepoint" + fluorescence_plate1 = activity.primitive_step( + "MeasureFluorescence", + samples=plate1.output_pin("samples"), + excitationWavelength=sbol3.Measure(488, OM.nanometer), + emissionWavelength=sbol3.Measure(530, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), + ) + fluorescence_plate1.name = "0 hr fluorescence timepoint" + + # Cover plate + seal = activity.primitive_step( + "EvaporativeSeal", + location=plate1.output_pin("samples"), + specification=labop.ContainerSpec( + "seal", + queryString="cont:MicroplateAdhesiveSealingFilm", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + # Begin outgrowth + incubate = activity.primitive_step( + "Incubate", + location=conical_tube.output_pin("samples"), + duration=sbol3.Measure(6, OM.hour), + temperature=sbol3.Measure(37, OM.degree_Celsius), + shakingFrequency=sbol3.Measure(220, "None"), + ) + + incubate = activity.primitive_step( + "Incubate", + location=plate1.output_pin("samples"), + duration=sbol3.Measure(6, OM.hour), + temperature=sbol3.Measure(37, OM.degree_Celsius), + shakingFrequency=sbol3.Measure(220, "None"), + ) + + # Hold on ice to inhibit cell growth + hold = activity.primitive_step( + "HoldOnIce", location=conical_tube.output_pin("samples") + ) + hold.description = ( + "This will inhibit cell growth during the subsequent pipetting steps." + ) + + hold = activity.primitive_step("HoldOnIce", location=plate1.output_pin("samples")) + hold.description = ( + "This will inhibit cell growth during the subsequent pipetting steps." + ) + + # Take a 6hr timepoint measurement + + plate2 = activity.primitive_step( + "EmptyContainer", + specification=labop.ContainerSpec( + "plate_2", + name="plate 2", + queryString="cont:Plate96Well", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + # Hold on ice + + hold = activity.primitive_step("HoldOnIce", location=plate2.output_pin("samples")) + + plan = labop.SampleData( + from_samples=conical_tube.output_pin("samples"), + values=quote( + json.dumps( + { + "1": "A2:D2", + "2": "E2:H2", + "3": "A3:D3", + "4": "E3:H3", + "5": "A4:D4", + "6": "E4:H4", + "7": "A5:D5", + "8": "E5:H5", + "9": "A7:D7", + "10": "E7:H7", + "11": "A8:D8", + "12": "E8:H8", + "13": "A9:D9", + "14": "E9:H9", + "15": "A10:D10", + "16": "E10:H10", + } + ) + ), + ) + + transfer = activity.primitive_step( + "TransferByMap", + source=conical_tube.output_pin("samples"), + destination=plate2.output_pin("samples"), + amount=sbol3.Measure(200, OM.microliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + plan=plan, + ) + transfer.description = "See the plate layout." + + # Plate the blanks + plate_blanks = activity.primitive_step( + "Transfer", + source=[lb_cam], + destination=plate2.output_pin("samples"), + coordinates="A1:H1, A10:H10, A12:H12", + temperature=sbol3.Measure(4, OM.degree_Celsius), + amount=sbol3.Measure(200, OM.microliter), + ) + plate_blanks.description = "These are the blanks." + + endpoint_absorbance_plate1 = activity.primitive_step( + "MeasureAbsorbance", + samples=plate1.output_pin("samples"), + wavelength=sbol3.Measure(600, OM.nanometer), + ) + endpoint_absorbance_plate1.name = "6 hr absorbance timepoint" + + endpoint_fluorescence_plate1 = activity.primitive_step( + "MeasureFluorescence", + samples=plate1.output_pin("samples"), + excitationWavelength=sbol3.Measure(485, OM.nanometer), + emissionWavelength=sbol3.Measure(530, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), + ) + endpoint_fluorescence_plate1.name = "6 hr fluorescence timepoint" + + endpoint_absorbance_plate2 = activity.primitive_step( + "MeasureAbsorbance", + samples=plate2.output_pin("samples"), + wavelength=sbol3.Measure(600, OM.nanometer), + ) + endpoint_absorbance_plate2.name = "6 hr absorbance timepoint" + + endpoint_fluorescence_plate2 = activity.primitive_step( + "MeasureFluorescence", + samples=plate2.output_pin("samples"), + excitationWavelength=sbol3.Measure(485, OM.nanometer), + emissionWavelength=sbol3.Measure(530, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), + ) + endpoint_fluorescence_plate2.name = "6 hr fluorescence timepoint" + + activity.designate_output( + "baseline_absorbance_measurements", + "http://bioprotocols.org/labop#SampleData", + source=baseline_absorbance.output_pin("measurements"), + ) + activity.designate_output( + "absorbance_plate1_measurements", + "http://bioprotocols.org/labop#SampleData", + source=absorbance_plate1.output_pin("measurements"), + ) + activity.designate_output( + "fluorescence_plate1_measurements", + "http://bioprotocols.org/labop#SampleData", + source=fluorescence_plate1.output_pin("measurements"), + ) + + activity.designate_output( + "endpoint_absorbance_plate1_measurements", + "http://bioprotocols.org/labop#SampleData", + source=endpoint_absorbance_plate1.output_pin("measurements"), + ) + activity.designate_output( + "endpoint_fluorescence_plate1_measurements", + "http://bioprotocols.org/labop#SampleData", + source=endpoint_fluorescence_plate1.output_pin("measurements"), + ) + + activity.designate_output( + "endpoint_absorbance_plate2_measurements", + "http://bioprotocols.org/labop#SampleData", + source=endpoint_absorbance_plate2.output_pin("measurements"), + ) + activity.designate_output( + "endpoint_fluorescence_plate2_measurements", + "http://bioprotocols.org/labop#SampleData", + source=endpoint_fluorescence_plate2.output_pin("measurements"), + ) + return activity + + +class InterlabCustomSpecialization(labop.execution.harness.ProtocolSpecialization): + def generate_artifact(self, harness: "ProtocolHarness"): + execution = self.specialization.execution + + render_kit_coordinates_table(execution) + print(execution.markdown) + + # Dress up the markdown to make it pretty and more readable + execution.markdown = execution.markdown.replace("`_E. coli_", "_`E. coli`_ `") + execution.markdown = execution.markdown.replace(" milliliter", "mL") + execution.markdown = execution.markdown.replace( + " degree Celsius", "\u00B0C" + ) # degree symbol + execution.markdown = execution.markdown.replace(" nanometer", "nm") + execution.markdown = execution.markdown.replace(" microliter", "uL") + + super().generate_artifact(harness) + + +if __name__ == "__main__": + harness = labop.execution.harness.ProtocolHarness( + protocol_name="interlab", + clean_output=True, + base_dir=os.path.join( + os.path.dirname(__file__), "out", "out_igem_interlab_endpoint" + ), + entry_point=generate_protocol, + agent="test_agent", + libraries=[ + "liquid_handling", + "plate_handling", + "spectrophotometry", + "sample_arrays", + "culturing", + ], + artifacts=[ + InterlabCustomSpecialization( + specialization=MarkdownSpecialization("test_LUDOX_markdown.md") + ) + ], + execution_kwargs={ + "failsafe": False, + "sample_format": "json", + "track_samples": False, + }, + execution_id="test_execution", + ) + harness.run() diff --git a/examples/protocols/iGEM/interlab-exp1.py b/examples/protocols/iGEM/interlab-exp1.py index 910d9631..591537cb 100644 --- a/examples/protocols/iGEM/interlab-exp1.py +++ b/examples/protocols/iGEM/interlab-exp1.py @@ -2,16 +2,15 @@ http://2018.igem.org/wiki/images/0/09/2018_InterLab_Plate_Reader_Protocol.pdf """ import json -import sys +import os from urllib.parse import quote import sbol3 from tyto import OM import labop +import labop_convert import uml -from labop.execution_engine import ExecutionEngine -from labop_convert import MarkdownSpecialization def render_kit_coordinates_table(ex: labop.ProtocolExecution): @@ -47,597 +46,626 @@ def render_kit_coordinates_table(ex: labop.ProtocolExecution): ex.markdown = ex.markdown[:insert_index] + table + ex.markdown[insert_index:] -if "unittest" in sys.modules: - REGENERATE_ARTIFACTS = False -else: - REGENERATE_ARTIFACTS = True - -filename = "".join(__file__.split(".py")[0].split("/")[-1:]) - -doc = sbol3.Document() -sbol3.set_namespace("http://igem.org/engineering/") - -############################################# -# Import the primitive libraries -print("Importing libraries") -labop.import_library("liquid_handling") -print("... Imported liquid handling") -labop.import_library("plate_handling") -# print('... Imported plate handling') -labop.import_library("spectrophotometry") -print("... Imported spectrophotometry") -labop.import_library("sample_arrays") -print("... Imported sample arrays") -labop.import_library("culturing") -############################################# - +def generate_protocol(doc: sbol3.Document, activity: labop.Protocol) -> labop.Protocol: + # create the materials to be provisioned + dh5alpha = sbol3.Component("dh5alpha", "https://identifiers.org/taxonomy:668369") + dh5alpha.name = "_E. coli_ DH5 alpha competent cells" + doc.add(dh5alpha) -# create the materials to be provisioned -dh5alpha = sbol3.Component("dh5alpha", "https://identifiers.org/taxonomy:668369") -dh5alpha.name = "_E. coli_ DH5 alpha competent cells" -doc.add(dh5alpha) - -neg_control_plasmid = sbol3.Component( - "neg_control_plasmid", "http://parts.igem.org/Part:BBa_J428100" -) -neg_control_plasmid.name = "Negative control 2022" -neg_control_plasmid.description = "BBa_J428100 Kit Plate 1 Well 12M" - -pos_control_plasmid = sbol3.Component( - "pos_control_plasmid", "http://parts.igem.org/Part:BBa_I20270" -) -pos_control_plasmid.name = "Positive control 2018" -pos_control_plasmid.description = "BBa_I20270 Kit Plate 1 Well 1A" - -test_device1 = sbol3.Component("test_device1", "http://parts.igem.org/Part:BBa_J428112") -test_device1.name = "Test Device 1 Exp 1 (Green Device)" -test_device1.description = "BBa_J428112 Kit Plate 1 Well 14C" - -test_device2 = sbol3.Component("test_device2", "http://parts.igem.org/Part:BBa_J428110") -test_device2.name = "Test Device 2 Exp 1 (Red mRFP1 device)" -test_device2.description = "BBa_J428110 Kit Plate 1 Well 12O" - -test_device3 = sbol3.Component("test_device3", "http://parts.igem.org/Part:BBa_J428111") -test_device3.name = "Test Device 3 Exp 1 (Red mCherry device)" -test_device3.description = "BBa_J428111 Kit Plate 1 Well 14A" - -test_device4 = sbol3.Component("test_device4", "http://parts.igem.org/Part:BBa_J428101") -test_device4.name = "Test Device 4 Exp 1 (RiboJ Insulated mCherry device)" -test_device4.description = "BBa_J428101 Kit Plate 1 Well 12I" - -test_device5 = sbol3.Component("test_device5", "http://parts.igem.org/Part:BBa_J428108") -test_device5.name = "Test Device 5 Exp 1 (Dual construct Blue and Red)" -test_device5.description = "BBa_J428108 Kit Plate 1 Well 14E" - -test_device6 = sbol3.Component("test_device6", "http://parts.igem.org/Part:BBa_J428106") -test_device6.name = "Test Device 6 Exp 1 (Dual construct Green and Blue)" -test_device6.description = "BBa_J428106 Kit Plate 1 Well 12G" - -doc.add(neg_control_plasmid) -doc.add(pos_control_plasmid) -doc.add(test_device1) -doc.add(test_device2) -doc.add(test_device3) -doc.add(test_device4) -doc.add(test_device5) -doc.add(test_device6) - -# Other reagents -lb_cam = sbol3.Component("lb_cam", "") -lb_cam.name = "LB Broth + Chloramphenicol (34 ug/mL)" - -lb_agar_cam = sbol3.Component("lb_agar_cam", "") -lb_agar_cam.name = "LB Agar + Chloramphenicol (34 ug/mL)" - -chloramphenicol = sbol3.Component( - "chloramphenicol", "https://pubchem.ncbi.nlm.nih.gov/compound/5959" -) -chloramphenicol.name = "Chloramphenicol stock solution (34 mg/mL)" - -ice = sbol3.Component("ice", "") -ice.name = "Ice" - -doc.add(lb_cam) -doc.add(lb_agar_cam) -doc.add(chloramphenicol) -doc.add(ice) - -# Instruments and laboratory equipment -# TODO: instruments should be represented by sbol3.Agent -plate_reader = sbol3.Component("plate_reader", "") -plate_reader.name = "Plate reader" - -shaking_incubator = sbol3.Component("shaking_incubator", "") -shaking_incubator.name = "Shaking incubator" - -doc.add(plate_reader) -doc.add(shaking_incubator) - -################################################################ -activity = labop.Protocol("interlab") -activity.name = "Testing the three color calibration protocol" -activity.version = sbol3.TextProperty( - activity, "http://igem.org/interlab_working_group#Version", 0, 1, [], "1.1b" -) -activity.description = """In this experiment, your team will measure the fluorescence of six devices that encode either a single fluorescence protein (blue, green, or red) or two fluorescence proteins encoded in two transcriptional units. You will calibrate the fluorescence of these devices to the three calibrant dyes and you will calibrate the optical density of the culture to the cell density calibrant. - -This experiment aims to assess the lab-to-lab reproducibility of the new three color calibration protocol. We will test if it works well for calibrating the fluorescence in cells that express one single fluorescent protein and for cells expressing two different fluorescent proteins at the same time. - -Before performing the cell measurements, you need to perform all the calibration measurements. Please do not proceed unless you have completed the calibration protocol. Completion of the calibrations will ensure that you understand the measurement process and that you can take the cell measurements under the same conditions. For consistency and reproducibility, we are requiring all teams to use E. coli K-12 DH5-alpha. If you do not have access to this strain, you can request streaks of the transformed devices from another team near you. If you are absolutely unable to obtain the DH5-alpha strain, you may still participate in the InterLab study by contacting the Engineering Committee (engineering [at] igem [dot] org) to discuss your situation. - -For all below indicated cell measurements, you must use the same type of plates and the same volumes that you used in your calibration protocol. You must also use the same settings (e.g., filters or excitation and emission wavelengths) that you used in your calibration measurements. If you do not use the same type of plates, volumes, and settings, the measurements will not be valid. - -Protocol summary: You will transform the eight devices listed in Table 1 into E. coli K-12 DH5-alpha cells. The next day you will pick two colonies from each transformation (16 total) and use them to inoculate 5 mL overnight cultures (this step is still in tubes). Each of these 16 overnight cultures will be used to inoculate four wells in a 96-well plate (200 microliter each, 4 replicates) and one test tube (12 mL). You will measure how fluorescence and optical density develops over 6 hours by taking measurements at time point 0 hour and at time point 6 hours. Follow the protocol below and the visual instructions in Figure 1 and Figure 2.""" - -doc.add(activity) -activity = doc.find(activity.identity) - -plasmids = [ - neg_control_plasmid, - pos_control_plasmid, - test_device1, - test_device2, - test_device3, - test_device4, - test_device5, - test_device6, -] - -# Day 1: Transformation -culture_plates = activity.primitive_step( - "CulturePlates", - quantity=len(plasmids), - specification=labop.ContainerSpec( - "transformant_strains", - name=f"transformant strains", - queryString="cont:PetriDish", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), - growth_medium=lb_agar_cam, -) - -transformation = activity.primitive_step( - f"Transform", - host=dh5alpha, - dna=plasmids, - selection_medium=lb_agar_cam, - destination=culture_plates.output_pin("samples"), -) -transformation.description = "Incubate overnight (for 16 hour) at 37.0 degree Celsius." - -# Day 2: Pick colonies and culture overnight -culture_container_day1 = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "culture_day_1", - name=f"culture (day 1)", - queryString="cont:CultureTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -pick_colonies = activity.primitive_step( - "PickColonies", - colonies=transformation.output_pin("transformants"), - quantity=2 * len(plasmids), - replicates=2, -) - -overnight_culture = activity.primitive_step( - "Culture", - inoculum=pick_colonies.output_pin("samples"), - replicates=2, - growth_medium=lb_cam, - volume=sbol3.Measure(5, OM.millilitre), # Actually 5-10 ml in the written protocol - duration=sbol3.Measure(16, OM.hour), # Actually 16-18 hours - orbital_shake_speed=sbol3.Measure( - 220, "None" - ), # No unit for RPM or inverse minutes - temperature=sbol3.Measure(37, OM.degree_Celsius), - container=culture_container_day1.output_pin("samples"), -) - -# Day 3 culture -culture_container_day2 = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "culture_day_2", - name=f"culture (day 2)", - queryString="cont:CultureTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - - -back_dilution = activity.primitive_step( - "Dilute", - source=culture_container_day1.output_pin("samples"), - destination=culture_container_day2.output_pin("samples"), - replicates=2, - diluent=lb_cam, - amount=sbol3.Measure(5.0, OM.millilitre), - dilution_factor=uml.LiteralInteger(value=10), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) -back_dilution.description = "(This can be also performed on ice)." - -# Transfer cultures to a microplate baseline measurement and outgrowth -timepoint_0hrs = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "culture_0hr_timepoint", - name="cultures (0 hr timepoint)", - queryString="cont:MicrofugeTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -hold = activity.primitive_step( - "HoldOnIce", location=timepoint_0hrs.output_pin("samples") -) -hold.description = "This will prevent cell growth while transferring samples." - -transfer = activity.primitive_step( - "Transfer", - source=culture_container_day2.output_pin("samples"), - destination=timepoint_0hrs.output_pin("samples"), - amount=sbol3.Measure(1, OM.milliliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) -transfer.description = "(This can be also performed on Ice)." - -# Abs measurement -baseline_absorbance = activity.primitive_step( - "MeasureAbsorbance", - samples=timepoint_0hrs.output_pin("samples"), - wavelength=sbol3.Measure(600, OM.nanometer), -) -baseline_absorbance.name = "baseline absorbance of culture (day 2)" - - -conical_tube = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "back_diluted_culture", - name=f"back-diluted culture", - queryString="cont:50mlConicalTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) -conical_tube.description = ( - "The conical tube should be opaque, amber-colored, or covered with foil." -) - -dilution = activity.primitive_step( - "DiluteToTargetOD", - source=culture_container_day2.output_pin("samples"), - destination=conical_tube.output_pin("samples"), - diluent=lb_cam, - amount=sbol3.Measure(12, OM.millilitre), - target_od=sbol3.Measure(0.02, "None"), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) # Dilute to a target OD of 0.2, opaque container -dilution.description = f"(This can be also performed on Ice)." - -embedded_image = activity.primitive_step( - "EmbeddedImage", - image="Exp1_2_protocol_published.png", - caption="Fig 1: Visual representation of protocol", -) - - -temporary = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "back_diluted_culture_aliquots", - name="back-diluted culture aliquots", - queryString="cont:MicrofugeTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -hold = activity.primitive_step("HoldOnIce", location=temporary.output_pin("samples")) -hold.description = "This will prevent cell growth while transferring samples." - -transfer = activity.primitive_step( - "Transfer", - source=conical_tube.output_pin("samples"), - destination=temporary.output_pin("samples"), - amount=sbol3.Measure(1, OM.milliliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) -transfer.description = "(This can be also performed on Ice)." - -plate1 = activity.primitive_step( - "EmptyContainer", - specification=labop.ContainerSpec( - "plate_1", - name="plate 1", - queryString="cont:Plate96Well", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - - -hold = activity.primitive_step("HoldOnIce", location=plate1.output_pin("samples")) - - -plan = labop.SampleData( - from_samples=temporary.output_pin("samples"), - values=quote( - json.dumps( - { - "1": "A2:D2", - "2": "E2:H2", - "3": "A3:D3", - "4": "E3:H3", - "5": "A4:D4", - "6": "E4:H4", - "7": "A5:D5", - "8": "E5:H5", - "9": "A7:D7", - "10": "E7:H7", - "11": "A8:D8", - "12": "E8:H8", - "13": "A9:D9", - "14": "E9:H9", - "15": "A10:D10", - "16": "E10:H10", - } - ) - ), -) - - -transfer = activity.primitive_step( - "TransferByMap", - source=temporary.output_pin("samples"), - destination=plate1.output_pin("samples"), - amount=sbol3.Measure(200, OM.microliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), - plan=plan, -) -transfer.description = "See also the plate layout below." - -plate_blanks = activity.primitive_step( - "Transfer", - source=[lb_cam], - destination=plate1.output_pin("samples"), - coordinates="A1:H1, A10:H10, A12:H12", - temperature=sbol3.Measure(4, OM.degree_Celsius), - amount=sbol3.Measure(200, OM.microliter), -) -plate_blanks.description = "These samples are blanks." - -embedded_image = activity.primitive_step( - "EmbeddedImage", - image="fig2_cell_calibration.png", - caption="Fig 2: Plate layout", -) - - -# Possibly display map here -absorbance_plate1 = activity.primitive_step( - "MeasureAbsorbance", - samples=plate1.output_pin("samples"), - wavelength=sbol3.Measure(600, OM.nanometer), -) -absorbance_plate1.name = "0 hr absorbance timepoint" -fluorescence_plate1 = activity.primitive_step( - "MeasureFluorescence", - samples=plate1.output_pin("samples"), - excitationWavelength=sbol3.Measure(488, OM.nanometer), - emissionWavelength=sbol3.Measure(530, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), -) -fluorescence_plate1.name = "0 hr green fluorescence timepoint" - -fluorescence_blue_plate1 = activity.primitive_step( - "MeasureFluorescence", - samples=plate1.output_pin("samples"), - excitationWavelength=sbol3.Measure(405, OM.nanometer), - emissionWavelength=sbol3.Measure(450, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(50, OM.nanometer), -) -fluorescence_blue_plate1.name = "0 hr blue fluorescence timepoint" - -fluorescence_red_plate1 = activity.primitive_step( - "MeasureFluorescence", - samples=plate1.output_pin("samples"), - excitationWavelength=sbol3.Measure(561, OM.nanometer), - emissionWavelength=sbol3.Measure(610, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(20, OM.nanometer), -) -fluorescence_red_plate1.name = "0 hr red fluorescence timepoint" - - -# Begin outgrowth -incubate = activity.primitive_step( - "Incubate", - location=conical_tube.output_pin("samples"), - duration=sbol3.Measure(6, OM.hour), - temperature=sbol3.Measure(37, OM.degree_Celsius), - shakingFrequency=sbol3.Measure(220, "None"), -) - - -# Hold on ice to inhibit cell growth -hold = activity.primitive_step("HoldOnIce", location=conical_tube.output_pin("samples")) -hold.description = ( - "This will inhibit cell growth during the subsequent pipetting steps." -) - -# Take a 6hr timepoint measurement -plate2 = activity.primitive_step( - "EmptyContainer", - specification=labop.ContainerSpec( - "plate_2", - name="plate 2", - queryString="cont:Plate96Well", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -# Hold on ice -hold = activity.primitive_step("HoldOnIce", location=plate2.output_pin("samples")) - - -plan = labop.SampleData( - from_samples=conical_tube.output_pin("samples"), - values=quote( - json.dumps( - { - "1": "A2:D2", - "2": "E2:H2", - "3": "A3:D3", - "4": "E3:H3", - "5": "A4:D4", - "6": "E4:H4", - "7": "A5:D5", - "8": "E5:H5", - "9": "A7:D7", - "10": "E7:H7", - "11": "A8:D8", - "12": "E8:H8", - "13": "A9:D9", - "14": "E9:H9", - "15": "A10:D10", - "16": "E10:H10", - } - ) - ), -) - -transfer = activity.primitive_step( - "TransferByMap", - source=conical_tube.output_pin("samples"), - destination=plate2.output_pin("samples"), - amount=sbol3.Measure(200, OM.microliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), - plan=plan, -) -transfer.description = "See the plate layout." - -# Plate the blanks -plate_blanks = activity.primitive_step( - "Transfer", - source=[lb_cam], - destination=plate2.output_pin("samples"), - coordinates="A1:H1, A10:H10, A12:H12", - temperature=sbol3.Measure(4, OM.degree_Celsius), - amount=sbol3.Measure(200, OM.microliter), -) -plate_blanks.description = "These are the blanks." - - -endpoint_absorbance_plate2 = activity.primitive_step( - "MeasureAbsorbance", - samples=plate2.output_pin("samples"), - wavelength=sbol3.Measure(600, OM.nanometer), -) -endpoint_absorbance_plate2.name = "6 hr absorbance timepoint" - -endpoint_fluorescence_plate2 = activity.primitive_step( - "MeasureFluorescence", - samples=plate2.output_pin("samples"), - excitationWavelength=sbol3.Measure(485, OM.nanometer), - emissionWavelength=sbol3.Measure(530, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), -) -endpoint_fluorescence_plate2.name = "6 hr green fluorescence timepoint" - -endpoint_fluorescence_blue_plate2 = activity.primitive_step( - "MeasureFluorescence", - samples=plate2.output_pin("samples"), - excitationWavelength=sbol3.Measure(405, OM.nanometer), - emissionWavelength=sbol3.Measure(450, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(50, OM.nanometer), -) -endpoint_fluorescence_blue_plate2.name = "6 hr blue fluorescence timepoint" - -endpoint_fluorescence_red_plate2 = activity.primitive_step( - "MeasureFluorescence", - samples=plate2.output_pin("samples"), - excitationWavelength=sbol3.Measure(561, OM.nanometer), - emissionWavelength=sbol3.Measure(610, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(20, OM.nanometer), -) -endpoint_fluorescence_red_plate2.name = "6 hr red fluorescence timepoint" - - -activity.designate_output( - "baseline_absorbance_measurements", - "http://bioprotocols.org/labop#SampleData", - source=baseline_absorbance.output_pin("measurements"), -) -activity.designate_output( - "absorbance_plate1_measurements", - "http://bioprotocols.org/labop#SampleData", - source=absorbance_plate1.output_pin("measurements"), -) -activity.designate_output( - "fluorescence_plate1_measurements", - "http://bioprotocols.org/labop#SampleData", - source=fluorescence_plate1.output_pin("measurements"), -) -activity.designate_output( - "fluorescence_blue_plate1_measurements", - "http://bioprotocols.org/labop#SampleData", - source=fluorescence_blue_plate1.output_pin("measurements"), -) -activity.designate_output( - "fluorescence_red_plate1_measurements", - "http://bioprotocols.org/labop#SampleData", - source=fluorescence_red_plate1.output_pin("measurements"), -) - - -activity.designate_output( - "endpoint_absorbance_plate2_measurements", - "http://bioprotocols.org/labop#SampleData", - source=endpoint_absorbance_plate2.output_pin("measurements"), -) -activity.designate_output( - "endpoint_fluorescence_plate2_measurements", - "http://bioprotocols.org/labop#SampleData", - source=endpoint_fluorescence_plate2.output_pin("measurements"), -) -activity.designate_output( - "endpoint_fluorescence_blue_plate2_measurements", - "http://bioprotocols.org/labop#SampleData", - source=endpoint_fluorescence_blue_plate2.output_pin("measurements"), -) -activity.designate_output( - "endpoint_fluorescence_red_plate2_measurements", - "http://bioprotocols.org/labop#SampleData", - source=endpoint_fluorescence_red_plate2.output_pin("measurements"), -) - -agent = sbol3.Agent("test_agent") -ee = ExecutionEngine( - specializations=[MarkdownSpecialization("test_LUDOX_markdown.md")], - failsafe=False, - sample_format="json", - track_samples=False, -) -execution = ee.execute(activity, agent, id="test_execution", parameter_values=[]) -render_kit_coordinates_table(execution) -print(execution.markdown) - -# Dress up the markdown to make it pretty and more readable -execution.markdown = execution.markdown.replace("`_E. coli_", "_`E. coli`_ `") -execution.markdown = execution.markdown.replace(" milliliter", "mL") -execution.markdown = execution.markdown.replace( - " degree Celsius", "\u00B0C" -) # degree symbol -execution.markdown = execution.markdown.replace(" nanometer", "nm") -execution.markdown = execution.markdown.replace(" microliter", "uL") - -if REGENERATE_ARTIFACTS: - with open(filename + ".md", "w", encoding="utf-8") as f: - f.write(execution.markdown) + neg_control_plasmid = sbol3.Component( + "neg_control_plasmid", "http://parts.igem.org/Part:BBa_J428100" + ) + neg_control_plasmid.name = "Negative control 2022" + neg_control_plasmid.description = "BBa_J428100 Kit Plate 1 Well 12M" + + pos_control_plasmid = sbol3.Component( + "pos_control_plasmid", "http://parts.igem.org/Part:BBa_I20270" + ) + pos_control_plasmid.name = "Positive control 2018" + pos_control_plasmid.description = "BBa_I20270 Kit Plate 1 Well 1A" + + test_device1 = sbol3.Component( + "test_device1", "http://parts.igem.org/Part:BBa_J428112" + ) + test_device1.name = "Test Device 1 Exp 1 (Green Device)" + test_device1.description = "BBa_J428112 Kit Plate 1 Well 14C" + + test_device2 = sbol3.Component( + "test_device2", "http://parts.igem.org/Part:BBa_J428110" + ) + test_device2.name = "Test Device 2 Exp 1 (Red mRFP1 device)" + test_device2.description = "BBa_J428110 Kit Plate 1 Well 12O" + + test_device3 = sbol3.Component( + "test_device3", "http://parts.igem.org/Part:BBa_J428111" + ) + test_device3.name = "Test Device 3 Exp 1 (Red mCherry device)" + test_device3.description = "BBa_J428111 Kit Plate 1 Well 14A" + + test_device4 = sbol3.Component( + "test_device4", "http://parts.igem.org/Part:BBa_J428101" + ) + test_device4.name = "Test Device 4 Exp 1 (RiboJ Insulated mCherry device)" + test_device4.description = "BBa_J428101 Kit Plate 1 Well 12I" + + test_device5 = sbol3.Component( + "test_device5", "http://parts.igem.org/Part:BBa_J428108" + ) + test_device5.name = "Test Device 5 Exp 1 (Dual construct Blue and Red)" + test_device5.description = "BBa_J428108 Kit Plate 1 Well 14E" + + test_device6 = sbol3.Component( + "test_device6", "http://parts.igem.org/Part:BBa_J428106" + ) + test_device6.name = "Test Device 6 Exp 1 (Dual construct Green and Blue)" + test_device6.description = "BBa_J428106 Kit Plate 1 Well 12G" + + doc.add(neg_control_plasmid) + doc.add(pos_control_plasmid) + doc.add(test_device1) + doc.add(test_device2) + doc.add(test_device3) + doc.add(test_device4) + doc.add(test_device5) + doc.add(test_device6) + + # Other reagents + lb_cam = sbol3.Component("lb_cam", "") + lb_cam.name = "LB Broth + Chloramphenicol (34 ug/mL)" + + lb_agar_cam = sbol3.Component("lb_agar_cam", "") + lb_agar_cam.name = "LB Agar + Chloramphenicol (34 ug/mL)" + + chloramphenicol = sbol3.Component( + "chloramphenicol", "https://pubchem.ncbi.nlm.nih.gov/compound/5959" + ) + chloramphenicol.name = "Chloramphenicol stock solution (34 mg/mL)" + + ice = sbol3.Component("ice", "") + ice.name = "Ice" + + doc.add(lb_cam) + doc.add(lb_agar_cam) + doc.add(chloramphenicol) + doc.add(ice) + + # Instruments and laboratory equipment + # TODO: instruments should be represented by sbol3.Agent + plate_reader = sbol3.Component("plate_reader", "") + plate_reader.name = "Plate reader" + + shaking_incubator = sbol3.Component("shaking_incubator", "") + shaking_incubator.name = "Shaking incubator" + + doc.add(plate_reader) + doc.add(shaking_incubator) + + ################################################################ + activity.name = "Testing the three color calibration protocol" + activity.version = sbol3.TextProperty( + activity, + "http://igem.org/interlab_working_group#Version", + 0, + 1, + [], + "1.1b", + ) + activity.description = """In this experiment, your team will measure the fluorescence of six devices that encode either a single fluorescence protein (blue, green, or red) or two fluorescence proteins encoded in two transcriptional units. You will calibrate the fluorescence of these devices to the three calibrant dyes and you will calibrate the optical density of the culture to the cell density calibrant. + + This experiment aims to assess the lab-to-lab reproducibility of the new three color calibration protocol. We will test if it works well for calibrating the fluorescence in cells that express one single fluorescent protein and for cells expressing two different fluorescent proteins at the same time. + + Before performing the cell measurements, you need to perform all the calibration measurements. Please do not proceed unless you have completed the calibration protocol. Completion of the calibrations will ensure that you understand the measurement process and that you can take the cell measurements under the same conditions. For consistency and reproducibility, we are requiring all teams to use E. coli K-12 DH5-alpha. If you do not have access to this strain, you can request streaks of the transformed devices from another team near you. If you are absolutely unable to obtain the DH5-alpha strain, you may still participate in the InterLab study by contacting the Engineering Committee (engineering [at] igem [dot] org) to discuss your situation. + + For all below indicated cell measurements, you must use the same type of plates and the same volumes that you used in your calibration protocol. You must also use the same settings (e.g., filters or excitation and emission wavelengths) that you used in your calibration measurements. If you do not use the same type of plates, volumes, and settings, the measurements will not be valid. + + Protocol summary: You will transform the eight devices listed in Table 1 into E. coli K-12 DH5-alpha cells. The next day you will pick two colonies from each transformation (16 total) and use them to inoculate 5 mL overnight cultures (this step is still in tubes). Each of these 16 overnight cultures will be used to inoculate four wells in a 96-well plate (200 microliter each, 4 replicates) and one test tube (12 mL). You will measure how fluorescence and optical density develops over 6 hours by taking measurements at time point 0 hour and at time point 6 hours. Follow the protocol below and the visual instructions in Figure 1 and Figure 2.""" + + activity = doc.find(activity.identity) + + plasmids = [ + neg_control_plasmid, + pos_control_plasmid, + test_device1, + test_device2, + test_device3, + test_device4, + test_device5, + test_device6, + ] + + # Day 1: Transformation + culture_plates = activity.primitive_step( + "CulturePlates", + quantity=len(plasmids), + specification=labop.ContainerSpec( + "transformant_strains", + name=f"transformant strains", + queryString="cont:PetriDish", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + growth_medium=lb_agar_cam, + ) + + transformation = activity.primitive_step( + f"Transform", + host=dh5alpha, + dna=plasmids, + selection_medium=lb_agar_cam, + destination=culture_plates.output_pin("samples"), + ) + transformation.description = ( + "Incubate overnight (for 16 hour) at 37.0 degree Celsius." + ) + + # Day 2: Pick colonies and culture overnight + culture_container_day1 = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "culture_day_1", + name=f"culture (day 1)", + queryString="cont:CultureTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + pick_colonies = activity.primitive_step( + "PickColonies", + colonies=transformation.output_pin("transformants"), + quantity=2 * len(plasmids), + replicates=2, + ) + + overnight_culture = activity.primitive_step( + "Culture", + inoculum=pick_colonies.output_pin("samples"), + replicates=2, + growth_medium=lb_cam, + volume=sbol3.Measure( + 5, OM.millilitre + ), # Actually 5-10 ml in the written protocol + duration=sbol3.Measure(16, OM.hour), # Actually 16-18 hours + orbital_shake_speed=sbol3.Measure( + 220, "None" + ), # No unit for RPM or inverse minutes + temperature=sbol3.Measure(37, OM.degree_Celsius), + container=culture_container_day1.output_pin("samples"), + ) + + # Day 3 culture + culture_container_day2 = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "culture_day_2", + name=f"culture (day 2)", + queryString="cont:CultureTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + back_dilution = activity.primitive_step( + "Dilute", + source=culture_container_day1.output_pin("samples"), + destination=culture_container_day2.output_pin("samples"), + replicates=2, + diluent=lb_cam, + amount=sbol3.Measure(5.0, OM.millilitre), + dilution_factor=uml.LiteralInteger(value=10), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + back_dilution.description = "(This can be also performed on ice)." + + # Transfer cultures to a microplate baseline measurement and outgrowth + timepoint_0hrs = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "culture_0hr_timepoint", + name="cultures (0 hr timepoint)", + queryString="cont:MicrofugeTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + hold = activity.primitive_step( + "HoldOnIce", location=timepoint_0hrs.output_pin("samples") + ) + hold.description = "This will prevent cell growth while transferring samples." + + transfer = activity.primitive_step( + "Transfer", + source=culture_container_day2.output_pin("samples"), + destination=timepoint_0hrs.output_pin("samples"), + amount=sbol3.Measure(1, OM.milliliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + transfer.description = "(This can be also performed on Ice)." + + # Abs measurement + baseline_absorbance = activity.primitive_step( + "MeasureAbsorbance", + samples=timepoint_0hrs.output_pin("samples"), + wavelength=sbol3.Measure(600, OM.nanometer), + ) + baseline_absorbance.name = "baseline absorbance of culture (day 2)" + + conical_tube = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "back_diluted_culture", + name=f"back-diluted culture", + queryString="cont:50mlConicalTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + conical_tube.description = ( + "The conical tube should be opaque, amber-colored, or covered with foil." + ) + + dilution = activity.primitive_step( + "DiluteToTargetOD", + source=culture_container_day2.output_pin("samples"), + destination=conical_tube.output_pin("samples"), + diluent=lb_cam, + amount=sbol3.Measure(12, OM.millilitre), + target_od=sbol3.Measure(0.02, "None"), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) # Dilute to a target OD of 0.2, opaque container + dilution.description = f"(This can be also performed on Ice)." + + embedded_image = activity.primitive_step( + "EmbeddedImage", + image="Exp1_2_protocol_published.png", + caption="Fig 1: Visual representation of protocol", + ) + + temporary = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "back_diluted_culture_aliquots", + name="back-diluted culture aliquots", + queryString="cont:MicrofugeTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + hold = activity.primitive_step( + "HoldOnIce", location=temporary.output_pin("samples") + ) + hold.description = "This will prevent cell growth while transferring samples." + + transfer = activity.primitive_step( + "Transfer", + source=conical_tube.output_pin("samples"), + destination=temporary.output_pin("samples"), + amount=sbol3.Measure(1, OM.milliliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + transfer.description = "(This can be also performed on Ice)." + + plate1 = activity.primitive_step( + "EmptyContainer", + specification=labop.ContainerSpec( + "plate_1", + name="plate 1", + queryString="cont:Plate96Well", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + hold = activity.primitive_step("HoldOnIce", location=plate1.output_pin("samples")) + + plan = labop.SampleData( + from_samples=temporary.output_pin("samples"), + values=quote( + json.dumps( + { + "1": "A2:D2", + "2": "E2:H2", + "3": "A3:D3", + "4": "E3:H3", + "5": "A4:D4", + "6": "E4:H4", + "7": "A5:D5", + "8": "E5:H5", + "9": "A7:D7", + "10": "E7:H7", + "11": "A8:D8", + "12": "E8:H8", + "13": "A9:D9", + "14": "E9:H9", + "15": "A10:D10", + "16": "E10:H10", + } + ) + ), + ) + + transfer = activity.primitive_step( + "TransferByMap", + source=temporary.output_pin("samples"), + destination=plate1.output_pin("samples"), + amount=sbol3.Measure(200, OM.microliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + plan=plan, + ) + transfer.description = "See also the plate layout below." + + plate_blanks = activity.primitive_step( + "Transfer", + source=[lb_cam], + destination=plate1.output_pin("samples"), + coordinates="A1:H1, A10:H10, A12:H12", + temperature=sbol3.Measure(4, OM.degree_Celsius), + amount=sbol3.Measure(200, OM.microliter), + ) + plate_blanks.description = "These samples are blanks." + + embedded_image = activity.primitive_step( + "EmbeddedImage", + image="fig2_cell_calibration.png", + caption="Fig 2: Plate layout", + ) + + # Possibly display map here + absorbance_plate1 = activity.primitive_step( + "MeasureAbsorbance", + samples=plate1.output_pin("samples"), + wavelength=sbol3.Measure(600, OM.nanometer), + ) + absorbance_plate1.name = "0 hr absorbance timepoint" + fluorescence_plate1 = activity.primitive_step( + "MeasureFluorescence", + samples=plate1.output_pin("samples"), + excitationWavelength=sbol3.Measure(488, OM.nanometer), + emissionWavelength=sbol3.Measure(530, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), + ) + fluorescence_plate1.name = "0 hr green fluorescence timepoint" + + fluorescence_blue_plate1 = activity.primitive_step( + "MeasureFluorescence", + samples=plate1.output_pin("samples"), + excitationWavelength=sbol3.Measure(405, OM.nanometer), + emissionWavelength=sbol3.Measure(450, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(50, OM.nanometer), + ) + fluorescence_blue_plate1.name = "0 hr blue fluorescence timepoint" + + fluorescence_red_plate1 = activity.primitive_step( + "MeasureFluorescence", + samples=plate1.output_pin("samples"), + excitationWavelength=sbol3.Measure(561, OM.nanometer), + emissionWavelength=sbol3.Measure(610, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(20, OM.nanometer), + ) + fluorescence_red_plate1.name = "0 hr red fluorescence timepoint" + + # Begin outgrowth + incubate = activity.primitive_step( + "Incubate", + location=conical_tube.output_pin("samples"), + duration=sbol3.Measure(6, OM.hour), + temperature=sbol3.Measure(37, OM.degree_Celsius), + shakingFrequency=sbol3.Measure(220, "None"), + ) + + # Hold on ice to inhibit cell growth + hold = activity.primitive_step( + "HoldOnIce", location=conical_tube.output_pin("samples") + ) + hold.description = ( + "This will inhibit cell growth during the subsequent pipetting steps." + ) + + # Take a 6hr timepoint measurement + plate2 = activity.primitive_step( + "EmptyContainer", + specification=labop.ContainerSpec( + "plate_2", + name="plate 2", + queryString="cont:Plate96Well", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + # Hold on ice + hold = activity.primitive_step("HoldOnIce", location=plate2.output_pin("samples")) + + plan = labop.SampleData( + from_samples=conical_tube.output_pin("samples"), + values=quote( + json.dumps( + { + "1": "A2:D2", + "2": "E2:H2", + "3": "A3:D3", + "4": "E3:H3", + "5": "A4:D4", + "6": "E4:H4", + "7": "A5:D5", + "8": "E5:H5", + "9": "A7:D7", + "10": "E7:H7", + "11": "A8:D8", + "12": "E8:H8", + "13": "A9:D9", + "14": "E9:H9", + "15": "A10:D10", + "16": "E10:H10", + } + ) + ), + ) + + transfer = activity.primitive_step( + "TransferByMap", + source=conical_tube.output_pin("samples"), + destination=plate2.output_pin("samples"), + amount=sbol3.Measure(200, OM.microliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + plan=plan, + ) + transfer.description = "See the plate layout." + + # Plate the blanks + plate_blanks = activity.primitive_step( + "Transfer", + source=[lb_cam], + destination=plate2.output_pin("samples"), + coordinates="A1:H1, A10:H10, A12:H12", + temperature=sbol3.Measure(4, OM.degree_Celsius), + amount=sbol3.Measure(200, OM.microliter), + ) + plate_blanks.description = "These are the blanks." + + endpoint_absorbance_plate2 = activity.primitive_step( + "MeasureAbsorbance", + samples=plate2.output_pin("samples"), + wavelength=sbol3.Measure(600, OM.nanometer), + ) + endpoint_absorbance_plate2.name = "6 hr absorbance timepoint" + + endpoint_fluorescence_plate2 = activity.primitive_step( + "MeasureFluorescence", + samples=plate2.output_pin("samples"), + excitationWavelength=sbol3.Measure(485, OM.nanometer), + emissionWavelength=sbol3.Measure(530, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), + ) + endpoint_fluorescence_plate2.name = "6 hr green fluorescence timepoint" + + endpoint_fluorescence_blue_plate2 = activity.primitive_step( + "MeasureFluorescence", + samples=plate2.output_pin("samples"), + excitationWavelength=sbol3.Measure(405, OM.nanometer), + emissionWavelength=sbol3.Measure(450, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(50, OM.nanometer), + ) + endpoint_fluorescence_blue_plate2.name = "6 hr blue fluorescence timepoint" + + endpoint_fluorescence_red_plate2 = activity.primitive_step( + "MeasureFluorescence", + samples=plate2.output_pin("samples"), + excitationWavelength=sbol3.Measure(561, OM.nanometer), + emissionWavelength=sbol3.Measure(610, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(20, OM.nanometer), + ) + endpoint_fluorescence_red_plate2.name = "6 hr red fluorescence timepoint" + + activity.designate_output( + "baseline_absorbance_measurements", + "http://bioprotocols.org/labop#SampleData", + source=baseline_absorbance.output_pin("measurements"), + ) + activity.designate_output( + "absorbance_plate1_measurements", + "http://bioprotocols.org/labop#SampleData", + source=absorbance_plate1.output_pin("measurements"), + ) + activity.designate_output( + "fluorescence_plate1_measurements", + "http://bioprotocols.org/labop#SampleData", + source=fluorescence_plate1.output_pin("measurements"), + ) + activity.designate_output( + "fluorescence_blue_plate1_measurements", + "http://bioprotocols.org/labop#SampleData", + source=fluorescence_blue_plate1.output_pin("measurements"), + ) + activity.designate_output( + "fluorescence_red_plate1_measurements", + "http://bioprotocols.org/labop#SampleData", + source=fluorescence_red_plate1.output_pin("measurements"), + ) + + activity.designate_output( + "endpoint_absorbance_plate2_measurements", + "http://bioprotocols.org/labop#SampleData", + source=endpoint_absorbance_plate2.output_pin("measurements"), + ) + activity.designate_output( + "endpoint_fluorescence_plate2_measurements", + "http://bioprotocols.org/labop#SampleData", + source=endpoint_fluorescence_plate2.output_pin("measurements"), + ) + activity.designate_output( + "endpoint_fluorescence_blue_plate2_measurements", + "http://bioprotocols.org/labop#SampleData", + source=endpoint_fluorescence_blue_plate2.output_pin("measurements"), + ) + activity.designate_output( + "endpoint_fluorescence_red_plate2_measurements", + "http://bioprotocols.org/labop#SampleData", + source=endpoint_fluorescence_red_plate2.output_pin("measurements"), + ) + return activity + + +class InterlabCustomSpecialization(labop.execution.harness.ProtocolSpecialization): + def generate_artifact(self, harness: "ProtocolHarness"): + execution = self.specialization.execution + + render_kit_coordinates_table(execution) + print(execution.markdown) + + # Dress up the markdown to make it pretty and more readable + execution.markdown = execution.markdown.replace("`_E. coli_", "_`E. coli`_ `") + execution.markdown = execution.markdown.replace(" milliliter", "mL") + execution.markdown = execution.markdown.replace( + " degree Celsius", "\u00B0C" + ) # degree symbol + execution.markdown = execution.markdown.replace(" nanometer", "nm") + execution.markdown = execution.markdown.replace(" microliter", "uL") + + super().generate_artifact(harness) + + +if __name__ == "__main__": + harness = labop.execution.harness.ProtocolHarness( + protocol_name="interlab", + clean_output=True, + base_dir=os.path.join(os.path.dirname(__file__), "out", "out_igem_example1"), + entry_point=generate_protocol, + agent="test_agent", + libraries=[ + "liquid_handling", + "plate_handling", + "spectrophotometry", + "sample_arrays", + "culturing", + ], + artifacts=[ + InterlabCustomSpecialization( + specialization=labop_convert.MarkdownSpecialization( + "test_LUDOX_markdown.md" + ) + ) + ], + execution_kwargs={ + "failsafe": False, + "sample_format": "json", + "track_samples": False, + }, + execution_id="test_execution", + ) + harness.run() diff --git a/examples/protocols/iGEM/interlab-exp1_MI.py b/examples/protocols/iGEM/interlab-exp1_MI.py index 32006875..5b16f325 100644 --- a/examples/protocols/iGEM/interlab-exp1_MI.py +++ b/examples/protocols/iGEM/interlab-exp1_MI.py @@ -10,7 +10,7 @@ import labop import uml -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop_convert.markdown.markdown_specialization import MarkdownSpecialization filename = "".join(__file__.split(".py")[0].split("/")[-1:]) diff --git a/examples/protocols/iGEM/interlab-exp2.py b/examples/protocols/iGEM/interlab-exp2.py index 5495c8a6..9fe46c0a 100644 --- a/examples/protocols/iGEM/interlab-exp2.py +++ b/examples/protocols/iGEM/interlab-exp2.py @@ -2,16 +2,15 @@ http://2018.igem.org/wiki/images/0/09/2018_InterLab_Plate_Reader_Protocol.pdf """ import json -import sys +import os from urllib.parse import quote import sbol3 from tyto import OM import labop +import labop_convert import uml -from labop.execution_engine import ExecutionEngine -from labop_convert import MarkdownSpecialization def render_kit_coordinates_table(ex: labop.ProtocolExecution): @@ -47,604 +46,623 @@ def render_kit_coordinates_table(ex: labop.ProtocolExecution): ex.markdown = ex.markdown[:insert_index] + table + ex.markdown[insert_index:] -if "unittest" in sys.modules: - REGENERATE_ARTIFACTS = False -else: - REGENERATE_ARTIFACTS = True - -filename = "".join(__file__.split(".py")[0].split("/")[-1:]) - -doc = sbol3.Document() -sbol3.set_namespace("http://igem.org/engineering/") - -############################################# -# Import the primitive libraries -print("Importing libraries") -labop.import_library("liquid_handling") -print("... Imported liquid handling") -labop.import_library("plate_handling") -# print('... Imported plate handling') -labop.import_library("spectrophotometry") -print("... Imported spectrophotometry") -labop.import_library("sample_arrays") -print("... Imported sample arrays") -labop.import_library("culturing") -############################################# - - -# create the materials to be provisioned -dh5alpha = sbol3.Component("dh5alpha", "https://identifiers.org/taxonomy:668369") -dh5alpha.name = "_E. coli_ DH5 alpha" -doc.add(dh5alpha) - -lb_cam = sbol3.Component("lb_cam", "") -lb_cam.name = "LB Broth+chloramphenicol" -doc.add(lb_cam) - -chloramphenicol = sbol3.Component( - "chloramphenicol", "https://pubchem.ncbi.nlm.nih.gov/compound/5959" -) -chloramphenicol.name = "chloramphenicol" -doc.add(chloramphenicol) - - -neg_control_plasmid = sbol3.Component( - "neg_control_plasmid", "http://parts.igem.org/Part:BBa_J428100" -) -neg_control_plasmid.name = "Negative control 2022" -neg_control_plasmid.description = "BBa_J428100 Kit Plate 1 Well 12M" - -pos_control_green_plasmid = sbol3.Component( - "pos_control_green_plasmid", "http://parts.igem.org/Part:BBa_J428112" -) -pos_control_green_plasmid.name = "Positive control 2022 Green" -pos_control_green_plasmid.description = ( - "3_Colors_ins_K2656022 BBa_J428112 Kit Plate 1 Well 14C" -) - -pos_control_red_plasmid = sbol3.Component( - "pos_control_red_plasmid", "http://parts.igem.org/Part:BBa_J428101" -) -pos_control_red_plasmid.name = "Positive control red (mCherry) Exp 2" -pos_control_red_plasmid.description = "BBa_J428101 Kit Plate 1 Well 12I" - -test_device1 = sbol3.Component("test_device1", "http://parts.igem.org/Part:BBa_J428106") -test_device1.name = "Test Device 1 Exp 2 (Dual construct Green and Blue)" -test_device1.description = "BBa_J428106 Kit Plate 1 Well 12G" - -test_device2 = sbol3.Component("test_device2", "http://parts.igem.org/Part:BBa_J428107") -test_device2.name = "Test Device 2 Exp 2 (Dual construct Green and Red)" -test_device2.description = "BBa_J428107 Kit Plate 1 Well 3L" - -test_device3 = sbol3.Component("test_device3", "http://parts.igem.org/Part:BBa_J428105") -test_device3.name = "Test Device 3 Exp 2 (Dual construct Red and Blue)" -test_device3.description = "BBa_J428105 Kit Plate 1 Well 5J" - -test_device4 = sbol3.Component("test_device4", "http://parts.igem.org/Part:BBa_J428108") -test_device4.name = "Test Device 4 Exp 2 (Dual construct Blue and Red)" -test_device4.description = "BBa_J428108 Kit Plate 1 Well 14E" - -test_device5 = sbol3.Component("test_device5", "http://parts.igem.org/Part:BBa_J428104") -test_device5.name = "Test Device 5 Exp 2 (Dual construct Red and Green)" -test_device5.description = "DC_R_ins_K2656022 BBa_J428104 Kit Plate 1 Well 5L" - -doc.add(neg_control_plasmid) -doc.add(pos_control_green_plasmid) -doc.add(pos_control_red_plasmid) -doc.add(test_device1) -doc.add(test_device2) -doc.add(test_device3) -doc.add(test_device4) -doc.add(test_device5) - - -activity = labop.Protocol("interlab") -activity.name = "Using the three color calibration protocol: Does the order of transcritional units influence their expression strength?" -activity.version = sbol3.TextProperty( - activity, "http://igem.org/interlab_working_group#Version", 0, 1, [], "1.0b" -) -activity.description = """In this experiment, your team will measure the fluorescence of six devices that encode two fluorescence proteins in two transcriptional units. The devices differ in the order of the transcriptional units. You will calibrate the fluorescence of these devices to the calibrant dyes and the optical density of the culture to the cell density calibrant. - -This experiment aims to assess the lab-to-lab reproducibility of the three color calibration protocol when two fluorescent proteins are expressed in the same cell. Besides this technical question, it also adresses a fundamental synthetic biology question: does the order of the transcritional units (that encode for the two different fluorescent proteins) on the devices influence their expression levels?""" - -doc.add(activity) -activity = doc.find(activity.identity) - -plasmids = [ - neg_control_plasmid, - pos_control_green_plasmid, - pos_control_red_plasmid, - test_device1, - test_device2, - test_device3, - test_device4, - test_device5, -] - -# Day 1: Transformation -culture_plates = activity.primitive_step( - "CulturePlates", - quantity=len(plasmids), - specification=labop.ContainerSpec( - "transformant_strains", - name=f"transformant strains", - queryString="cont:PetriDish", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), - growth_medium=lb_cam, -) - -transformation = activity.primitive_step( - f"Transform", - host=dh5alpha, - dna=plasmids, - selection_medium=lb_cam, - destination=culture_plates.output_pin("samples"), -) - -# Day 2: Pick colonies and culture overnight -culture_container_day1 = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "culture_day_1", - name=f"culture (day 1)", - queryString="cont:CultureTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -overnight_culture = activity.primitive_step( - "Culture", - inoculum=transformation.output_pin("transformants"), - replicates=2, - growth_medium=lb_cam, - volume=sbol3.Measure(5, OM.millilitre), # Actually 5-10 ml in the written protocol - duration=sbol3.Measure(16, OM.hour), # Actually 16-18 hours - orbital_shake_speed=sbol3.Measure( - 220, "None" - ), # No unit for RPM or inverse minutes - temperature=sbol3.Measure(37, OM.degree_Celsius), - container=culture_container_day1.output_pin("samples"), -) - -# Day 3 culture -culture_container_day2 = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "culture_day_2", - name=f"culture (day 2)", - queryString="cont:CultureTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - - -back_dilution = activity.primitive_step( - "Dilute", - source=culture_container_day1.output_pin("samples"), - destination=culture_container_day2.output_pin("samples"), - replicates=2, - diluent=lb_cam, - amount=sbol3.Measure(5.0, OM.millilitre), - dilution_factor=uml.LiteralInteger(value=10), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) - -# Transfer cultures to a microplate baseline measurement and outgrowth -timepoint_0hrs = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "culture_0hr_timepoint", - name="cultures (0 hr timepoint)", - queryString="cont:MicrofugeTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -hold = activity.primitive_step( - "Hold", - location=timepoint_0hrs.output_pin("samples"), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) -hold.description = "This will prevent cell growth while transferring samples." - -transfer = activity.primitive_step( - "Transfer", - source=culture_container_day2.output_pin("samples"), - destination=timepoint_0hrs.output_pin("samples"), - amount=sbol3.Measure(1, OM.milliliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) - -baseline_absorbance = activity.primitive_step( - "MeasureAbsorbance", - samples=timepoint_0hrs.output_pin("samples"), - wavelength=sbol3.Measure(600, OM.nanometer), -) -baseline_absorbance.name = "baseline absorbance of culture (day 2)" - - -conical_tube = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "back_diluted_culture", - name=f"back-diluted culture", - queryString="cont:50mlConicalTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) -conical_tube.description = ( - "The conical tube should be opaque, amber-colored, or covered with foil." -) - -dilution = activity.primitive_step( - "DiluteToTargetOD", - source=culture_container_day2.output_pin("samples"), - destination=conical_tube.output_pin("samples"), - diluent=lb_cam, - amount=sbol3.Measure(12, OM.millilitre), - target_od=sbol3.Measure(0.02, "None"), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) # Dilute to a target OD of 0.2, opaque container -dilution.description = " Use the provided Excel sheet to calculate this dilution. Reliability of the dilution upon Abs600 measurement: should stay between 0.1-0.9" - -embedded_image = activity.primitive_step( - "EmbeddedImage", - image="/Users/bbartley/Dev/git/sd2/labop/fig1_cell_calibration.png", - caption="Figure 1: Cell Calibration", -) - - -temporary = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "back_diluted_culture_aliquots", - name="back-diluted culture aliquots", - queryString="cont:MicrofugeTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -hold = activity.primitive_step( - "Hold", - location=temporary.output_pin("samples"), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) -hold.description = "This will prevent cell growth while transferring samples." - -transfer = activity.primitive_step( - "Transfer", - source=conical_tube.output_pin("samples"), - destination=temporary.output_pin("samples"), - amount=sbol3.Measure(1, OM.milliliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) - -plate1 = activity.primitive_step( - "EmptyContainer", - specification=labop.ContainerSpec( - "plate_1", - name="plate 1", - queryString="cont:Plate96Well", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - - -hold = activity.primitive_step( - "Hold", - location=plate1.output_pin("samples"), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) - - -plan = labop.SampleData( - from_samples=timepoint_0hrs.output_pin("samples"), - values=quote( - json.dumps( - { - "1": "A2:D2", - "2": "E2:H2", - "3": "A3:D3", - "4": "E3:H3", - "5": "A4:D4", - "6": "E4:H4", - "7": "A5:D5", - "8": "E5:H5", - "9": "A7:D7", - "10": "E7:H7", - "11": "A8:D8", - "12": "E8:H8", - "13": "A9:D9", - "14": "E9:H9", - "15": "A10:D10", - "16": "E10:H10", - } - ) - ), -) - - -transfer = activity.primitive_step( - "TransferByMap", - source=timepoint_0hrs.output_pin("samples"), - destination=plate1.output_pin("samples"), - amount=sbol3.Measure(100, OM.microliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), - plan=plan, -) -transfer.description = "See also the plate layout below." - -plate_blanks = activity.primitive_step( - "Transfer", - source=[lb_cam], - destination=plate1.output_pin("samples"), - coordinates="A1:H1, A10:H10, A12:H12", - temperature=sbol3.Measure(4, OM.degree_Celsius), - amount=sbol3.Measure(100, OM.microliter), -) -plate_blanks.description = "These samples are blanks." - -embedded_image = activity.primitive_step( - "EmbeddedImage", - image="/Users/bbartley/Dev/git/sd2/labop/fig2_cell_calibration.png", - caption="Figure 2: Cell Calibration", -) - - -# Possibly display map here -absorbance_plate1 = activity.primitive_step( - "MeasureAbsorbance", - samples=plate1.output_pin("samples"), - wavelength=sbol3.Measure(600, OM.nanometer), -) -absorbance_plate1.name = "0 hr absorbance timepoint" -fluorescence_plate1 = activity.primitive_step( - "MeasureFluorescence", - samples=plate1.output_pin("samples"), - excitationWavelength=sbol3.Measure(488, OM.nanometer), - emissionWavelength=sbol3.Measure(530, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), -) -fluorescence_plate1.name = "0 hr green fluorescence timepoint" - -fluorescence_blue_plate1 = activity.primitive_step( - "MeasureFluorescence", - samples=plate1.output_pin("samples"), - excitationWavelength=sbol3.Measure(405, OM.nanometer), - emissionWavelength=sbol3.Measure(450, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(50, OM.nanometer), -) -fluorescence_blue_plate1.name = "0 hr blue fluorescence timepoint" - -fluorescence_red_plate1 = activity.primitive_step( - "MeasureFluorescence", - samples=plate1.output_pin("samples"), - excitationWavelength=sbol3.Measure(561, OM.nanometer), - emissionWavelength=sbol3.Measure(610, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(20, OM.nanometer), -) -fluorescence_red_plate1.name = "0 hr red fluorescence timepoint" - - -# Begin outgrowth -incubate = activity.primitive_step( - "Incubate", - location=conical_tube.output_pin("samples"), - duration=sbol3.Measure(6, OM.hour), - temperature=sbol3.Measure(37, OM.degree_Celsius), - shakingFrequency=sbol3.Measure(220, "None"), -) - - -# Hold on ice to inhibit cell growth -hold = activity.primitive_step( - "Hold", - location=timepoint_0hrs.output_pin("samples"), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) -hold.description = ( - "This will inhibit cell growth during the subsequent pipetting steps." -) - - -# Take a 6hr timepoint measurement -timepoint_6hrs = activity.primitive_step( - "ContainerSet", - quantity=len(plasmids) * 2, - specification=labop.ContainerSpec( - "timepoint_6hr", - name=f"6hr timepoint", - queryString="cont:MicrofugeTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -plate2 = activity.primitive_step( - "EmptyContainer", - specification=labop.ContainerSpec( - "plate_2", - name="plate 2", - queryString="cont:Plate96Well", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -# Hold on ice -hold = activity.primitive_step( - "Hold", - location=timepoint_6hrs.output_pin("samples"), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) -hold.description = "This will prevent cell growth while transferring samples." - -hold = activity.primitive_step( - "Hold", - location=plate2.output_pin("samples"), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) - - -transfer = activity.primitive_step( - "Transfer", - source=conical_tube.output_pin("samples"), - destination=timepoint_6hrs.output_pin("samples"), - temperature=sbol3.Measure(4, OM.degree_Celsius), - amount=sbol3.Measure(1, OM.milliliter), -) - - -plan = labop.SampleData( - from_samples=timepoint_6hrs.output_pin("samples"), - values=quote( - json.dumps( - { - "1": "A2:D2", - "2": "E2:H2", - "3": "A3:D3", - "4": "E3:H3", - "5": "A4:D4", - "6": "E4:H4", - "7": "A5:D5", - "8": "E5:H5", - "9": "A7:D7", - "10": "E7:H7", - "11": "A8:D8", - "12": "E8:H8", - "13": "A9:D9", - "14": "E9:H9", - "15": "A10:D10", - "16": "E10:H10", - } - ) - ), -) - -transfer = activity.primitive_step( - "TransferByMap", - source=timepoint_6hrs.output_pin("samples"), - destination=plate2.output_pin("samples"), - amount=sbol3.Measure(100, OM.microliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), - plan=plan, -) -transfer.description = "See the plate layout." - -# Plate the blanks -plate_blanks = activity.primitive_step( - "Transfer", - source=[lb_cam], - destination=plate2.output_pin("samples"), - coordinates="A1:H1, A10:H10, A12:H12", - temperature=sbol3.Measure(4, OM.degree_Celsius), - amount=sbol3.Measure(100, OM.microliter), -) -plate_blanks.description = "These are the blanks." - - -endpoint_absorbance_plate2 = activity.primitive_step( - "MeasureAbsorbance", - samples=plate2.output_pin("samples"), - wavelength=sbol3.Measure(600, OM.nanometer), -) -endpoint_absorbance_plate2.name = "6 hr absorbance timepoint" - -endpoint_fluorescence_plate2 = activity.primitive_step( - "MeasureFluorescence", - samples=plate2.output_pin("samples"), - excitationWavelength=sbol3.Measure(485, OM.nanometer), - emissionWavelength=sbol3.Measure(530, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), -) -endpoint_fluorescence_plate2.name = "6 hr green fluorescence timepoint" - -endpoint_fluorescence_blue_plate2 = activity.primitive_step( - "MeasureFluorescence", - samples=plate2.output_pin("samples"), - excitationWavelength=sbol3.Measure(405, OM.nanometer), - emissionWavelength=sbol3.Measure(450, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(50, OM.nanometer), -) -endpoint_fluorescence_blue_plate2.name = "6 hr blue fluorescence timepoint" - -endpoint_fluorescence_red_plate2 = activity.primitive_step( - "MeasureFluorescence", - samples=plate2.output_pin("samples"), - excitationWavelength=sbol3.Measure(561, OM.nanometer), - emissionWavelength=sbol3.Measure(610, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(20, OM.nanometer), -) -endpoint_fluorescence_red_plate2.name = "6 hr red fluorescence timepoint" - - -activity.designate_output( - "baseline_absorbance_measurements", - "http://bioprotocols.org/labop#SampleData", - source=baseline_absorbance.output_pin("measurements"), -) -activity.designate_output( - "absorbance_plate1_measurements", - "http://bioprotocols.org/labop#SampleData", - source=absorbance_plate1.output_pin("measurements"), -) -activity.designate_output( - "fluorescence_plate1_measurements", - "http://bioprotocols.org/labop#SampleData", - source=fluorescence_plate1.output_pin("measurements"), -) -activity.designate_output( - "fluorescence_blue_plate1_measurements", - "http://bioprotocols.org/labop#SampleData", - source=fluorescence_blue_plate1.output_pin("measurements"), -) -activity.designate_output( - "fluorescence_red_plate1_measurements", - "http://bioprotocols.org/labop#SampleData", - source=fluorescence_red_plate1.output_pin("measurements"), -) - - -activity.designate_output( - "endpoint_absorbance_plate2_measurements", - "http://bioprotocols.org/labop#SampleData", - source=endpoint_absorbance_plate2.output_pin("measurements"), -) -activity.designate_output( - "endpoint_fluorescence_plate2_measurements", - "http://bioprotocols.org/labop#SampleData", - source=endpoint_fluorescence_plate2.output_pin("measurements"), -) -activity.designate_output( - "endpoint_fluorescence_blue_plate2_measurements", - "http://bioprotocols.org/labop#SampleData", - source=endpoint_fluorescence_blue_plate2.output_pin("measurements"), -) -activity.designate_output( - "endpoint_fluorescence_red_plate2_measurements", - "http://bioprotocols.org/labop#SampleData", - source=endpoint_fluorescence_red_plate2.output_pin("measurements"), -) - -agent = sbol3.Agent("test_agent") -ee = ExecutionEngine( - specializations=[MarkdownSpecialization("test_LUDOX_markdown.md")], - failsafe=False, - sample_format="json", -) -execution = ee.execute(activity, agent, id="test_execution", parameter_values=[]) - -# Post-process the markdown to add a table that shows where each -# iGEM part is contained in the parts distribution kit -render_kit_coordinates_table(execution) - -print(execution.markdown) -execution.markdown = execution.markdown.replace("`_E. coli_", "_`E. coli`_ `") - -if REGENERATE_ARTIFACTS: - with open(filename + ".md", "w", encoding="utf-8") as f: - f.write(execution.markdown) +def generate_protocol(doc: sbol3.Document, activity: labop.Protocol) -> labop.Protocol: + # create the materials to be provisioned + dh5alpha = sbol3.Component("dh5alpha", "https://identifiers.org/taxonomy:668369") + dh5alpha.name = "_E. coli_ DH5 alpha" + doc.add(dh5alpha) + + lb_cam = sbol3.Component("lb_cam", "") + lb_cam.name = "LB Broth+chloramphenicol" + doc.add(lb_cam) + + chloramphenicol = sbol3.Component( + "chloramphenicol", "https://pubchem.ncbi.nlm.nih.gov/compound/5959" + ) + chloramphenicol.name = "chloramphenicol" + doc.add(chloramphenicol) + + neg_control_plasmid = sbol3.Component( + "neg_control_plasmid", "http://parts.igem.org/Part:BBa_J428100" + ) + neg_control_plasmid.name = "Negative control 2022" + neg_control_plasmid.description = "BBa_J428100 Kit Plate 1 Well 12M" + + pos_control_green_plasmid = sbol3.Component( + "pos_control_green_plasmid", "http://parts.igem.org/Part:BBa_J428112" + ) + pos_control_green_plasmid.name = "Positive control 2022 Green" + pos_control_green_plasmid.description = ( + "3_Colors_ins_K2656022 BBa_J428112 Kit Plate 1 Well 14C" + ) + + pos_control_red_plasmid = sbol3.Component( + "pos_control_red_plasmid", "http://parts.igem.org/Part:BBa_J428101" + ) + pos_control_red_plasmid.name = "Positive control red (mCherry) Exp 2" + pos_control_red_plasmid.description = "BBa_J428101 Kit Plate 1 Well 12I" + + test_device1 = sbol3.Component( + "test_device1", "http://parts.igem.org/Part:BBa_J428106" + ) + test_device1.name = "Test Device 1 Exp 2 (Dual construct Green and Blue)" + test_device1.description = "BBa_J428106 Kit Plate 1 Well 12G" + + test_device2 = sbol3.Component( + "test_device2", "http://parts.igem.org/Part:BBa_J428107" + ) + test_device2.name = "Test Device 2 Exp 2 (Dual construct Green and Red)" + test_device2.description = "BBa_J428107 Kit Plate 1 Well 3L" + + test_device3 = sbol3.Component( + "test_device3", "http://parts.igem.org/Part:BBa_J428105" + ) + test_device3.name = "Test Device 3 Exp 2 (Dual construct Red and Blue)" + test_device3.description = "BBa_J428105 Kit Plate 1 Well 5J" + + test_device4 = sbol3.Component( + "test_device4", "http://parts.igem.org/Part:BBa_J428108" + ) + test_device4.name = "Test Device 4 Exp 2 (Dual construct Blue and Red)" + test_device4.description = "BBa_J428108 Kit Plate 1 Well 14E" + + test_device5 = sbol3.Component( + "test_device5", "http://parts.igem.org/Part:BBa_J428104" + ) + test_device5.name = "Test Device 5 Exp 2 (Dual construct Red and Green)" + test_device5.description = "DC_R_ins_K2656022 BBa_J428104 Kit Plate 1 Well 5L" + + doc.add(neg_control_plasmid) + doc.add(pos_control_green_plasmid) + doc.add(pos_control_red_plasmid) + doc.add(test_device1) + doc.add(test_device2) + doc.add(test_device3) + doc.add(test_device4) + doc.add(test_device5) + + activity.name = "Using the three color calibration protocol: Does the order of transcritional units influence their expression strength?" + activity.version = sbol3.TextProperty( + activity, + "http://igem.org/interlab_working_group#Version", + 0, + 1, + [], + "1.0b", + ) + activity.description = """In this experiment, your team will measure the fluorescence of six devices that encode two fluorescence proteins in two transcriptional units. The devices differ in the order of the transcriptional units. You will calibrate the fluorescence of these devices to the calibrant dyes and the optical density of the culture to the cell density calibrant. + + This experiment aims to assess the lab-to-lab reproducibility of the three color calibration protocol when two fluorescent proteins are expressed in the same cell. Besides this technical question, it also adresses a fundamental synthetic biology question: does the order of the transcritional units (that encode for the two different fluorescent proteins) on the devices influence their expression levels?""" + + activity = doc.find(activity.identity) + + plasmids = [ + neg_control_plasmid, + pos_control_green_plasmid, + pos_control_red_plasmid, + test_device1, + test_device2, + test_device3, + test_device4, + test_device5, + ] + + # Day 1: Transformation + culture_plates = activity.primitive_step( + "CulturePlates", + quantity=len(plasmids), + specification=labop.ContainerSpec( + "transformant_strains", + name=f"transformant strains", + queryString="cont:PetriDish", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + growth_medium=lb_cam, + ) + + transformation = activity.primitive_step( + f"Transform", + host=dh5alpha, + dna=plasmids, + selection_medium=lb_cam, + destination=culture_plates.output_pin("samples"), + ) + + # Day 2: Pick colonies and culture overnight + culture_container_day1 = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "culture_day_1", + name=f"culture (day 1)", + queryString="cont:CultureTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + overnight_culture = activity.primitive_step( + "Culture", + inoculum=transformation.output_pin("transformants"), + replicates=2, + growth_medium=lb_cam, + volume=sbol3.Measure( + 5, OM.millilitre + ), # Actually 5-10 ml in the written protocol + duration=sbol3.Measure(16, OM.hour), # Actually 16-18 hours + orbital_shake_speed=sbol3.Measure( + 220, "None" + ), # No unit for RPM or inverse minutes + temperature=sbol3.Measure(37, OM.degree_Celsius), + container=culture_container_day1.output_pin("samples"), + ) + + # Day 3 culture + culture_container_day2 = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "culture_day_2", + name=f"culture (day 2)", + queryString="cont:CultureTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + back_dilution = activity.primitive_step( + "Dilute", + source=culture_container_day1.output_pin("samples"), + destination=culture_container_day2.output_pin("samples"), + replicates=2, + diluent=lb_cam, + amount=sbol3.Measure(5.0, OM.millilitre), + dilution_factor=uml.LiteralInteger(value=10), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + + # Transfer cultures to a microplate baseline measurement and outgrowth + timepoint_0hrs = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "culture_0hr_timepoint", + name="cultures (0 hr timepoint)", + queryString="cont:MicrofugeTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + hold = activity.primitive_step( + "Hold", + location=timepoint_0hrs.output_pin("samples"), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + hold.description = "This will prevent cell growth while transferring samples." + + transfer = activity.primitive_step( + "Transfer", + source=culture_container_day2.output_pin("samples"), + destination=timepoint_0hrs.output_pin("samples"), + amount=sbol3.Measure(1, OM.milliliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + + baseline_absorbance = activity.primitive_step( + "MeasureAbsorbance", + samples=timepoint_0hrs.output_pin("samples"), + wavelength=sbol3.Measure(600, OM.nanometer), + ) + baseline_absorbance.name = "baseline absorbance of culture (day 2)" + + conical_tube = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "back_diluted_culture", + name=f"back-diluted culture", + queryString="cont:50mlConicalTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + conical_tube.description = ( + "The conical tube should be opaque, amber-colored, or covered with foil." + ) + + dilution = activity.primitive_step( + "DiluteToTargetOD", + source=culture_container_day2.output_pin("samples"), + destination=conical_tube.output_pin("samples"), + diluent=lb_cam, + amount=sbol3.Measure(12, OM.millilitre), + target_od=sbol3.Measure(0.02, "None"), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) # Dilute to a target OD of 0.2, opaque container + dilution.description = " Use the provided Excel sheet to calculate this dilution. Reliability of the dilution upon Abs600 measurement: should stay between 0.1-0.9" + + embedded_image = activity.primitive_step( + "EmbeddedImage", + image="/Users/bbartley/Dev/git/sd2/labop/fig1_cell_calibration.png", + caption="Figure 1: Cell Calibration", + ) + + temporary = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "back_diluted_culture_aliquots", + name="back-diluted culture aliquots", + queryString="cont:MicrofugeTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + hold = activity.primitive_step( + "Hold", + location=temporary.output_pin("samples"), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + hold.description = "This will prevent cell growth while transferring samples." + + transfer = activity.primitive_step( + "Transfer", + source=conical_tube.output_pin("samples"), + destination=temporary.output_pin("samples"), + amount=sbol3.Measure(1, OM.milliliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + + plate1 = activity.primitive_step( + "EmptyContainer", + specification=labop.ContainerSpec( + "plate_1", + name="plate 1", + queryString="cont:Plate96Well", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + hold = activity.primitive_step( + "Hold", + location=plate1.output_pin("samples"), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + + plan = labop.SampleData( + from_samples=timepoint_0hrs.output_pin("samples"), + values=quote( + json.dumps( + { + "1": "A2:D2", + "2": "E2:H2", + "3": "A3:D3", + "4": "E3:H3", + "5": "A4:D4", + "6": "E4:H4", + "7": "A5:D5", + "8": "E5:H5", + "9": "A7:D7", + "10": "E7:H7", + "11": "A8:D8", + "12": "E8:H8", + "13": "A9:D9", + "14": "E9:H9", + "15": "A10:D10", + "16": "E10:H10", + } + ) + ), + ) + + transfer = activity.primitive_step( + "TransferByMap", + source=timepoint_0hrs.output_pin("samples"), + destination=plate1.output_pin("samples"), + amount=sbol3.Measure(100, OM.microliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + plan=plan, + ) + transfer.description = "See also the plate layout below." + + plate_blanks = activity.primitive_step( + "Transfer", + source=[lb_cam], + destination=plate1.output_pin("samples"), + coordinates="A1:H1, A10:H10, A12:H12", + temperature=sbol3.Measure(4, OM.degree_Celsius), + amount=sbol3.Measure(100, OM.microliter), + ) + plate_blanks.description = "These samples are blanks." + + embedded_image = activity.primitive_step( + "EmbeddedImage", + image="/Users/bbartley/Dev/git/sd2/labop/fig2_cell_calibration.png", + caption="Figure 2: Cell Calibration", + ) + + # Possibly display map here + absorbance_plate1 = activity.primitive_step( + "MeasureAbsorbance", + samples=plate1.output_pin("samples"), + wavelength=sbol3.Measure(600, OM.nanometer), + ) + absorbance_plate1.name = "0 hr absorbance timepoint" + fluorescence_plate1 = activity.primitive_step( + "MeasureFluorescence", + samples=plate1.output_pin("samples"), + excitationWavelength=sbol3.Measure(488, OM.nanometer), + emissionWavelength=sbol3.Measure(530, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), + ) + fluorescence_plate1.name = "0 hr green fluorescence timepoint" + + fluorescence_blue_plate1 = activity.primitive_step( + "MeasureFluorescence", + samples=plate1.output_pin("samples"), + excitationWavelength=sbol3.Measure(405, OM.nanometer), + emissionWavelength=sbol3.Measure(450, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(50, OM.nanometer), + ) + fluorescence_blue_plate1.name = "0 hr blue fluorescence timepoint" + + fluorescence_red_plate1 = activity.primitive_step( + "MeasureFluorescence", + samples=plate1.output_pin("samples"), + excitationWavelength=sbol3.Measure(561, OM.nanometer), + emissionWavelength=sbol3.Measure(610, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(20, OM.nanometer), + ) + fluorescence_red_plate1.name = "0 hr red fluorescence timepoint" + + # Begin outgrowth + incubate = activity.primitive_step( + "Incubate", + location=conical_tube.output_pin("samples"), + duration=sbol3.Measure(6, OM.hour), + temperature=sbol3.Measure(37, OM.degree_Celsius), + shakingFrequency=sbol3.Measure(220, "None"), + ) + + # Hold on ice to inhibit cell growth + hold = activity.primitive_step( + "Hold", + location=timepoint_0hrs.output_pin("samples"), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + hold.description = ( + "This will inhibit cell growth during the subsequent pipetting steps." + ) + + # Take a 6hr timepoint measurement + timepoint_6hrs = activity.primitive_step( + "ContainerSet", + quantity=len(plasmids) * 2, + specification=labop.ContainerSpec( + "timepoint_6hr", + name=f"6hr timepoint", + queryString="cont:MicrofugeTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + plate2 = activity.primitive_step( + "EmptyContainer", + specification=labop.ContainerSpec( + "plate_2", + name="plate 2", + queryString="cont:Plate96Well", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + # Hold on ice + hold = activity.primitive_step( + "Hold", + location=timepoint_6hrs.output_pin("samples"), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + hold.description = "This will prevent cell growth while transferring samples." + + hold = activity.primitive_step( + "Hold", + location=plate2.output_pin("samples"), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + + transfer = activity.primitive_step( + "Transfer", + source=conical_tube.output_pin("samples"), + destination=timepoint_6hrs.output_pin("samples"), + temperature=sbol3.Measure(4, OM.degree_Celsius), + amount=sbol3.Measure(1, OM.milliliter), + ) + + plan = labop.SampleData( + from_samples=timepoint_6hrs.output_pin("samples"), + values=quote( + json.dumps( + { + "1": "A2:D2", + "2": "E2:H2", + "3": "A3:D3", + "4": "E3:H3", + "5": "A4:D4", + "6": "E4:H4", + "7": "A5:D5", + "8": "E5:H5", + "9": "A7:D7", + "10": "E7:H7", + "11": "A8:D8", + "12": "E8:H8", + "13": "A9:D9", + "14": "E9:H9", + "15": "A10:D10", + "16": "E10:H10", + } + ) + ), + ) + + transfer = activity.primitive_step( + "TransferByMap", + source=timepoint_6hrs.output_pin("samples"), + destination=plate2.output_pin("samples"), + amount=sbol3.Measure(100, OM.microliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + plan=plan, + ) + transfer.description = "See the plate layout." + + # Plate the blanks + plate_blanks = activity.primitive_step( + "Transfer", + source=[lb_cam], + destination=plate2.output_pin("samples"), + coordinates="A1:H1, A10:H10, A12:H12", + temperature=sbol3.Measure(4, OM.degree_Celsius), + amount=sbol3.Measure(100, OM.microliter), + ) + plate_blanks.description = "These are the blanks." + + endpoint_absorbance_plate2 = activity.primitive_step( + "MeasureAbsorbance", + samples=plate2.output_pin("samples"), + wavelength=sbol3.Measure(600, OM.nanometer), + ) + endpoint_absorbance_plate2.name = "6 hr absorbance timepoint" + + endpoint_fluorescence_plate2 = activity.primitive_step( + "MeasureFluorescence", + samples=plate2.output_pin("samples"), + excitationWavelength=sbol3.Measure(485, OM.nanometer), + emissionWavelength=sbol3.Measure(530, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), + ) + endpoint_fluorescence_plate2.name = "6 hr green fluorescence timepoint" + + endpoint_fluorescence_blue_plate2 = activity.primitive_step( + "MeasureFluorescence", + samples=plate2.output_pin("samples"), + excitationWavelength=sbol3.Measure(405, OM.nanometer), + emissionWavelength=sbol3.Measure(450, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(50, OM.nanometer), + ) + endpoint_fluorescence_blue_plate2.name = "6 hr blue fluorescence timepoint" + + endpoint_fluorescence_red_plate2 = activity.primitive_step( + "MeasureFluorescence", + samples=plate2.output_pin("samples"), + excitationWavelength=sbol3.Measure(561, OM.nanometer), + emissionWavelength=sbol3.Measure(610, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(20, OM.nanometer), + ) + endpoint_fluorescence_red_plate2.name = "6 hr red fluorescence timepoint" + + activity.designate_output( + "baseline_absorbance_measurements", + "http://bioprotocols.org/labop#SampleData", + source=baseline_absorbance.output_pin("measurements"), + ) + activity.designate_output( + "absorbance_plate1_measurements", + "http://bioprotocols.org/labop#SampleData", + source=absorbance_plate1.output_pin("measurements"), + ) + activity.designate_output( + "fluorescence_plate1_measurements", + "http://bioprotocols.org/labop#SampleData", + source=fluorescence_plate1.output_pin("measurements"), + ) + activity.designate_output( + "fluorescence_blue_plate1_measurements", + "http://bioprotocols.org/labop#SampleData", + source=fluorescence_blue_plate1.output_pin("measurements"), + ) + activity.designate_output( + "fluorescence_red_plate1_measurements", + "http://bioprotocols.org/labop#SampleData", + source=fluorescence_red_plate1.output_pin("measurements"), + ) + + activity.designate_output( + "endpoint_absorbance_plate2_measurements", + "http://bioprotocols.org/labop#SampleData", + source=endpoint_absorbance_plate2.output_pin("measurements"), + ) + activity.designate_output( + "endpoint_fluorescence_plate2_measurements", + "http://bioprotocols.org/labop#SampleData", + source=endpoint_fluorescence_plate2.output_pin("measurements"), + ) + activity.designate_output( + "endpoint_fluorescence_blue_plate2_measurements", + "http://bioprotocols.org/labop#SampleData", + source=endpoint_fluorescence_blue_plate2.output_pin("measurements"), + ) + activity.designate_output( + "endpoint_fluorescence_red_plate2_measurements", + "http://bioprotocols.org/labop#SampleData", + source=endpoint_fluorescence_red_plate2.output_pin("measurements"), + ) + + return activity + + +class InterlabCustomSpecialization(labop.execution.harness.ProtocolSpecialization): + def generate_artifact(self, harness: "ProtocolHarness"): + execution = self.specialization.execution + + # Post-process the markdown to add a table that shows where each + # iGEM part is contained in the parts distribution kit + render_kit_coordinates_table(execution) + + print(execution.markdown) + execution.markdown = execution.markdown.replace("`_E. coli_", "_`E. coli`_ `") + super().generate_artifact(harness) + + +if __name__ == "__main__": + harness = labop.execution.harness.ProtocolHarness( + protocol_name="interlab", + clean_output=True, + base_dir=os.path.join(os.path.dirname(__file__), "out", "out_igem_example2"), + entry_point=generate_protocol, + agent="test_agent", + libraries=[ + "liquid_handling", + "plate_handling", + "spectrophotometry", + "sample_arrays", + "culturing", + ], + artifacts=[ + InterlabCustomSpecialization( + specialization=labop_convert.MarkdownSpecialization( + "test_LUDOX_markdown.md" + ) + ) + ], + execution_kwargs={ + "failsafe": False, + "sample_format": "json", + "track_samples": False, + }, + execution_id="test_execution", + ) + harness.run() diff --git a/examples/protocols/iGEM/interlab-exp2_MI.py b/examples/protocols/iGEM/interlab-exp2_MI.py index cb9f1d94..3888de8e 100644 --- a/examples/protocols/iGEM/interlab-exp2_MI.py +++ b/examples/protocols/iGEM/interlab-exp2_MI.py @@ -10,7 +10,7 @@ import labop import uml -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop_convert.markdown.markdown_specialization import MarkdownSpecialization filename = "".join(__file__.split(".py")[0].split("/")[-1:]) diff --git a/examples/protocols/iGEM/interlab-exp2_from1.py b/examples/protocols/iGEM/interlab-exp2_from1.py index 91118127..f72982c8 100644 --- a/examples/protocols/iGEM/interlab-exp2_from1.py +++ b/examples/protocols/iGEM/interlab-exp2_from1.py @@ -10,7 +10,7 @@ import labop import uml -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop_convert.markdown.markdown_specialization import MarkdownSpecialization filename = "".join(__file__.split(".py")[0].split("/")[-1:]) diff --git a/examples/protocols/iGEM/interlab-exp3_challenge.py b/examples/protocols/iGEM/interlab-exp3_challenge.py index 8306b504..c0f8401f 100644 --- a/examples/protocols/iGEM/interlab-exp3_challenge.py +++ b/examples/protocols/iGEM/interlab-exp3_challenge.py @@ -2,16 +2,15 @@ http://2018.igem.org/wiki/images/0/09/2018_InterLab_Plate_Reader_Protocol.pdf """ import json -import sys +import os from urllib.parse import quote import sbol3 from tyto import OM import labop +import labop_convert import uml -from labop.execution_engine import ExecutionEngine -from labop_convert import MarkdownSpecialization def render_kit_coordinates_table(ex: labop.ProtocolExecution): @@ -47,660 +46,691 @@ def render_kit_coordinates_table(ex: labop.ProtocolExecution): ex.markdown = ex.markdown[:insert_index] + table + ex.markdown[insert_index:] -if "unittest" in sys.modules: - REGENERATE_ARTIFACTS = False -else: - REGENERATE_ARTIFACTS = True - -filename = "".join(__file__.split(".py")[0].split("/")[-1:]) - -doc = sbol3.Document() -sbol3.set_namespace("http://igem.org/engineering/") - -############################################# -# Import the primitive libraries -print("Importing libraries") -labop.import_library("liquid_handling") -print("... Imported liquid handling") -labop.import_library("plate_handling") -# print('... Imported plate handling') -labop.import_library("spectrophotometry") -print("... Imported spectrophotometry") -labop.import_library("sample_arrays") -print("... Imported sample arrays") -labop.import_library("culturing") -############################################# +def generate_protocol(doc: sbol3.Document, activity: labop.Protocol) -> labop.Protocol: + # Cells and test circuits + dh5alpha = sbol3.Component("dh5alpha", "https://identifiers.org/taxonomy:668369") + dh5alpha.name = "_E. coli_ DH5 alpha competent cells" + doc.add(dh5alpha) + neg_control_plasmid = sbol3.Component( + "neg_control_plasmid", "http://parts.igem.org/Part:BBa_J428100" + ) + neg_control_plasmid.name = "Negative control" + neg_control_plasmid.description = "BBa_J428100 Kit Plate 1 Well 12M" -# Cells and test circuits -dh5alpha = sbol3.Component("dh5alpha", "https://identifiers.org/taxonomy:668369") -dh5alpha.name = "_E. coli_ DH5 alpha competent cells" -doc.add(dh5alpha) - -neg_control_plasmid = sbol3.Component( - "neg_control_plasmid", "http://parts.igem.org/Part:BBa_J428100" -) -neg_control_plasmid.name = "Negative control" -neg_control_plasmid.description = "BBa_J428100 Kit Plate 1 Well 12M" - -pos_control_plasmid = sbol3.Component( - "pos_control_plasmid", "http://parts.igem.org/Part:BBa_I20270" -) -pos_control_plasmid.name = "Positive control (I20270)" -pos_control_plasmid.description = "BBa_I20270 Kit Plate 1 Well 1A" - -test_device1 = sbol3.Component("test_device1", "http://parts.igem.org/Part:BBa_J364000") -test_device1.name = "Test Device 1 (J364000)" -test_device1.description = "BBa_J364000 Kit Plate 1 Well 1C" - -test_device2 = sbol3.Component("test_device2", "http://parts.igem.org/Part:BBa_J364001") -test_device2.name = "Test Device 2 (J364001)" -test_device2.description = "BBa_J364001 Kit Plate 1 Well 1E" - -test_device3 = sbol3.Component("test_device3", "http://parts.igem.org/Part:BBa_J364002") -test_device3.name = "Test Device 3 (J364002)" -test_device3.description = "BBa_J364002 Kit Plate 1 Well 1G" - -test_device4 = sbol3.Component("test_device4", "http://parts.igem.org/Part:BBa_J364007") -test_device4.name = "Test Device 4 (J364007)" -test_device4.description = "BBa_J364007 Kit Plate 1 Well 1I" - -test_device5 = sbol3.Component("test_device5", "http://parts.igem.org/Part:BBa_J364008") -test_device5.name = "Test Device 5 (J364008)" -test_device5.description = "BBa_J364008 Kit Plate 1 Well 1K" - -test_device6 = sbol3.Component("test_device6", "http://parts.igem.org/Part:BBa_J364009") -test_device6.name = "Test Device 6 (J364009)" -test_device6.description = "BBa_J364009 Kit Plate 1 Well 1M" - -doc.add(neg_control_plasmid) -doc.add(pos_control_plasmid) -doc.add(test_device1) -doc.add(test_device2) -doc.add(test_device3) -doc.add(test_device4) -doc.add(test_device5) -doc.add(test_device6) - -# Other reagents -lb_cam = sbol3.Component("lb_cam", "") -lb_cam.name = "LB Broth + Chloramphenicol (34 ug/mL)" - -lb_agar_cam = sbol3.Component("lb_agar_cam", "") -lb_agar_cam.name = "LB Agar + Chloramphenicol (34 ug/mL)" - -chloramphenicol = sbol3.Component( - "chloramphenicol", "https://pubchem.ncbi.nlm.nih.gov/compound/5959" -) -chloramphenicol.name = "Chloramphenicol stock solution (34 mg/mL)" - -ice = sbol3.Component("ice", "") -ice.name = "Ice" - -doc.add(lb_cam) -doc.add(lb_agar_cam) -doc.add(chloramphenicol) -doc.add(ice) - -# Instruments and laboratory equipment -# TODO: instruments should be represented by sbol3.Agent -plate_reader = sbol3.Component("plate_reader", "") -plate_reader.name = "Plate reader" - -shaking_incubator = sbol3.Component("shaking_incubator", "") -shaking_incubator.name = "Shaking incubator" - -doc.add(plate_reader) -doc.add(shaking_incubator) - - -activity = labop.Protocol("interlab") -activity.name = "Experiment 3 - Cell measurement protocol Challenge" -activity.version = sbol3.TextProperty( - activity, "http://igem.org/interlab_working_group#Version", 0, 1, [], "1.2b" -) -activity.description = """This year we plan to test protocols that will eventually be automated. For this reason, we will use 96-well plates instead of test tubes for culturing. Consequently, we want to evaluate how the performance of our plate culturing protocol compares to culturing in test tubes (e.g. 10 mL falcon tube) on a global scale. This version of the interlab protocol involves 2 hr. time interval measurements and incubation inside a microplate reader/incubator. - -At the end of the experiment, you will have four plates to be measured. You will measure both fluorescence and absorbance in each plate. - -Before performing the cell measurements, you need to perform all the calibration measurements. Please do not proceed unless you have completed the calibration protocol. Completion of the calibrations will ensure that you understand the measurement process and that you can take the cell measurements under the same conditions. For consistency and reproducibility, we are requiring all teams to use E. coli K-12 DH5-alpha. If you do not have access to this strain, you can request streaks of the transformed devices from another team near you. If you are absolutely unable to obtain the DH5-alpha strain, you may still participate in the InterLab study by contacting the Engineering Committee (engineering [at] igem [dot] org) to discuss your situation. - -For all below indicated cell measurements, you must use the same type of plates and the same volumes that you used in your calibration protocol. You must also use the same settings (e.g., filters or excitation and emission wavelengths) that you used in your calibration measurements. If you do not use the same type of plates, volumes, and settings, the measurements will not be valid. - -Protocol summary: UPDATE You will transform the eight devices listed in Table 1 into E. coli K-12 DH5-alpha cells. The next day you will pick two colonies from each transformation (16 total) and use them to inoculate 5 mL overnight cultures (this step is still in tubes). Each of these 16 overnight cultures will be used to inoculate four wells in a 96-well plate (200 microliter each, 4 replicates) and one test tube (12 mL). You will measure how fluorescence and optical density develops over 6 hours by taking measurements at time point 0 hour and at time point 6 hours. Follow the protocol below and the visual instructions in Figure 1 and Figure 2.""" - -doc.add(activity) -activity = doc.find(activity.identity) - -plasmids = [ - neg_control_plasmid, - pos_control_plasmid, - test_device1, - test_device2, - test_device3, - test_device4, - test_device5, - test_device6, -] - -# Day 1: Transformation -culture_plates = activity.primitive_step( - "CulturePlates", - quantity=len(plasmids), - specification=labop.ContainerSpec( - "transformant_strains", - name=f"transformant strains", - queryString="cont:PetriDish", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), - growth_medium=lb_agar_cam, -) - -transformation = activity.primitive_step( - f"Transform", - host=dh5alpha, - dna=plasmids, - selection_medium=lb_agar_cam, - destination=culture_plates.output_pin("samples"), -) -transformation.description = "Incubate overnight (for 16 hour) at 37.0 degree Celsius." - -# Day 2: Pick colonies and culture overnight -culture_container_day1 = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "culture_day_1", - name=f"culture (day 1)", - queryString="cont:CultureTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -pick_colonies = activity.primitive_step( - "PickColonies", - colonies=transformation.output_pin("transformants"), - quantity=2 * len(plasmids), - replicates=2, -) - -overnight_culture = activity.primitive_step( - "Culture", - inoculum=pick_colonies.output_pin("samples"), - replicates=2, - growth_medium=lb_cam, - volume=sbol3.Measure(5, OM.millilitre), # Actually 5-10 ml in the written protocol - duration=sbol3.Measure(16, OM.hour), # Actually 16-18 hours - orbital_shake_speed=sbol3.Measure( - 220, "None" - ), # No unit for RPM or inverse minutes - temperature=sbol3.Measure(37, OM.degree_Celsius), - container=culture_container_day1.output_pin("samples"), -) - -# Day 3 culture -culture_container_day2 = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "culture_day_2", - name=f"culture (day 2)", - queryString="cont:CultureTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - - -back_dilution = activity.primitive_step( - "Dilute", - source=culture_container_day1.output_pin("samples"), - destination=culture_container_day2.output_pin("samples"), - replicates=2, - diluent=lb_cam, - amount=sbol3.Measure(5.0, OM.millilitre), - dilution_factor=uml.LiteralInteger(value=10), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) -back_dilution.description = "(This can be also performed on ice)." - - -# Transfer cultures to a microplate baseline measurement and outgrowth -timepoint_0hrs = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "culture_0hr_timepoint", - name="cultures (0 hr timepoint)", - queryString="cont:MicrofugeTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -hold = activity.primitive_step( - "HoldOnIce", location=timepoint_0hrs.output_pin("samples") -) -hold.description = "This will prevent cell growth while transferring samples." - -transfer = activity.primitive_step( - "Transfer", - source=culture_container_day2.output_pin("samples"), - destination=timepoint_0hrs.output_pin("samples"), - amount=sbol3.Measure(1, OM.milliliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) -transfer.description = "(This can be also performed on Ice)." - -# Abs measurement step 11 -baseline_absorbance = activity.primitive_step( - "MeasureAbsorbance", - samples=timepoint_0hrs.output_pin("samples"), - wavelength=sbol3.Measure(600, OM.nanometer), -) -baseline_absorbance.name = "baseline absorbance of culture (day 2)" - -# Every measurement primitive produces an output pin called `measurements` that must be designated as a protocol output -activity.designate_output( - "baseline_absorbance_measurements", - "http://bioprotocols.org/labop#SampleData", - source=baseline_absorbance.output_pin("measurements"), -) - - -conical_tube = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "back_diluted_culture", - name=f"back-diluted culture", - queryString="cont:50mlConicalTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) -conical_tube.description = ( - "The conical tube should be opaque, amber-colored, or covered with foil." -) - -dilution = activity.primitive_step( - "DiluteToTargetOD", - source=culture_container_day2.output_pin("samples"), - destination=conical_tube.output_pin("samples"), - diluent=lb_cam, - amount=sbol3.Measure(40, OM.millilitre), - target_od=sbol3.Measure(0.02, "None"), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) # Dilute to a target OD of 0.2, opaque container -dilution.description = f"(This can be also performed on Ice)." - -embedded_image = activity.primitive_step( - "EmbeddedImage", - image="fig1_challenge_protocol.png", - caption="Fig 1: Visual representation of protocol", -) - - -### Aliquot subcultures for timepoint samples -spec = labop.ContainerSpec( - "tube1", - name=f"Tube 1", - queryString="cont:50mlConicalTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, -) -timepoint_subculture1 = activity.primitive_step( - "ContainerSet", quantity=2 * len(plasmids), specification=spec -) -timepoint_subculture1.description = ( - "The conical tubes should be opaque, amber-colored, or covered with foil." -) - -timepoint_subculture2 = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "tube2", - name=f"Tube 2", - queryString="cont:50mlConicalTube", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) -timepoint_subculture2.description = ( - "The conical tubes should be opaque, amber-colored, or covered with foil." -) - -timepoint_subculture3 = activity.primitive_step( - "ContainerSet", - quantity=2 * len(plasmids), - specification=labop.ContainerSpec( - "tube3", - name=f"Tube 3", + pos_control_plasmid = sbol3.Component( + "pos_control_plasmid", "http://parts.igem.org/Part:BBa_I20270" + ) + pos_control_plasmid.name = "Positive control (I20270)" + pos_control_plasmid.description = "BBa_I20270 Kit Plate 1 Well 1A" + + test_device1 = sbol3.Component( + "test_device1", "http://parts.igem.org/Part:BBa_J364000" + ) + test_device1.name = "Test Device 1 (J364000)" + test_device1.description = "BBa_J364000 Kit Plate 1 Well 1C" + + test_device2 = sbol3.Component( + "test_device2", "http://parts.igem.org/Part:BBa_J364001" + ) + test_device2.name = "Test Device 2 (J364001)" + test_device2.description = "BBa_J364001 Kit Plate 1 Well 1E" + + test_device3 = sbol3.Component( + "test_device3", "http://parts.igem.org/Part:BBa_J364002" + ) + test_device3.name = "Test Device 3 (J364002)" + test_device3.description = "BBa_J364002 Kit Plate 1 Well 1G" + + test_device4 = sbol3.Component( + "test_device4", "http://parts.igem.org/Part:BBa_J364007" + ) + test_device4.name = "Test Device 4 (J364007)" + test_device4.description = "BBa_J364007 Kit Plate 1 Well 1I" + + test_device5 = sbol3.Component( + "test_device5", "http://parts.igem.org/Part:BBa_J364008" + ) + test_device5.name = "Test Device 5 (J364008)" + test_device5.description = "BBa_J364008 Kit Plate 1 Well 1K" + + test_device6 = sbol3.Component( + "test_device6", "http://parts.igem.org/Part:BBa_J364009" + ) + test_device6.name = "Test Device 6 (J364009)" + test_device6.description = "BBa_J364009 Kit Plate 1 Well 1M" + + doc.add(neg_control_plasmid) + doc.add(pos_control_plasmid) + doc.add(test_device1) + doc.add(test_device2) + doc.add(test_device3) + doc.add(test_device4) + doc.add(test_device5) + doc.add(test_device6) + + # Other reagents + lb_cam = sbol3.Component("lb_cam", "") + lb_cam.name = "LB Broth + Chloramphenicol (34 ug/mL)" + + lb_agar_cam = sbol3.Component("lb_agar_cam", "") + lb_agar_cam.name = "LB Agar + Chloramphenicol (34 ug/mL)" + + chloramphenicol = sbol3.Component( + "chloramphenicol", "https://pubchem.ncbi.nlm.nih.gov/compound/5959" + ) + chloramphenicol.name = "Chloramphenicol stock solution (34 mg/mL)" + + ice = sbol3.Component("ice", "") + ice.name = "Ice" + + doc.add(lb_cam) + doc.add(lb_agar_cam) + doc.add(chloramphenicol) + doc.add(ice) + + # Instruments and laboratory equipment + # TODO: instruments should be represented by sbol3.Agent + plate_reader = sbol3.Component("plate_reader", "") + plate_reader.name = "Plate reader" + + shaking_incubator = sbol3.Component("shaking_incubator", "") + shaking_incubator.name = "Shaking incubator" + + doc.add(plate_reader) + doc.add(shaking_incubator) + + activity.name = "Experiment 3 - Cell measurement protocol Challenge" + activity.version = sbol3.TextProperty( + activity, + "http://igem.org/interlab_working_group#Version", + 0, + 1, + [], + "1.2b", + ) + activity.description = """This year we plan to test protocols that will eventually be automated. For this reason, we will use 96-well plates instead of test tubes for culturing. Consequently, we want to evaluate how the performance of our plate culturing protocol compares to culturing in test tubes (e.g. 10 mL falcon tube) on a global scale. This version of the interlab protocol involves 2 hr. time interval measurements and incubation inside a microplate reader/incubator. + + At the end of the experiment, you will have four plates to be measured. You will measure both fluorescence and absorbance in each plate. + + Before performing the cell measurements, you need to perform all the calibration measurements. Please do not proceed unless you have completed the calibration protocol. Completion of the calibrations will ensure that you understand the measurement process and that you can take the cell measurements under the same conditions. For consistency and reproducibility, we are requiring all teams to use E. coli K-12 DH5-alpha. If you do not have access to this strain, you can request streaks of the transformed devices from another team near you. If you are absolutely unable to obtain the DH5-alpha strain, you may still participate in the InterLab study by contacting the Engineering Committee (engineering [at] igem [dot] org) to discuss your situation. + + For all below indicated cell measurements, you must use the same type of plates and the same volumes that you used in your calibration protocol. You must also use the same settings (e.g., filters or excitation and emission wavelengths) that you used in your calibration measurements. If you do not use the same type of plates, volumes, and settings, the measurements will not be valid. + + Protocol summary: UPDATE You will transform the eight devices listed in Table 1 into E. coli K-12 DH5-alpha cells. The next day you will pick two colonies from each transformation (16 total) and use them to inoculate 5 mL overnight cultures (this step is still in tubes). Each of these 16 overnight cultures will be used to inoculate four wells in a 96-well plate (200 microliter each, 4 replicates) and one test tube (12 mL). You will measure how fluorescence and optical density develops over 6 hours by taking measurements at time point 0 hour and at time point 6 hours. Follow the protocol below and the visual instructions in Figure 1 and Figure 2.""" + + activity = doc.find(activity.identity) + + plasmids = [ + neg_control_plasmid, + pos_control_plasmid, + test_device1, + test_device2, + test_device3, + test_device4, + test_device5, + test_device6, + ] + + # Day 1: Transformation + culture_plates = activity.primitive_step( + "CulturePlates", + quantity=len(plasmids), + specification=labop.ContainerSpec( + "transformant_strains", + name=f"transformant strains", + queryString="cont:PetriDish", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + growth_medium=lb_agar_cam, + ) + + transformation = activity.primitive_step( + f"Transform", + host=dh5alpha, + dna=plasmids, + selection_medium=lb_agar_cam, + destination=culture_plates.output_pin("samples"), + ) + transformation.description = ( + "Incubate overnight (for 16 hour) at 37.0 degree Celsius." + ) + + # Day 2: Pick colonies and culture overnight + culture_container_day1 = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "culture_day_1", + name=f"culture (day 1)", + queryString="cont:CultureTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + pick_colonies = activity.primitive_step( + "PickColonies", + colonies=transformation.output_pin("transformants"), + quantity=2 * len(plasmids), + replicates=2, + ) + + overnight_culture = activity.primitive_step( + "Culture", + inoculum=pick_colonies.output_pin("samples"), + replicates=2, + growth_medium=lb_cam, + volume=sbol3.Measure( + 5, OM.millilitre + ), # Actually 5-10 ml in the written protocol + duration=sbol3.Measure(16, OM.hour), # Actually 16-18 hours + orbital_shake_speed=sbol3.Measure( + 220, "None" + ), # No unit for RPM or inverse minutes + temperature=sbol3.Measure(37, OM.degree_Celsius), + container=culture_container_day1.output_pin("samples"), + ) + + # Day 3 culture + culture_container_day2 = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "culture_day_2", + name=f"culture (day 2)", + queryString="cont:CultureTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + back_dilution = activity.primitive_step( + "Dilute", + source=culture_container_day1.output_pin("samples"), + destination=culture_container_day2.output_pin("samples"), + replicates=2, + diluent=lb_cam, + amount=sbol3.Measure(5.0, OM.millilitre), + dilution_factor=uml.LiteralInteger(value=10), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + back_dilution.description = "(This can be also performed on ice)." + + # Transfer cultures to a microplate baseline measurement and outgrowth + timepoint_0hrs = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "culture_0hr_timepoint", + name="cultures (0 hr timepoint)", + queryString="cont:MicrofugeTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + hold = activity.primitive_step( + "HoldOnIce", location=timepoint_0hrs.output_pin("samples") + ) + hold.description = "This will prevent cell growth while transferring samples." + + transfer = activity.primitive_step( + "Transfer", + source=culture_container_day2.output_pin("samples"), + destination=timepoint_0hrs.output_pin("samples"), + amount=sbol3.Measure(1, OM.milliliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + transfer.description = "(This can be also performed on Ice)." + + # Abs measurement step 11 + baseline_absorbance = activity.primitive_step( + "MeasureAbsorbance", + samples=timepoint_0hrs.output_pin("samples"), + wavelength=sbol3.Measure(600, OM.nanometer), + ) + baseline_absorbance.name = "baseline absorbance of culture (day 2)" + + # Every measurement primitive produces an output pin called `measurements` that must be designated as a protocol output + activity.designate_output( + "baseline_absorbance_measurements", + "http://bioprotocols.org/labop#SampleData", + source=baseline_absorbance.output_pin("measurements"), + ) + + conical_tube = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "back_diluted_culture", + name=f"back-diluted culture", + queryString="cont:50mlConicalTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + conical_tube.description = ( + "The conical tube should be opaque, amber-colored, or covered with foil." + ) + + dilution = activity.primitive_step( + "DiluteToTargetOD", + source=culture_container_day2.output_pin("samples"), + destination=conical_tube.output_pin("samples"), + diluent=lb_cam, + amount=sbol3.Measure(40, OM.millilitre), + target_od=sbol3.Measure(0.02, "None"), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) # Dilute to a target OD of 0.2, opaque container + dilution.description = f"(This can be also performed on Ice)." + + embedded_image = activity.primitive_step( + "EmbeddedImage", + image="fig1_challenge_protocol.png", + caption="Fig 1: Visual representation of protocol", + ) + + ### Aliquot subcultures for timepoint samples + spec = labop.ContainerSpec( + "tube1", + name=f"Tube 1", queryString="cont:50mlConicalTube", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) -timepoint_subculture3.description = ( - "The conical tubes should be opaque, amber-colored, or covered with foil." -) - -transfer = activity.primitive_step( - "Transfer", - source=conical_tube.output_pin("samples"), - destination=timepoint_subculture1.output_pin("samples"), - amount=sbol3.Measure(12, OM.milliliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) - -transfer = activity.primitive_step( - "Transfer", - source=conical_tube.output_pin("samples"), - destination=timepoint_subculture2.output_pin("samples"), - amount=sbol3.Measure(12, OM.milliliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) - -transfer = activity.primitive_step( - "Transfer", - source=conical_tube.output_pin("samples"), - destination=timepoint_subculture3.output_pin("samples"), - amount=sbol3.Measure(12, OM.milliliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), -) - -plate1 = activity.primitive_step( - "EmptyContainer", - specification=labop.ContainerSpec( - "plate1", - name="plate 1", - queryString="cont:Plate96Well", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -hold = activity.primitive_step("HoldOnIce", location=plate1.output_pin("samples")) - -plan = labop.SampleData( - from_samples=conical_tube.output_pin("samples"), - values=quote( - json.dumps( - { - "1": "A2:D2", - "2": "E2:H2", - "3": "A3:D3", - "4": "E3:H3", - "5": "A4:D4", - "6": "E4:H4", - "7": "A5:D5", - "8": "E5:H5", - "9": "A7:D7", - "10": "E7:H7", - "11": "A8:D8", - "12": "E8:H8", - "13": "A9:D9", - "14": "E9:H9", - "15": "A10:D10", - "16": "E10:H10", - } - ) - ), -) - - -transfer = activity.primitive_step( - "TransferByMap", - source=conical_tube.output_pin("samples"), - destination=plate1.output_pin("samples"), - amount=sbol3.Measure(200, OM.microliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), - plan=plan, -) -transfer.description = "See the plate layout below." -plate_blanks = activity.primitive_step( - "Transfer", - source=[lb_cam], - destination=plate1.output_pin("samples"), - coordinates="A1:H1, A10:H10, A12:H12", - temperature=sbol3.Measure(4, OM.degree_Celsius), - amount=sbol3.Measure(200, OM.microliter), -) -plate_blanks.description = "These samples are blanks." - -# Display ma here -embedded_image = activity.primitive_step( - "EmbeddedImage", - image="fig2_cell_calibration.png", - caption="Fig 2: Plate layout", -) - -absorbance_plate1 = activity.primitive_step( - "MeasureAbsorbance", - samples=plate1.output_pin("samples"), - wavelength=sbol3.Measure(600, OM.nanometer), -) -absorbance_plate1.name = "0 hr absorbance timepoint" -fluorescence_plate1 = activity.primitive_step( - "MeasureFluorescence", - samples=plate1.output_pin("samples"), - excitationWavelength=sbol3.Measure(488, OM.nanometer), - emissionWavelength=sbol3.Measure(530, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), -) -fluorescence_plate1.name = "0 hr fluorescence timepoint" - -activity.designate_output( - "0_absorbance_measurements", - "http://bioprotocols.org/labop#SampleData", - source=absorbance_plate1.output_pin("measurements"), -) -activity.designate_output( - "0_fluorescence_measurements", - "http://bioprotocols.org/labop#SampleData", - source=fluorescence_plate1.output_pin("measurements"), -) - - -# Cover plate -seal = activity.primitive_step( - "EvaporativeSeal", - location=plate1.output_pin("samples"), - specification=labop.ContainerSpec( - "seal", - queryString="cont:MicroplateAdhesiveSealingFilm", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -############### Hasta aca bien -# Begin outgrowth - -incubate = activity.primitive_step( - "Incubate", - location=plate1.output_pin("samples"), - duration=sbol3.Measure(6, OM.hour), - temperature=sbol3.Measure(37, OM.degree_Celsius), - shakingFrequency=sbol3.Measure(220, "None"), -) - -absorbance_plate1 = activity.primitive_step( - "MeasureAbsorbance", - samples=plate1.output_pin("samples"), - wavelength=sbol3.Measure(600, OM.nanometer), - timepoints=[ - sbol3.Measure(2, OM.hour), - sbol3.Measure(4, OM.hour), - sbol3.Measure(6, OM.hour), - ], -) - -absorbance_plate1.name = "absorbance timepoint" - -fluorescence_plate1 = activity.primitive_step( - "MeasureFluorescence", - samples=plate1.output_pin("samples"), - excitationWavelength=sbol3.Measure(488, OM.nanometer), - emissionWavelength=sbol3.Measure(530, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), - timepoints=[ - sbol3.Measure(2, OM.hour), - sbol3.Measure(4, OM.hour), - sbol3.Measure(6, OM.hour), - ], -) -fluorescence_plate1.name = "fluorescence timepoint" -activity.designate_output( - "246_absorbance_measurements", - "http://bioprotocols.org/labop#SampleData", - source=absorbance_plate1.output_pin("measurements"), -) -activity.designate_output( - "246_fluorescence_measurements", - "http://bioprotocols.org/labop#SampleData", - source=fluorescence_plate1.output_pin("measurements"), -) -# Added to si if it changes the next stap -spec.name = "Tube 1" -incubate = activity.primitive_step( - "Incubate", - location=timepoint_subculture1.output_pin("samples"), - duration=sbol3.Measure(2, OM.hour), - temperature=sbol3.Measure(37, OM.degree_Celsius), - shakingFrequency=sbol3.Measure(220, "None"), -) - -# Hold on ice to inhibit cell growth -hold = activity.primitive_step( - "HoldOnIce", location=timepoint_subculture1.output_pin("samples") -) -hold.description = "Reserve until the end of the experiment for absorbance and fluorescence measurements." - - -incubate = activity.primitive_step( - "Incubate", - location=timepoint_subculture2.output_pin("samples"), - duration=sbol3.Measure(4, OM.hour), - temperature=sbol3.Measure(37, OM.degree_Celsius), - shakingFrequency=sbol3.Measure(220, "None"), -) -hold = activity.primitive_step( - "HoldOnIce", location=timepoint_subculture2.output_pin("samples") -) -hold.description = "Reserve until the end of the experiment for absorbance and fluorescence measurements." - - -incubate = activity.primitive_step( - "Incubate", - location=timepoint_subculture3.output_pin("samples"), - duration=sbol3.Measure(6, OM.hour), - temperature=sbol3.Measure(37, OM.degree_Celsius), - shakingFrequency=sbol3.Measure(220, "None"), -) -hold = activity.primitive_step( - "HoldOnIce", location=timepoint_subculture3.output_pin("samples") -) -hold.description = "Reserve until the end of the experiment for absorbance and fluorescence measurements." - - -plates234 = activity.primitive_step( - "EmptyContainer", - specification=labop.ContainerSpec( - "plates234", - name="Plates 2, 3, and 4", - queryString="cont:Plate96Well", - prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, - ), -) - -plan = labop.SampleData( - from_samples=timepoint_subculture1.output_pin("samples"), - values=quote( - json.dumps( - { - "1": "A2:D2", - "2": "E2:H2", - "3": "A3:D3", - "4": "E3:H3", - "5": "A4:D4", - "6": "E4:H4", - "7": "A5:D5", - "8": "E5:H5", - "9": "A7:D7", - "10": "E7:H7", - "11": "A8:D8", - "12": "E8:H8", - "13": "A9:D9", - "14": "E9:H9", - "15": "A10:D10", - "16": "E10:H10", - } - ) - ), -) - -spec.name = "Tubes 1, 2 and 3" -transfer = activity.primitive_step( - "TransferByMap", - source=timepoint_subculture1.output_pin("samples"), - destination=plates234.output_pin("samples"), - amount=sbol3.Measure(200, OM.microliter), - temperature=sbol3.Measure(4, OM.degree_Celsius), - plan=plan, -) - - -plate_blanks = activity.primitive_step( - "Transfer", - source=[lb_cam], - destination=plates234.output_pin("samples"), - coordinates="A1:H1, A10:H10, A12:H12", - temperature=sbol3.Measure(4, OM.degree_Celsius), - amount=sbol3.Measure(200, OM.microliter), -) -plate_blanks.description = "These samples are blanks." - -absorbance_plate = activity.primitive_step( - "MeasureAbsorbance", - samples=plates234.output_pin("samples"), - wavelength=sbol3.Measure(600, OM.nanometer), -) -absorbance_plate.name = f"absorbance timepoint" -fluorescence_plate = activity.primitive_step( - "MeasureFluorescence", - samples=plates234.output_pin("samples"), - excitationWavelength=sbol3.Measure(488, OM.nanometer), - emissionWavelength=sbol3.Measure(530, OM.nanometer), - emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), -) -fluorescence_plate.name = f"fluorescence timepoint" - -activity.designate_output( - "plates234_absorbance_measurements", - "http://bioprotocols.org/labop#SampleData", - source=absorbance_plate.output_pin("measurements"), -) -activity.designate_output( - "plates234_fluorescence_measurements", - "http://bioprotocols.org/labop#SampleData", - source=fluorescence_plate.output_pin("measurements"), -) - - -agent = sbol3.Agent("test_agent") -ee = ExecutionEngine( - specializations=[MarkdownSpecialization("test_LUDOX_markdown.md")], - failsafe=False, - sample_format="json", - track_samples=False, -) -execution = ee.execute(activity, agent, id="test_execution", parameter_values=[]) -render_kit_coordinates_table(execution) -print(execution.markdown) - -# Dress up the markdown to make it pretty and more readable -execution.markdown = execution.markdown.replace("`_E. coli_", "_`E. coli`_ `") -execution.markdown = execution.markdown.replace(" milliliter", "mL") -execution.markdown = execution.markdown.replace( - " degree Celsius", "\u00B0C" -) # degree symbol -execution.markdown = execution.markdown.replace(" nanometer", "nm") -execution.markdown = execution.markdown.replace(" microliter", "uL") - -if REGENERATE_ARTIFACTS: - with open(filename + ".md", "w", encoding="utf-8") as f: - f.write(execution.markdown) + ) + timepoint_subculture1 = activity.primitive_step( + "ContainerSet", quantity=2 * len(plasmids), specification=spec + ) + timepoint_subculture1.description = ( + "The conical tubes should be opaque, amber-colored, or covered with foil." + ) + + timepoint_subculture2 = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "tube2", + name=f"Tube 2", + queryString="cont:50mlConicalTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + timepoint_subculture2.description = ( + "The conical tubes should be opaque, amber-colored, or covered with foil." + ) + + timepoint_subculture3 = activity.primitive_step( + "ContainerSet", + quantity=2 * len(plasmids), + specification=labop.ContainerSpec( + "tube3", + name=f"Tube 3", + queryString="cont:50mlConicalTube", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + timepoint_subculture3.description = ( + "The conical tubes should be opaque, amber-colored, or covered with foil." + ) + + transfer = activity.primitive_step( + "Transfer", + source=conical_tube.output_pin("samples"), + destination=timepoint_subculture1.output_pin("samples"), + amount=sbol3.Measure(12, OM.milliliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + + transfer = activity.primitive_step( + "Transfer", + source=conical_tube.output_pin("samples"), + destination=timepoint_subculture2.output_pin("samples"), + amount=sbol3.Measure(12, OM.milliliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + + transfer = activity.primitive_step( + "Transfer", + source=conical_tube.output_pin("samples"), + destination=timepoint_subculture3.output_pin("samples"), + amount=sbol3.Measure(12, OM.milliliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + ) + + plate1 = activity.primitive_step( + "EmptyContainer", + specification=labop.ContainerSpec( + "plate1", + name="plate 1", + queryString="cont:Plate96Well", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + hold = activity.primitive_step("HoldOnIce", location=plate1.output_pin("samples")) + + plan = labop.SampleData( + from_samples=conical_tube.output_pin("samples"), + values=quote( + json.dumps( + { + "1": "A2:D2", + "2": "E2:H2", + "3": "A3:D3", + "4": "E3:H3", + "5": "A4:D4", + "6": "E4:H4", + "7": "A5:D5", + "8": "E5:H5", + "9": "A7:D7", + "10": "E7:H7", + "11": "A8:D8", + "12": "E8:H8", + "13": "A9:D9", + "14": "E9:H9", + "15": "A10:D10", + "16": "E10:H10", + } + ) + ), + ) + + transfer = activity.primitive_step( + "TransferByMap", + source=conical_tube.output_pin("samples"), + destination=plate1.output_pin("samples"), + amount=sbol3.Measure(200, OM.microliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + plan=plan, + ) + transfer.description = "See the plate layout below." + plate_blanks = activity.primitive_step( + "Transfer", + source=[lb_cam], + destination=plate1.output_pin("samples"), + coordinates="A1:H1, A10:H10, A12:H12", + temperature=sbol3.Measure(4, OM.degree_Celsius), + amount=sbol3.Measure(200, OM.microliter), + ) + plate_blanks.description = "These samples are blanks." + + # Display ma here + embedded_image = activity.primitive_step( + "EmbeddedImage", + image="fig2_cell_calibration.png", + caption="Fig 2: Plate layout", + ) + + absorbance_plate1 = activity.primitive_step( + "MeasureAbsorbance", + samples=plate1.output_pin("samples"), + wavelength=sbol3.Measure(600, OM.nanometer), + ) + absorbance_plate1.name = "0 hr absorbance timepoint" + fluorescence_plate1 = activity.primitive_step( + "MeasureFluorescence", + samples=plate1.output_pin("samples"), + excitationWavelength=sbol3.Measure(488, OM.nanometer), + emissionWavelength=sbol3.Measure(530, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), + ) + fluorescence_plate1.name = "0 hr fluorescence timepoint" + + activity.designate_output( + "0_absorbance_measurements", + "http://bioprotocols.org/labop#SampleData", + source=absorbance_plate1.output_pin("measurements"), + ) + activity.designate_output( + "0_fluorescence_measurements", + "http://bioprotocols.org/labop#SampleData", + source=fluorescence_plate1.output_pin("measurements"), + ) + + # Cover plate + seal = activity.primitive_step( + "EvaporativeSeal", + location=plate1.output_pin("samples"), + specification=labop.ContainerSpec( + "seal", + queryString="cont:MicroplateAdhesiveSealingFilm", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + ############### Hasta aca bien + # Begin outgrowth + + incubate = activity.primitive_step( + "Incubate", + location=plate1.output_pin("samples"), + duration=sbol3.Measure(6, OM.hour), + temperature=sbol3.Measure(37, OM.degree_Celsius), + shakingFrequency=sbol3.Measure(220, "None"), + ) + + absorbance_plate1 = activity.primitive_step( + "MeasureAbsorbance", + samples=plate1.output_pin("samples"), + wavelength=sbol3.Measure(600, OM.nanometer), + timepoints=[ + sbol3.Measure(2, OM.hour), + sbol3.Measure(4, OM.hour), + sbol3.Measure(6, OM.hour), + ], + ) + + absorbance_plate1.name = "absorbance timepoint" + + fluorescence_plate1 = activity.primitive_step( + "MeasureFluorescence", + samples=plate1.output_pin("samples"), + excitationWavelength=sbol3.Measure(488, OM.nanometer), + emissionWavelength=sbol3.Measure(530, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), + timepoints=[ + sbol3.Measure(2, OM.hour), + sbol3.Measure(4, OM.hour), + sbol3.Measure(6, OM.hour), + ], + ) + fluorescence_plate1.name = "fluorescence timepoint" + activity.designate_output( + "246_absorbance_measurements", + "http://bioprotocols.org/labop#SampleData", + source=absorbance_plate1.output_pin("measurements"), + ) + activity.designate_output( + "246_fluorescence_measurements", + "http://bioprotocols.org/labop#SampleData", + source=fluorescence_plate1.output_pin("measurements"), + ) + # Added to si if it changes the next stap + spec.name = "Tube 1" + incubate = activity.primitive_step( + "Incubate", + location=timepoint_subculture1.output_pin("samples"), + duration=sbol3.Measure(2, OM.hour), + temperature=sbol3.Measure(37, OM.degree_Celsius), + shakingFrequency=sbol3.Measure(220, "None"), + ) + + # Hold on ice to inhibit cell growth + hold = activity.primitive_step( + "HoldOnIce", location=timepoint_subculture1.output_pin("samples") + ) + hold.description = "Reserve until the end of the experiment for absorbance and fluorescence measurements." + + incubate = activity.primitive_step( + "Incubate", + location=timepoint_subculture2.output_pin("samples"), + duration=sbol3.Measure(4, OM.hour), + temperature=sbol3.Measure(37, OM.degree_Celsius), + shakingFrequency=sbol3.Measure(220, "None"), + ) + hold = activity.primitive_step( + "HoldOnIce", location=timepoint_subculture2.output_pin("samples") + ) + hold.description = "Reserve until the end of the experiment for absorbance and fluorescence measurements." + + incubate = activity.primitive_step( + "Incubate", + location=timepoint_subculture3.output_pin("samples"), + duration=sbol3.Measure(6, OM.hour), + temperature=sbol3.Measure(37, OM.degree_Celsius), + shakingFrequency=sbol3.Measure(220, "None"), + ) + hold = activity.primitive_step( + "HoldOnIce", location=timepoint_subculture3.output_pin("samples") + ) + hold.description = "Reserve until the end of the experiment for absorbance and fluorescence measurements." + + plates234 = activity.primitive_step( + "EmptyContainer", + specification=labop.ContainerSpec( + "plates234", + name="Plates 2, 3, and 4", + queryString="cont:Plate96Well", + prefixMap={ + "cont": "https://sift.net/container-ontology/container-ontology#" + }, + ), + ) + + plan = labop.SampleData( + from_samples=timepoint_subculture1.output_pin("samples"), + values=quote( + json.dumps( + { + "1": "A2:D2", + "2": "E2:H2", + "3": "A3:D3", + "4": "E3:H3", + "5": "A4:D4", + "6": "E4:H4", + "7": "A5:D5", + "8": "E5:H5", + "9": "A7:D7", + "10": "E7:H7", + "11": "A8:D8", + "12": "E8:H8", + "13": "A9:D9", + "14": "E9:H9", + "15": "A10:D10", + "16": "E10:H10", + } + ) + ), + ) + + spec.name = "Tubes 1, 2 and 3" + transfer = activity.primitive_step( + "TransferByMap", + source=timepoint_subculture1.output_pin("samples"), + destination=plates234.output_pin("samples"), + amount=sbol3.Measure(200, OM.microliter), + temperature=sbol3.Measure(4, OM.degree_Celsius), + plan=plan, + ) + + plate_blanks = activity.primitive_step( + "Transfer", + source=[lb_cam], + destination=plates234.output_pin("samples"), + coordinates="A1:H1, A10:H10, A12:H12", + temperature=sbol3.Measure(4, OM.degree_Celsius), + amount=sbol3.Measure(200, OM.microliter), + ) + plate_blanks.description = "These samples are blanks." + + absorbance_plate = activity.primitive_step( + "MeasureAbsorbance", + samples=plates234.output_pin("samples"), + wavelength=sbol3.Measure(600, OM.nanometer), + ) + absorbance_plate.name = f"absorbance timepoint" + fluorescence_plate = activity.primitive_step( + "MeasureFluorescence", + samples=plates234.output_pin("samples"), + excitationWavelength=sbol3.Measure(488, OM.nanometer), + emissionWavelength=sbol3.Measure(530, OM.nanometer), + emissionBandpassWidth=sbol3.Measure(30, OM.nanometer), + ) + fluorescence_plate.name = f"fluorescence timepoint" + + activity.designate_output( + "plates234_absorbance_measurements", + "http://bioprotocols.org/labop#SampleData", + source=absorbance_plate.output_pin("measurements"), + ) + activity.designate_output( + "plates234_fluorescence_measurements", + "http://bioprotocols.org/labop#SampleData", + source=fluorescence_plate.output_pin("measurements"), + ) + + return activity + + +class InterlabCustomSpecialization(labop.execution.harness.ProtocolSpecialization): + def generate_artifact(self, harness: "ProtocolHarness"): + execution = self.specialization.execution + + render_kit_coordinates_table(execution) + print(execution.markdown) + + # Dress up the markdown to make it pretty and more readable + execution.markdown = execution.markdown.replace("`_E. coli_", "_`E. coli`_ `") + execution.markdown = execution.markdown.replace(" milliliter", "mL") + execution.markdown = execution.markdown.replace( + " degree Celsius", "\u00B0C" + ) # degree symbol + execution.markdown = execution.markdown.replace(" nanometer", "nm") + execution.markdown = execution.markdown.replace(" microliter", "uL") + + super().generate_artifact(harness) + + +if __name__ == "__main__": + harness = labop.execution.harness.ProtocolHarness( + protocol_name="interlab", + clean_output=True, + base_dir=os.path.join(os.path.dirname(__file__), "out", "out_igem_example3"), + entry_point=generate_protocol, + agent="test_agent", + libraries=[ + "liquid_handling", + "plate_handling", + "spectrophotometry", + "sample_arrays", + "culturing", + ], + artifacts=[ + InterlabCustomSpecialization( + specialization=labop_convert.MarkdownSpecialization( + "test_LUDOX_markdown.md" + ) + ) + ], + execution_kwargs={ + "failsafe": False, + "sample_format": "json", + "track_samples": False, + }, + execution_id="test_execution", + ) + harness.run() diff --git a/examples/protocols/iGEM/interlab-growth-curve.ipynb b/examples/protocols/iGEM/interlab-growth-curve.ipynb index e1e1a334..4076e08c 100644 --- a/examples/protocols/iGEM/interlab-growth-curve.ipynb +++ b/examples/protocols/iGEM/interlab-growth-curve.ipynb @@ -14,7 +14,7 @@ "import uml\n", "import sbol3\n", "from tyto import OM\n", - "from labop.execution_engine import ExecutionEngine\n", + "from labop.execution.execution_engine import ExecutionEngine\n", "from labop_convert.markdown.markdown_specialization import MarkdownSpecialization\n", "\n", "\n", diff --git a/examples/protocols/iGEM/interlab-growth-curve.py b/examples/protocols/iGEM/interlab-growth-curve.py index b2785794..50f5f6e5 100644 --- a/examples/protocols/iGEM/interlab-growth-curve.py +++ b/examples/protocols/iGEM/interlab-growth-curve.py @@ -9,7 +9,7 @@ import labop import uml -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop_convert.markdown.markdown_specialization import MarkdownSpecialization doc = sbol3.Document() diff --git a/examples/protocols/iGEM/interlab-timepoint-B.py b/examples/protocols/iGEM/interlab-timepoint-B.py index 767b54cf..e7561f2b 100644 --- a/examples/protocols/iGEM/interlab-timepoint-B.py +++ b/examples/protocols/iGEM/interlab-timepoint-B.py @@ -11,7 +11,7 @@ import labop import uml -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop_convert.markdown.markdown_specialization import MarkdownSpecialization filename = "".join(__file__.split(".py")[0].split("/")[-1:]) diff --git a/examples/protocols/iGEM/interlab-timepoint-B_AV.py b/examples/protocols/iGEM/interlab-timepoint-B_AV.py index f891369a..9725d1b8 100644 --- a/examples/protocols/iGEM/interlab-timepoint-B_AV.py +++ b/examples/protocols/iGEM/interlab-timepoint-B_AV.py @@ -11,7 +11,7 @@ import labop import uml -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop_convert.markdown.markdown_specialization import MarkdownSpecialization filename = "".join(__file__.split(".py")[0].split("/")[-1:]) diff --git a/examples/protocols/iGEM/revised-interlab-growth-curve.py b/examples/protocols/iGEM/revised-interlab-growth-curve.py index bcf4dbb3..b7f07d5e 100644 --- a/examples/protocols/iGEM/revised-interlab-growth-curve.py +++ b/examples/protocols/iGEM/revised-interlab-growth-curve.py @@ -2,6 +2,7 @@ http://2018.igem.org/wiki/images/0/09/2018_InterLab_Plate_Reader_Protocol.pdf """ import json +import sys from urllib.parse import quote import sbol3 @@ -9,7 +10,7 @@ import labop import uml -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop_convert import MarkdownSpecialization if "unittest" in sys.modules: @@ -134,6 +135,7 @@ "ContainerSet", quantity=2 * len(plasmids), specification=labop.ContainerSpec( + "culture_day_1", name=f"culture (day 1)", queryString="cont:CultureTube", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -157,6 +159,7 @@ "ContainerSet", quantity=2 * len(plasmids), specification=labop.ContainerSpec( + "culture_day_2", name=f"culture (day 2)", queryString="cont:CultureTube", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -180,6 +183,7 @@ "ContainerSet", quantity=2 * len(plasmids), specification=labop.ContainerSpec( + "cultures_0_hr_timepoint", name="cultures (0 hr timepoint)", queryString="cont:MicrofugeTube", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -213,6 +217,7 @@ "ContainerSet", quantity=2 * len(plasmids), specification=labop.ContainerSpec( + "back_diluted_culture", name=f"back-diluted culture", queryString="cont:50mlConicalTube", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -243,6 +248,7 @@ "ContainerSet", quantity=2 * len(plasmids), specification=labop.ContainerSpec( + "back_diluted_culture_aliquots", name="back-diluted culture aliquots", queryString="cont:MicrofugeTube", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -267,6 +273,7 @@ plate1 = activity.primitive_step( "EmptyContainer", specification=labop.ContainerSpec( + "plate_1", name="plate 1", queryString="cont:Plate96Well", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -333,9 +340,7 @@ ) # Cover plate -seal = activity.primitive_step( - "EvaporativeSeal", location=plate1.output_pin("samples"), type="foo" -) +seal = activity.primitive_step("EvaporativeSeal", location=plate1.output_pin("samples")) # Possibly display map here @@ -397,6 +402,7 @@ "ContainerSet", quantity=len(plasmids) * 2, specification=labop.ContainerSpec( + "six_hr_timepoint", name=f"6hr timepoint", queryString="cont:MicrofugeTube", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -406,6 +412,7 @@ plate2 = activity.primitive_step( "EmptyContainer", specification=labop.ContainerSpec( + "plate_2", name="plate 2", queryString="cont:Plate96Well", prefixMap={"cont": "https://sift.net/container-ontology/container-ontology#"}, @@ -483,9 +490,7 @@ plate_blanks.description = "These are the blanks." # Cover plate -seal = activity.primitive_step( - "EvaporativeSeal", location=plate1.output_pin("samples"), type="foo" -) +seal = activity.primitive_step("EvaporativeSeal", location=plate1.output_pin("samples")) # quick_spin = protocol.primitive_step('QuickSpin', diff --git a/examples/protocols/ludox/LUDOX_protocol.py b/examples/protocols/ludox/LUDOX_protocol.py index 7f4d6422..20ea351e 100644 --- a/examples/protocols/ludox/LUDOX_protocol.py +++ b/examples/protocols/ludox/LUDOX_protocol.py @@ -1,14 +1,12 @@ import logging +import os import sbol3 import tyto from sbol3 import Document import labop -from labop.constants import ddh2o, ludox -from labop.strings import Strings -from labop.utils.harness import ProtocolHarness, ProtocolSpecialization -from labop_convert.markdown.markdown_specialization import MarkdownSpecialization +import labop_convert logger: logging.Logger = logging.Logger(__name__) @@ -21,11 +19,13 @@ def create_plate(protocol: labop.Protocol): + from labop.constants import PREFIX_MAP + spec = labop.ContainerSpec( "plateRequirement", name="calibration plate", queryString=PLATE_SPECIFICATION, - prefixMap=labop.constants.PREFIX_MAP, + prefixMap=PREFIX_MAP, ) plate = protocol.primitive_step("EmptyContainer", specification=spec) plate.name = "calibration plate" @@ -33,6 +33,8 @@ def create_plate(protocol: labop.Protocol): def provision_h2o(protocol: labop.Protocol, plate) -> None: + from labop.constants import ddh2o + c_ddh2o = protocol.primitive_step( "PlateCoordinates", source=plate.output_pin("samples"), @@ -47,6 +49,8 @@ def provision_h2o(protocol: labop.Protocol, plate) -> None: def provision_ludox(protocol: labop.Protocol, plate) -> None: + from labop.constants import ludox + c_ludox = protocol.primitive_step( "PlateCoordinates", source=plate.output_pin("samples"), @@ -92,6 +96,9 @@ def measure_fluorescence( def ludox_protocol(doc: Document, protocol: labop.Protocol) -> labop.Protocol: + # importing the constants after the doc namespace has been set, so the constants have the same namespace. + from labop.constants import ddh2o, ludox + # create the materials to be provisioned doc.add(ddh2o) doc.add(ludox) @@ -126,32 +133,32 @@ def ludox_protocol(doc: Document, protocol: labop.Protocol) -> labop.Protocol: return protocol -harness = ProtocolHarness( - entry_point=ludox_protocol, - artifacts=[ - ProtocolSpecialization( - specialization=MarkdownSpecialization( - "test_LUDOX_markdown.md", - sample_format=Strings.XARRAY, - ) - ) - ], - namespace="https://labop.io/examples/protocols/ludox/", - protocol_name="iGEM_LUDOX_OD_calibration_2018", - protocol_long_name="iGEM 2018 LUDOX OD calibration protocol", - protocol_version="1.0", - protocol_description=""" -With this protocol you will use LUDOX CL-X (a 45% colloidal silica suspension) as a single point reference to -obtain a conversion factor to transform absorbance (OD600) data from your plate reader into a comparable -OD600 measurement as would be obtained in a spectrophotometer. This conversion is necessary because plate -reader measurements of absorbance are volume dependent; the depth of the fluid in the well defines the path -length of the light passing through the sample, which can vary slightly from well to well. In a standard -spectrophotometer, the path length is fixed and is defined by the width of the cuvette, which is constant. -Therefore this conversion calculation can transform OD600 measurements from a plate reader (i.e. absorbance -at 600 nm, the basic output of most instruments) into comparable OD600 measurements. The LUDOX solution -is only weakly scattering and so will give a low absorbance value. - """, -) - if __name__ == "__main__": + harness = labop.execution.harness.ProtocolHarness( + base_dir=os.path.dirname(__file__), + entry_point=ludox_protocol, + artifacts=[ + labop.execution.harness.ProtocolSpecialization( + specialization=labop_convert.MarkdownSpecialization( + "test_LUDOX_markdown.md", + sample_format=labop.Strings.XARRAY, + ) + ) + ], + namespace="https://labop.io/examples/protocols/ludox/", + protocol_name="iGEM_LUDOX_OD_calibration_2018", + protocol_long_name="iGEM 2018 LUDOX OD calibration protocol", + protocol_version="1.0", + protocol_description=""" + With this protocol you will use LUDOX CL-X (a 45% colloidal silica suspension) as a single point reference to + obtain a conversion factor to transform absorbance (OD600) data from your plate reader into a comparable + OD600 measurement as would be obtained in a spectrophotometer. This conversion is necessary because plate + reader measurements of absorbance are volume dependent; the depth of the fluid in the well defines the path + length of the light passing through the sample, which can vary slightly from well to well. In a standard + spectrophotometer, the path length is fixed and is defined by the width of the cuvette, which is constant. + Therefore this conversion calculation can transform OD600 measurements from a plate reader (i.e. absorbance + at 600 nm, the basic output of most instruments) into comparable OD600 measurements. The LUDOX solution + is only weakly scattering and so will give a low absorbance value. + """, + ) harness.run() diff --git a/examples/protocols/ludox/artifacts/test_LUDOX_markdown.md b/examples/protocols/ludox/artifacts/test_LUDOX_markdown.md index 84f6ea23..bc9e51a8 100644 --- a/examples/protocols/ludox/artifacts/test_LUDOX_markdown.md +++ b/examples/protocols/ludox/artifacts/test_LUDOX_markdown.md @@ -1,102 +1,33 @@ -# Experiment 3 - Cell measurement protocol Challenge +# iGEM 2018 LUDOX OD calibration protocol -This year we plan to test protocols that will eventually be automated. For this reason, we will use 96-well plates instead of test tubes for culturing. Consequently, we want to evaluate how the performance of our plate culturing protocol compares to culturing in test tubes (e.g. 10 mL falcon tube) on a global scale. This version of the interlab protocol involves 2 hr. time interval measurements and incubation inside a microplate reader/incubator. -At the end of the experiment, you will have four plates to be measured. You will measure both fluorescence and absorbance in each plate. - -Before performing the cell measurements, you need to perform all the calibration measurements. Please do not proceed unless you have completed the calibration protocol. Completion of the calibrations will ensure that you understand the measurement process and that you can take the cell measurements under the same conditions. For consistency and reproducibility, we are requiring all teams to use E. coli K-12 DH5-alpha. If you do not have access to this strain, you can request streaks of the transformed devices from another team near you. If you are absolutely unable to obtain the DH5-alpha strain, you may still participate in the InterLab study by contacting the Engineering Committee (engineering [at] igem [dot] org) to discuss your situation. - -For all below indicated cell measurements, you must use the same type of plates and the same volumes that you used in your calibration protocol. You must also use the same settings (e.g., filters or excitation and emission wavelengths) that you used in your calibration measurements. If you do not use the same type of plates, volumes, and settings, the measurements will not be valid. - -Protocol summary: UPDATE You will transform the eight devices listed in Table 1 into E. coli K-12 DH5-alpha cells. The next day you will pick two colonies from each transformation (16 total) and use them to inoculate 5 mL overnight cultures (this step is still in tubes). Each of these 16 overnight cultures will be used to inoculate four wells in a 96-well plate (200 microliter each, 4 replicates) and one test tube (12 mL). You will measure how fluorescence and optical density develops over 6 hours by taking measurements at time point 0 hour and at time point 6 hours. Follow the protocol below and the visual instructions in Figure 1 and Figure 2. - - -## Protocol Outputs: -* `baseline absorbance of culture (day 2) measurements of cultures (0 hr timepoint)` -* `0 hr absorbance timepoint measurements of plate 1` -* `0 hr fluorescence timepoint measurements of plate 1` -* `absorbance timepoint measurements of plate 1 at timepoints 2.0 hour, 4.0 hour, 6.0 hour` -* `fluorescence timepoint measurements of plate 1 at timepoints 2.0 hour, 4.0 hour, 6.0 hour` -* `absorbance timepoint measurements of Plates 2, 3, and 4` -* `fluorescence timepoint measurements of Plates 2, 3, and 4` +With this protocol you will use LUDOX CL-X (a 45% colloidal silica suspension) as a single point reference to +obtain a conversion factor to transform absorbance (OD600) data from your plate reader into a comparable +OD600 measurement as would be obtained in a spectrophotometer. This conversion is necessary because plate +reader measurements of absorbance are volume dependent; the depth of the fluid in the well defines the path +length of the light passing through the sample, which can vary slightly from well to well. In a standard +spectrophotometer, the path length is fixed and is defined by the width of the cuvette, which is constant. +Therefore this conversion calculation can transform OD600 measurements from a plate reader (i.e. absorbance +at 600 nm, the basic output of most instruments) into comparable OD600 measurements. The LUDOX solution +is only weakly scattering and so will give a low absorbance value. + ## Protocol Materials: -* [_E. coli_ DH5 alpha competent cells](https://identifiers.org/taxonomy:668369) -* [Negative control](http://parts.igem.org/Part:BBa_J428100) -* [Positive control (I20270)](http://parts.igem.org/Part:BBa_I20270) -* [Test Device 1 (J364000)](http://parts.igem.org/Part:BBa_J364000) -* [Test Device 2 (J364001)](http://parts.igem.org/Part:BBa_J364001) -* [Test Device 3 (J364002)](http://parts.igem.org/Part:BBa_J364002) -* [Test Device 4 (J364007)](http://parts.igem.org/Part:BBa_J364007) -* [Test Device 5 (J364008)](http://parts.igem.org/Part:BBa_J364008) -* [Test Device 6 (J364009)](http://parts.igem.org/Part:BBa_J364009) -* [LB Broth + Chloramphenicol (34 ug/mL)]() -* [LB Agar + Chloramphenicol (34 ug/mL)]() -* [Chloramphenicol stock solution (34 mg/mL)](https://pubchem.ncbi.nlm.nih.gov/compound/5959) -* [Ice]() -* [Plate reader]() -* [Shaking incubator]() -* Petri dish (x 8) -* culture tube (x 32) -* 1.5 mL microfuge tube (x 16) -* 50 ml conical tube (x 64) -* 96 well microplate (x 2) -* microplate adhesive sealing film +* [Water, sterile-filtered, BioReagent, suitable for cell culture](https://identifiers.org/pubchem.substance:24901740) +* [LUDOX(R) CL-X colloidal silica, 45 wt. % suspension in H2O](https://identifiers.org/pubchem.substance:24866361) ## Protocol Steps: -1. Obtain 8 x Petri dish containing LB Agar + Chloramphenicol (34 ug/mL) growth medium for culturing `transformant strains` -2. Transform `Negative control` DNA into `_E. coli_ DH5 alpha competent cells`. Repeat for the remaining transformant DNA: `Positive control (I20270)`, `Test Device 1 (J364000)`, `Test Device 2 (J364001)`, `Test Device 3 (J364002)`, `Test Device 4 (J364007)`, `Test Device 5 (J364008)`, and `Test Device 6 (J364009)`. Plate transformants on LB Agar + Chloramphenicol (34 ug/mL) `transformant strains` plates. Incubate overnight (for 16 hour) at 37.0 degree Celsius. -3. Obtain 16 x culture tubes to contain `culture (day 1)` -4. Pick 2 colonies from each `transformant strains` plate. -5. Inoculate 2 colonies of each transformant strains, for a total of 16 cultures. Inoculate each into 5.0 milliliter of LB Broth + Chloramphenicol (34 ug/mL) in culture (day 1) and grow overnight (for 16.0 hour) at 37.0 degree Celsius and 220 rpm. -6. Obtain 16 x culture tubes to contain `culture (day 2)` -7. Dilute each of 16 `culture (day 1)` samples with LB Broth + Chloramphenicol (34 ug/mL) into the culture tube at a 1:10 ratio and final volume of 5.0 milliliter. Maintain at 4.0 degree Celsius while performing dilutions. (This can be also performed on ice). -8. Obtain 16 x 1.5 mL microfuge tubes to contain `cultures (0 hr timepoint)` -9. Hold `cultures (0 hr timepoint)` on ice. This will prevent cell growth while transferring samples. -10. Transfer 1.0 milliliter of each of 16 `culture (day 2)` samples to 1.5 mL microfuge tube containers to contain a total of 16 `cultures (0 hr timepoint)` samples. Maintain at 4.0 degree Celsius during transfer. (This can be also performed on Ice). -11. Measure baseline absorbance of culture (day 2) of `cultures (0 hr timepoint)` at 600.0 nanometer. -12. Obtain 16 x 50 ml conical tubes to contain `back-diluted culture` The conical tube should be opaque, amber-colored, or covered with foil. -13. Back-dilute each of 16 `culture (day 2)` samples to a target OD of 0.02 using LB Broth + Chloramphenicol (34 ug/mL) as diluent to a final volume of 40.0 milliliter. Maintain at 4.0 degree Celsius while performing dilutions. - -![](fig1_challenge_protocol.png) -
Fig 1: Visual representation of protocol
- -14. Obtain 16 x 50 ml conical tubes to contain `Tubes 1, 2 and 3` The conical tubes should be opaque, amber-colored, or covered with foil. -15. Obtain 16 x 50 ml conical tubes to contain `Tube 2` The conical tubes should be opaque, amber-colored, or covered with foil. -16. Obtain 16 x 50 ml conical tubes to contain `Tube 3` The conical tubes should be opaque, amber-colored, or covered with foil. -17. Transfer 12.0 milliliter of each of 16 `back-diluted culture` samples to 50 ml conical tube containers to contain a total of 16 `Tubes 1, 2 and 3` samples. Maintain at 4.0 degree Celsius during transfer. -18. Transfer 12.0 milliliter of each of 16 `back-diluted culture` samples to 50 ml conical tube containers to contain a total of 16 `Tube 2` samples. Maintain at 4.0 degree Celsius during transfer. -19. Transfer 12.0 milliliter of each of 16 `back-diluted culture` samples to 50 ml conical tube containers to contain a total of 16 `Tube 3` samples. Maintain at 4.0 degree Celsius during transfer. -20. Obtain a 96 well microplate to contain `plate 1` -21. Hold `plate 1` on ice. -22. Transfer 200.0 microliter of each `back-diluted culture` sample to 96 well microplate `plate 1` in the wells indicated in the plate layout. - Maintain at 4.0 degree Celsius during transfer. -23. Transfer 200.0 microliter of `LB Broth + Chloramphenicol (34 ug/mL)` sample to wells A1:H1, A10:H10, A12:H12 of 96 well microplate `plate 1`. Maintain at 4.0 degree Celsius during transfer. These samples are blanks. - -![](fig2_cell_calibration.png) -Fig 2: Plate layout
+1. Provision a container named `calibration plate` meeting specification: cont:ClearPlate and + cont:SLAS-4-2004 and + (cont:wellVolume some + ((om:hasUnit value om:microlitre) and + (om:hasNumericalValue only xsd:decimal[>= "200"^^xsd:decimal]))). +2. Pipette 100.0 microliter of [Water, sterile-filtered, BioReagent, suitable for cell culture](https://identifiers.org/pubchem.substance:24901740) into `calibration plate A1:D1`. +3. Pipette 100.0 microliter of [LUDOX(R) CL-X colloidal silica, 45 wt. % suspension in H2O](https://identifiers.org/pubchem.substance:24866361) into `calibration plate A2:D2`. +4. Import data into the provided Excel file: . -24. Measure 0 hr absorbance timepoint of `plate 1` at 600.0 nanometer. -25. Measure 0 hr fluorescence timepoint of `plate 1` with excitation wavelength of 488.0 nanometer and emission filter of 530.0 nanometer and 30.0 nanometer bandpass. -26. Cover `plate 1` samples in 96 well microplate with your choice of material to prevent evaporation. -27. Incubate all `plate 1` samples for 6.0 hour at 37.0 degree Celsius at 220 rpm. -28. Measure absorbance timepoint of `plate 1` at 600.0 nanometer at timepoints 2.0 hour, 4.0 hour, 6.0 hour. -29. Measure fluorescence timepoint of `plate 1` with excitation wavelength of 488.0 nanometer and emission filter of 530.0 nanometer and 30.0 nanometer bandpass at timepoints 2.0 hour, 4.0 hour, 6.0 hour. -30. Incubate all `Tubes 1, 2 and 3` samples for 2.0 hour at 37.0 degree Celsius at 220 rpm. -31. Hold all `Tubes 1, 2 and 3` samples on ice. Reserve until the end of the experiment for absorbance and fluorescence measurements. -32. Incubate all `Tube 2` samples for 4.0 hour at 37.0 degree Celsius at 220 rpm. -33. Hold all `Tube 2` samples on ice. Reserve until the end of the experiment for absorbance and fluorescence measurements. -34. Incubate all `Tube 3` samples for 6.0 hour at 37.0 degree Celsius at 220 rpm. -35. Hold all `Tube 3` samples on ice. Reserve until the end of the experiment for absorbance and fluorescence measurements. -36. Obtain a 96 well microplate to contain `Plates 2, 3, and 4` -37. Transfer 200.0 microliter of each `Tubes 1, 2 and 3` sample to 96 well microplate `Plates 2, 3, and 4` in the wells indicated in the plate layout. - Maintain at 4.0 degree Celsius during transfer. -38. Transfer 200.0 microliter of `LB Broth + Chloramphenicol (34 ug/mL)` sample to wells A1:H1, A10:H10, A12:H12 of 96 well microplate `Plates 2, 3, and 4`. Maintain at 4.0 degree Celsius during transfer. These samples are blanks. -39. Measure absorbance timepoint of `Plates 2, 3, and 4` at 600.0 nanometer. -40. Measure fluorescence timepoint of `Plates 2, 3, and 4` with excitation wavelength of 488.0 nanometer and emission filter of 530.0 nanometer and 30.0 nanometer bandpass. -41. Import data for `baseline absorbance of culture (day 2) measurements of cultures (0 hr timepoint)`, `0 hr absorbance timepoint measurements of plate 1`, `0 hr fluorescence timepoint measurements of plate 1`, `absorbance timepoint measurements of plate 1 at timepoints 2.0 hour, 4.0 hour, 6.0 hour`, `fluorescence timepoint measurements of plate 1 at timepoints 2.0 hour, 4.0 hour, 6.0 hour`, `absorbance timepoint measurements of Plates 2, 3, and 4`, `fluorescence timepoint measurements of Plates 2, 3, and 4` into provided Excel file. --- -Timestamp: 2022-07-08 19:34:25.986146--- -Protocol version: 1.2b \ No newline at end of file +Timestamp: 2023-10-14 18:13:35.378047 +Protocol version: 1.0 diff --git a/examples/protocols/opentrons/opentrons-ludox/opentrons_ludox_example.py b/examples/protocols/opentrons/opentrons-ludox/opentrons_ludox_example.py index 5b1ae849..e27ce02f 100644 --- a/examples/protocols/opentrons/opentrons-ludox/opentrons_ludox_example.py +++ b/examples/protocols/opentrons/opentrons-ludox/opentrons_ludox_example.py @@ -2,10 +2,10 @@ import tyto import labop -from labop.constants import ddh2o, ludox +from labop.constants import PREFIX_MAP, ddh2o, ludox +from labop.execution.harness import ProtocolHarness, ProtocolSpecialization from labop.protocol import Protocol from labop.strings import Strings -from labop.utils.harness import ProtocolHarness, ProtocolSpecialization from labop_convert.markdown.markdown_specialization import MarkdownSpecialization from labop_convert.opentrons.opentrons_specialization import OT2Specialization @@ -27,30 +27,30 @@ def generate_protocol(doc: sbol3.Document, activity: Protocol) -> Protocol: "working_reagents_rack", name="rack for reagent aliquots", queryString="cont:Opentrons24TubeRackwithEppendorf1.5mLSafe-LockSnapcap", - prefixMap=labop.constants.PREFIX_MAP, + prefixMap=PREFIX_MAP, ) spec_ludox_container = labop.ContainerSpec( "ludox_working_solution", name="tube for ludox working solution", queryString="cont:MicrofugeTube", - prefixMap=labop.constants.PREFIX_MAP, + prefixMap=PREFIX_MAP, ) spec_water_container = labop.ContainerSpec( "water_stock", name="tube for water aliquot", queryString="cont:MicrofugeTube", - prefixMap=labop.constants.PREFIX_MAP, + prefixMap=PREFIX_MAP, ) spec_plate = labop.ContainerSpec( "calibration_plate", name="calibration plate", queryString="cont:Corning96WellPlate360uLFlat", - prefixMap=labop.constants.PREFIX_MAP, + prefixMap=PREFIX_MAP, ) spec_tiprack = labop.ContainerSpec( "tiprack", queryString="cont:Opentrons96TipRack300uL", - prefixMap=labop.constants.PREFIX_MAP, + prefixMap=PREFIX_MAP, ) doc.add(spec_rack) doc.add(spec_ludox_container) @@ -125,26 +125,25 @@ def generate_protocol(doc: sbol3.Document, activity: Protocol) -> Protocol: return activity -harness = ProtocolHarness( - entry_point=generate_protocol, - artifacts=[ - ProtocolSpecialization( - specialization=MarkdownSpecialization( - "opentrons_ludox_example_protocol.md", - sample_format=Strings.XARRAY, - ) - ), - ProtocolSpecialization( - specialization=OT2Specialization("opentrons_ludox_example_labop.py") - ), - ], - namespace="https://labop.io/examples/protocols/opentrons/", - protocol_name="iGEM_LUDOX_OD_calibration_2018", - protocol_long_name="iGEM 2018 LUDOX OD calibration protocol for OT2", - protocol_version="1.0", - protocol_description="Test Execution", - agent=sbol3.Agent("ot2_machine", name="OT2 machine"), -) - if __name__ == "__main__": + harness = ProtocolHarness( + entry_point=generate_protocol, + artifacts=[ + ProtocolSpecialization( + specialization=MarkdownSpecialization( + "opentrons_ludox_example_protocol.md", + sample_format=Strings.XARRAY, + ) + ), + ProtocolSpecialization( + specialization=OT2Specialization("opentrons_ludox_example_labop.py") + ), + ], + namespace="https://labop.io/examples/protocols/opentrons/", + protocol_name="iGEM_LUDOX_OD_calibration_2018", + protocol_long_name="iGEM 2018 LUDOX OD calibration protocol for OT2", + protocol_version="1.0", + protocol_description="Test Execution", + agent=sbol3.Agent("ot2_machine", name="OT2 machine"), + ) harness.run() diff --git a/examples/protocols/opentrons/opentrons-pcr/opentrons_pcr_example.py b/examples/protocols/opentrons/opentrons-pcr/opentrons_pcr_example.py index e3a00179..cc91f22d 100644 --- a/examples/protocols/opentrons/opentrons-pcr/opentrons_pcr_example.py +++ b/examples/protocols/opentrons/opentrons-pcr/opentrons_pcr_example.py @@ -6,9 +6,8 @@ import labop import uml -from labop.constants import ddh2o, ludox -from labop.execution_engine import ExecutionEngine -from labop_convert.markdown.markdown_specialization import MarkdownSpecialization +from labop.constants import PREFIX_MAP, ddh2o, ludox +from labop.execution.execution_engine import ExecutionEngine from labop_convert.opentrons.opentrons_specialization import OT2Specialization # Dev Note: This is a test of the initial version of the OT2 specialization. Any specs shown here can be changed in the future. Use at your own risk. Here be dragons. @@ -155,7 +154,7 @@ def load_pcr_plan(fname: str, doc: sbol3.Document): "reagent_rack", name="Tube rack for reagents", queryString="cont:Opentrons24TubeRackwithEppendorf1.5mLSafe-LockSnapcap", - prefixMap=labop.constants.PREFIX_MAP, + prefixMap=PREFIX_MAP, ) rack = activity.primitive_step("EmptyRack", specification=reagent_rack) load_rack = activity.primitive_step( @@ -167,7 +166,7 @@ def load_pcr_plan(fname: str, doc: sbol3.Document): "primer_plate", name="primers in 96-well plate", queryString="cont:Corning96WellPlate360uLFlat", - prefixMap=labop.constants.PREFIX_MAP, + prefixMap=PREFIX_MAP, ) load = activity.primitive_step( "LoadRackOnInstrument", rack=primer_plate, coordinates="3" @@ -179,7 +178,7 @@ def load_pcr_plan(fname: str, doc: sbol3.Document): "polymerase", name="DNA Polymerase", queryString="cont:StockReagent", - prefixMap=labop.constants.PREFIX_MAP, + prefixMap=PREFIX_MAP, ) load_reagents = activity.primitive_step( "LoadContainerInRack", @@ -196,7 +195,7 @@ def load_pcr_plan(fname: str, doc: sbol3.Document): "water", name="tube for water", queryString="cont:MicrofugeTube", - prefixMap=labop.constants.PREFIX_MAP, + prefixMap=PREFIX_MAP, ), coordinates="B1", ) @@ -217,7 +216,7 @@ def load_pcr_plan(fname: str, doc: sbol3.Document): template.display_id + "_container", name="container of " + template.name, queryString="cont:MicrofugeTube", - prefixMap=labop.constants.PREFIX_MAP, + prefixMap=PREFIX_MAP, ) load_template = activity.primitive_step( "LoadContainerInRack", @@ -232,7 +231,7 @@ def load_pcr_plan(fname: str, doc: sbol3.Document): "pcr_plate", name="PCR plate", queryString="cont:Biorad96WellPCRPlate", - prefixMap=labop.constants.PREFIX_MAP, + prefixMap=PREFIX_MAP, ) load_pcr_plate_on_thermocycler = activity.primitive_step( "LoadContainerOnInstrument", diff --git a/examples/protocols/opentrons/opentrons-toy/opentrons_toy_protocol.py b/examples/protocols/opentrons/opentrons-toy/opentrons_toy_protocol.py index 1e586ca1..34baed8d 100644 --- a/examples/protocols/opentrons/opentrons-toy/opentrons_toy_protocol.py +++ b/examples/protocols/opentrons/opentrons-toy/opentrons_toy_protocol.py @@ -1,24 +1,14 @@ import logging -import rdflib as rdfl import sbol3 import tyto from sbol3 import Document import labop -import uml -from labop.execution_engine import ExecutionEngine +from labop.constants import PREFIX_MAP +from labop.execution import ProtocolHarness, ProtocolSpecialization from labop.protocol import Protocol from labop.strings import Strings -from labop.utils.harness import ( - ProtocolDiagram, - ProtocolExecutionDiagram, - ProtocolExecutionNTuples, - ProtocolHarness, - ProtocolNTuples, - ProtocolSampleTrace, - ProtocolSpecialization, -) from labop_convert.markdown.markdown_specialization import MarkdownSpecialization from labop_convert.opentrons.opentrons_specialization import ( REVERSE_LABWARE_MAP, @@ -61,7 +51,7 @@ def get_container(protocol: labop.Protocol, container_name: str, container_type: container_name.replace(" ", "_"), name=container_name, queryString=query_string, - prefixMap=labop.constants.PREFIX_MAP, + prefixMap=PREFIX_MAP, ) plate = protocol.primitive_step("EmptyContainer", specification=plate_spec) return plate @@ -126,24 +116,23 @@ def opentrons_toy_protocol(doc: sbol3.Document, protocol: Protocol) -> Protocol: return protocol -harness = ProtocolHarness( - entry_point=opentrons_toy_protocol, - artifacts=[ - ProtocolSpecialization( - specialization=MarkdownSpecialization( - "opentrons_toy_protocol.md", sample_format=Strings.XARRAY - ) - ), - ProtocolSpecialization( - specialization=OT2Specialization("opentrons_toy_protocol_labop.py") - ), - ], - namespace="https://labop.io/examples/protocols/opentrons/", - protocol_name="opentrons_toy", - protocol_long_name="OT2 simple toy demonstration", - protocol_version="1.0", - protocol_description="Example Opentrons Protocol as LabOP", -) - if __name__ == "__main__": + harness = ProtocolHarness( + entry_point=opentrons_toy_protocol, + artifacts=[ + ProtocolSpecialization( + specialization=MarkdownSpecialization( + "opentrons_toy_protocol.md", sample_format=Strings.XARRAY + ) + ), + ProtocolSpecialization( + specialization=OT2Specialization("opentrons_toy_protocol_labop.py") + ), + ], + namespace="https://labop.io/examples/protocols/opentrons/", + protocol_name="opentrons_toy", + protocol_long_name="OT2 simple toy demonstration", + protocol_version="1.0", + protocol_description="Example Opentrons Protocol as LabOP", + ) harness.run() diff --git a/examples/protocols/pH_calibration/pH_calibration.py b/examples/protocols/pH_calibration/pH_calibration.py index c3c90d4d..af9e13cc 100644 --- a/examples/protocols/pH_calibration/pH_calibration.py +++ b/examples/protocols/pH_calibration/pH_calibration.py @@ -18,7 +18,7 @@ import examples.pH_calibration.ph_calibration_utils as util import labop import uml -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine logger: logging.Logger = logging.Logger("pH_calibration") @@ -439,7 +439,10 @@ def main(): ] try: execution = ee.execute( - new_protocol, agent, id="test_execution", parameter_values=parameter_values + new_protocol, + agent, + id="test_execution", + parameter_values=parameter_values, ) except Exception as e: logger.exception(e) diff --git a/interlab-growth-curve.ipynb b/interlab-growth-curve.ipynb index e1e1a334..4076e08c 100644 --- a/interlab-growth-curve.ipynb +++ b/interlab-growth-curve.ipynb @@ -14,7 +14,7 @@ "import uml\n", "import sbol3\n", "from tyto import OM\n", - "from labop.execution_engine import ExecutionEngine\n", + "from labop.execution.execution_engine import ExecutionEngine\n", "from labop_convert.markdown.markdown_specialization import MarkdownSpecialization\n", "\n", "\n", diff --git a/interlab-growth-curve.py b/interlab-growth-curve.py index b2785794..50f5f6e5 100644 --- a/interlab-growth-curve.py +++ b/interlab-growth-curve.py @@ -9,7 +9,7 @@ import labop import uml -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop_convert.markdown.markdown_specialization import MarkdownSpecialization doc = sbol3.Document() diff --git a/labop/__init__.py b/labop/__init__.py index 00c251d0..54ccdc91 100644 --- a/labop/__init__.py +++ b/labop/__init__.py @@ -4,7 +4,6 @@ from . import inner -from .utils import * from .parameter_value import * from .sample_collection import * from .activity_edge_flow import * @@ -24,9 +23,7 @@ from .container_spec import * from .data import * -from .execution_engine import * -from .execution_engine_utils import * -from .lab_interface import * +from .execution import * from .material import * from .primitive_array import * @@ -43,6 +40,12 @@ from .library import * +from .execution import * +from .utils import * + +# from .constants import * + + ######################################### # Kludge for getting parents and TopLevels - workaround for pySBOL3 issue #234 # TODO: remove after resolution of https://github.com/SynBioDex/pySBOL3/issues/234 diff --git a/labop/activity_edge_flow.py b/labop/activity_edge_flow.py index 0ea7cdbe..c8143af8 100644 --- a/labop/activity_edge_flow.py +++ b/labop/activity_edge_flow.py @@ -7,6 +7,7 @@ import sbol3 from uml import CallBehaviorAction, InputPin, LiteralReference, Parameter +from uml.activity_edge import ActivityEdge from . import inner from .protocol import Protocol @@ -16,7 +17,7 @@ class ActivityEdgeFlow(inner.ActivityEdgeFlow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - def get_edge(self): + def get_edge(self) -> ActivityEdge: return self.edge.lookup() def get_value(self): diff --git a/labop/call_behavior_execution.py b/labop/call_behavior_execution.py index 5c670b25..c3aa8222 100644 --- a/labop/call_behavior_execution.py +++ b/labop/call_behavior_execution.py @@ -123,14 +123,18 @@ def check_next_tokens( for token in tokens: edge = token.get_edge() if isinstance(edge, ObjectFlow): - target: OutputPin = edge.get_target() # target is an OutputPin - call.parameter_values += [ - ParameterValue( - parameter=self.get_parameter(name=target.name, ordered=True), - value=literal(v.get_value(), reference=True), - ) - for v in token.get_value() - ] + target = edge.get_target() + # only want output pin tokens. It is possible that the target is a subprotocol ActivityParameterNode. + if isinstance(target, OutputPin): + for v in token.get_value(): + call.parameter_values.append( + ParameterValue( + parameter=self.get_parameter( + name=target.get_name(), ordered=True + ), + value=literal(v.get_value(), reference=True), + ) + ) ### Check that the same parameter names are sane: # 1. unbounded parameters can appear 0+ times @@ -139,7 +143,7 @@ def check_next_tokens( pin_sets = {} for pv in call.parameter_values: - name = pv.get_parameter().name + name = pv.get_name() value = pv.value.get_value() if pv.value else None if name not in pin_sets: pin_sets[name] = [] diff --git a/labop/constants.py b/labop/constants.py index 1992f7de..e0c4df92 100644 --- a/labop/constants.py +++ b/labop/constants.py @@ -2,6 +2,7 @@ import rdflib as rdfl import sbol3 +import tyto CONT_NS = rdfl.Namespace("https://sift.net/container-ontology/container-ontology#") OM_NS = rdfl.Namespace("http://www.ontology-of-units-of-measure.org/resource/om-2/") @@ -14,3 +15,37 @@ ludox = sbol3.Component("LUDOX", "https://identifiers.org/pubchem.substance:24866361") ludox.name = "LUDOX(R) CL-X colloidal silica, 45 wt. % suspension in H2O" + +pbs = sbol3.Component("pbs", "https://pubchem.ncbi.nlm.nih.gov/compound/24978514") +pbs.name = "Phosphate Buffered Saline" + +silica_beads = sbol3.Component( + "silica_beads", + "https://nanocym.com/wp-content/uploads/2018/07/NanoCym-All-Datasheets-.pdf", +) +silica_beads.name = "NanoCym 950 nm monodisperse silica nanoparticles" +silica_beads.description = "3e9 NanoCym microspheres" + +fluorescein = sbol3.Component( + "fluorescein", "https://pubchem.ncbi.nlm.nih.gov/substance/329753341" +) +fluorescein.name = "Fluorescein" + +cascade_blue = sbol3.Component( + "cascade_blue", "https://pubchem.ncbi.nlm.nih.gov/substance/57269662" +) +cascade_blue.name = "Cascade Blue" + +sulforhodamine = sbol3.Component( + "sulforhodamine", "https://pubchem.ncbi.nlm.nih.gov/compound/139216224" +) +sulforhodamine.name = "Sulforhodamine" + +rpm = sbol3.UnitDivision( + "rpm", + name="rpm", + symbol="rpm", + label="revolutions per minute", + numerator=tyto.OM.revolution, + denominator=tyto.OM.minute, +) diff --git a/labop/execution/__init__.py b/labop/execution/__init__.py new file mode 100644 index 00000000..415d4c5a --- /dev/null +++ b/labop/execution/__init__.py @@ -0,0 +1,4 @@ +from .execution_engine import * +from .execution_context import * +from .execution_engine_utils import * +from .harness import * diff --git a/labop_convert/behavior_dynamics.py b/labop/execution/behavior_dynamics.py similarity index 99% rename from labop_convert/behavior_dynamics.py rename to labop/execution/behavior_dynamics.py index c2aa7282..52449733 100644 --- a/labop_convert/behavior_dynamics.py +++ b/labop/execution/behavior_dynamics.py @@ -71,7 +71,9 @@ def update(self, record: ActivityNodeExecution) -> None: if new_nodes: self.graph = self.update_graph(new_nodes) self.to_dot().render( - os.path.join(self.outdir, f"{self.name}_{self.exec_tick}") + os.path.join(self.outdir, f"{self.name}_{self.exec_tick}"), + cleanup=True, + overwrite_source=True, ) self.exec_tick += 1 else: diff --git a/labop/execution_context.py b/labop/execution/execution_context.py similarity index 99% rename from labop/execution_context.py rename to labop/execution/execution_context.py index fc84cca8..bb72d003 100644 --- a/labop/execution_context.py +++ b/labop/execution/execution_context.py @@ -2,6 +2,8 @@ import sbol3 +from labop.call_behavior_execution import CallBehaviorExecution +from labop.parameter_value import ParameterValue from uml import ( Action, Activity, @@ -23,9 +25,6 @@ from uml.final_node import FinalNode from uml.initial_node import InitialNode -from .call_behavior_execution import CallBehaviorExecution -from .parameter_value import ParameterValue - class ExecutionContext(object): """ @@ -211,7 +210,7 @@ def get_invocation_edge(self, source: ActivityNode, target: ActivityNode): ) ) except StopIteration: - raise Exception(f"Could not find invocation edge from {source}") + raise Exception(f"Could not find invocation edge from {source} to {target}") def get_nodes(self): nodes = [] diff --git a/labop/execution_engine.py b/labop/execution/execution_engine.py similarity index 97% rename from labop/execution_engine.py rename to labop/execution/execution_engine.py index 5f513757..6ddf49c1 100644 --- a/labop/execution_engine.py +++ b/labop/execution/execution_engine.py @@ -11,7 +11,17 @@ import sbol3 from numpy import record -from labop_convert.behavior_dynamics import SampleProvenanceObserver +from labop.activity_edge_flow import ActivityEdgeFlow +from labop.activity_node_execution import ActivityNodeExecution +from labop.behavior_execution import BehaviorExecution +from labop.call_behavior_execution import CallBehaviorExecution +from labop.dataset import Dataset +from labop.parameter_value import ParameterValue +from labop.primitive import Primitive +from labop.protocol import Protocol +from labop.protocol_execution import ProtocolExecution +from labop.sample_data import SampleData +from labop.strings import Strings from uml import ActivityNode, CallBehaviorAction from uml.activity import Activity from uml.activity_edge import ActivityEdge @@ -21,18 +31,8 @@ from uml.pin import Pin from uml.utils import WellFormednessIssue, WellformednessLevels, literal -from .activity_edge_flow import ActivityEdgeFlow -from .activity_node_execution import ActivityNodeExecution -from .behavior_execution import BehaviorExecution -from .call_behavior_execution import CallBehaviorExecution -from .dataset import Dataset +from .behavior_dynamics import SampleProvenanceObserver from .execution_context import ExecutionContext -from .parameter_value import ParameterValue -from .primitive import Primitive -from .protocol import Protocol -from .protocol_execution import ProtocolExecution -from .sample_data import SampleData -from .strings import Strings l: logging.Logger = logging.getLogger(__file__) l.setLevel(logging.ERROR) @@ -534,9 +534,12 @@ def next_tokens( ActivityEdgeFlow( edge=new_execution_context.get_invocation_edge( token_consumed.get_edge().get_source(), + activity_parameter_node, ), token_source=token_consumed.token_source, - value=[literal(token_consumed.value, reference=True)], + value=[ + literal(v, reference=True) for v in token_consumed.value + ], ) for token_consumed in record.get_incoming_flows() if isinstance(token_consumed.get_edge().get_source(), Pin) diff --git a/labop/execution_engine_utils.py b/labop/execution/execution_engine_utils.py similarity index 96% rename from labop/execution_engine_utils.py rename to labop/execution/execution_engine_utils.py index 35e67549..fd3ca2b4 100644 --- a/labop/execution_engine_utils.py +++ b/labop/execution/execution_engine_utils.py @@ -1,10 +1,9 @@ import logging from labop import ActivityEdgeFlow +from labop.activity_node_execution import ActivityNodeExecution from uml import CallBehaviorAction, Pin -from .activity_node_execution import ActivityNodeExecution - l = logging.getLogger(__file__) l.setLevel(logging.ERROR) diff --git a/labop/execution/harness.py b/labop/execution/harness.py new file mode 100644 index 00000000..bb62ece7 --- /dev/null +++ b/labop/execution/harness.py @@ -0,0 +1,728 @@ +import filecmp +import glob +import json +import logging +import os +import shutil +from abc import ABC +from datetime import datetime +from importlib.machinery import SourceFileLoader +from importlib.util import module_from_spec, spec_from_loader +from typing import Any, Callable, Dict, List, Optional, Union + +import sbol3 + +from labop import ProtocolExecution +from labop.execution import ExecutionEngine +from labop.library import import_library +from labop.parameter_value import ParameterValue +from labop.protocol import Protocol +from labop.utils.helpers import file_diff, prepare_document +from labop_convert import BehaviorSpecialization + +l = logging.Logger(__file__) +l.setLevel(logging.INFO) +ConsoleOutputHandler = logging.StreamHandler() +l.addHandler(ConsoleOutputHandler) + + +class ProtocolArtifactStatus: + PASS = "pass" + FAIL = "fail" + PENDING = "pending" + + +class ProtocolArtifact(ABC): + results: Dict[str, Any] = None + status: str = None + filename: str = None + + def __init__(self, *args, **kwargs): + super().__init__(*args) + self.status = ProtocolArtifactStatus.PENDING + self.results = {} + + def results_summary(self) -> str: + return f"{json.dumps(self.results, indent=4)}" + + def configuration_summary(self, verbose=False) -> str: + return "" + + +class ProtocolNTuples(ProtocolArtifact): + cached_protocol_file: Optional[str] = None + namespace: str + protocol_name: str + protocol_long_name: str + protocol_version: str + protocol_description: str + _protocol: Protocol = None + _doc: sbol3.Document = None + next: Optional[ProtocolArtifact] = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.cached_protocol_file = ( + kwargs["cached_protocol_file"] if "cached_protocol_file" in kwargs else None + ) + self.namespace = kwargs["namespace"] if "namespace" in kwargs else None + self.protocol_name = ( + kwargs["protocol_name"] if "protocol_name" in kwargs else None + ) + self.protocol_long_name = ( + kwargs["protocol_long_name"] if "protocol_long_name" in kwargs else None + ) + self.protocol_version = ( + kwargs["protocol_version"] if "protocol_version" in kwargs else None + ) + self.protocol_description = ( + kwargs["protocol_description"] if "protocol_description" in kwargs else None + ) + self.next = kwargs["next"] if "next" in kwargs else None + self._doc = self.prepare_document() + self._protocol = None + + def read_protocol(self, filename: str = None): + filename = self.cached_protocol_file if filename is None else filename + self._doc.read(filename, "nt") + self._protocol = self._doc.find(f"{self.namespace}{self.protocol_name}") + return self._protocol, self._doc + + def import_libraries(self, libraries: List[str]): + self.results["libraries"] = [] + for library in libraries: + import_library(library) + self.results["libraries"].append(library) + + def generate_protocol(self, harness: "ProtocolHarness") -> Protocol: + self.import_libraries(harness.libraries) + entry_point = harness.entry_point + self._protocol = Protocol(harness.protocol_name) + self._protocol.name = harness.protocol_long_name + self._protocol.version = harness.protocol_version + self._protocol.description = harness.protocol_description + self.results["protocol"] = { + "name": self.protocol_name, + "version": self._protocol.version, + } + self._doc.add(self._protocol) + self._protocol = entry_point(self._doc, self._protocol) + l.info("Validating and writing protocol") + v = self._doc.validate() + + if len(v) > 0: + self.results["protocol"]["validation"] = "".join(f"\n {e}" for e in v) + self.status = ProtocolArtifactStatus.FAIL + else: + self.results["protocol"]["validation"] = ProtocolArtifactStatus.PASS + self.status = ProtocolArtifactStatus.PASS + + return self._protocol + + def prepare_document(self) -> sbol3.Document: + self._doc = prepare_document(namespace=self.namespace) + self.results["document"] = {"namespace": self.namespace} + return self._doc + + def ntuples_filename(self, filename_prefix) -> str: + return filename_prefix + ".nt" + + def generate_artifact(self, harness: "ProtocolHarness"): + if self.cached_protocol_file is not None: + l.info(f"Bypassing Protocol Generation, looking for existing .nt file ...") + if os.path.exists(self.cached_protocol_file): + l.info(f"Found .nt file: {self.cached_protocol_file}") + self.read_protocol() + + self.filename = ( + os.path.join( + harness.full_output_dir, + self.ntuples_filename(harness.filename_prefix()), + ) + if self.filename is None + else self.filename + ) + try: + self.generate_protocol(harness) + + assert self._protocol is not None, f"Protocol was not generated ... " + + # with open(self.filename, "w") as f: + l.info(f"Saving protocol [{self.filename}].") + # f.write(self._doc.write_string(sbol3.SORTED_NTRIPLES).strip()) + self.results["nt_filename"] = self.filename + # self._doc.write(self.filename, sbol3.SORTED_NTRIPLES) + with open(self.filename, "w") as f: + f.write(self._doc.write_string(sbol3.SORTED_NTRIPLES).strip()) + + except Exception as e: + self.status = ProtocolArtifactStatus.FAIL + self.results["exception"] = str(e) + + +class ProtocolDownstreamArtifact(ProtocolArtifact): + protocol_artifact: ProtocolNTuples = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.protocol_artifact = ( + kwargs["protocol_artifact"] if "protocol_artifact" in kwargs else None + ) + + def protocol(self): + return self.protocol_artifact._protocol + + def protocol_name(self): + return self.protocol_artifact.protocol_name + + +class ProtocolDiagram(ProtocolDownstreamArtifact): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def diagram_filename(self, filename_prefix: str) -> str: + return filename_prefix + ".diagram" + + def generate_artifact(self, harness: "ProtocolHarness"): + self.filename = ( + os.path.join( + harness.full_output_dir, + self.diagram_filename(harness.filename_prefix()), + ) + if self.filename is None + else self.filename + ) + try: + self.protocol().to_dot().render( + self.filename, cleanup=True, overwrite_source=True + ) + self.results["filename"] = self.filename + self.status = ProtocolArtifactStatus.PASS + except Exception as e: + self.results["exception"] = str(e) + self.status = ProtocolArtifactStatus.FAIL + + +class ProtocolRubric(ProtocolDownstreamArtifact): + filename: str = None + + def __init__(self, filename, *args, **kwargs): + super().__init__(*args, **kwargs) + self.filename = filename + + def generate_artifact(self, harness: "ProtocolHarness"): + # diff = "" + try: + diff = file_diff(self.filename, self.protocol_artifact.filename) + # print(f"Difference: {diff}") + assert filecmp.cmp( + self.protocol_artifact.filename, self.filename + ), "Files are not identical" + self.results["filename"] = self.filename + self.status = ProtocolArtifactStatus.PASS + except Exception as e: + self.results["exception"] = str(e) + self.status = ProtocolArtifactStatus.FAIL + self.results["diff"] = diff + + +class ProtocolExecutionNTuples(ProtocolDownstreamArtifact): + agent: Union[sbol3.Agent, str] = None + execution: ProtocolExecution = None + execution_id: str = None + parameter_values: List[ParameterValue] = None + execution_engine: ExecutionEngine = None + specializations: List[BehaviorSpecialization] = None + dataset_filename: str = None + execution_kwargs: Dict[str, Any] = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.agent = ( + kwargs["agent"] if "agent" in kwargs else sbol3.Agent("labop_harness") + ) + if isinstance(self.agent, str): + self.agent = sbol3.Agent(self.agent) + + self.execution_id = ( + kwargs["execution_id"] if "execution_id" in kwargs else self.execution_id + ) + self.parameter_values = ( + kwargs["parameter_values"] + if "parameter_values" in kwargs + else self.parameter_values + ) + + self.dataset_filename = ( + kwargs["dataset_filename"] + if "dataset_filename" in kwargs + else "dataset.xslx" + ) + self.specializations = ( + kwargs["specializations"] + if "specializations" in kwargs + else self.specializations + ) + self.execution_kwargs = ( + kwargs["execution_kwargs"] if "execution_kwargs" in kwargs else {} + ) + self.execution_engine = None + + def _execution_engine(self, output_dir) -> ExecutionEngine: + specializations = [s.specialization for s in self.specializations] + kwargs = { + "out_dir": output_dir, + "specializations": specializations, + "failsafe": False, + "sample_format": "xarray", + "dataset_file": self.dataset_filename, + } + kwargs.update(self.execution_kwargs) + + return ExecutionEngine(**kwargs) + + def ntuples_filename(self, filename_prefix) -> str: + return filename_prefix + ".nt" + + def get_execution_id(self) -> str: + if self.execution_id is None: + self.execution_id = ( + (f"harness_execution_{self.protocol_name()}_{datetime.now()}") + .replace(" ", "_") + .replace("-", "_") + .replace(":", "_") + .replace(".", "_") + ) + return self.execution_id + + def generate_artifact(self, harness: "ProtocolHarness"): + self.execution_engine = self._execution_engine(harness.full_output_dir) + self.execution: ProtocolExecution = self.execution_engine.execute( + self.protocol(), + self.agent, + id=self.get_execution_id(), + parameter_values=self.parameter_values, + ) + + try: + self.filename = ( + os.path.join( + harness.full_output_dir, + self.ntuples_filename(harness.filename_prefix()), + ) + if self.filename is None + else self.filename + ) + with open(self.filename, "w") as f: + f.write( + self.protocol().document.write_string(sbol3.SORTED_NTRIPLES).strip() + ) + self.results["filename"] = self.filename + self.status = ProtocolArtifactStatus.PASS + + except Exception as e: + self.status = ProtocolArtifactStatus.FAIL + self.results["exception"] = str(e) + + +class ProtocolExecutionDownstreamArtifact(ProtocolDownstreamArtifact): + protocol_execution_artifact: ProtocolExecutionNTuples = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.protocol_execution_artifact = ( + kwargs["protocol_execution_artifact"] + if "protocol_execution_artifact" in kwargs + else None + ) + + +class ProtocolExecutionRubric(ProtocolExecutionDownstreamArtifact): + filename: str = None + overwrite_rubric: bool = False + + def __init__(self, filename, *args, **kwargs): + super().__init__(*args, **kwargs) + self.filename = filename + + self.overwrite_rubric = ( + kwargs["overwrite_rubric"] + if "overwrite_rubric" in kwargs + else self.overwrite_rubric + ) + + def generate_artifact(self, harness: "ProtocolHarness"): + diff = "" + try: + if self.overwrite_rubric: + l.warn( + f"Overwriting rubric at: {self.filename} with {self.protocol_execution_artifact.filename}" + ) + shutil.copyfile( + self.protocol_execution_artifact.filename, self.filename + ) + + diff = file_diff(self.filename, self.protocol_execution_artifact.filename) + + # print(f"Difference: {diff}") + assert filecmp.cmp( + self.protocol_execution_artifact.filename, self.filename + ), "Files are not identical" + self.results["filename"] = self.filename + self.status = ProtocolArtifactStatus.PASS + except Exception as e: + self.results["exception"] = str(e) + self.status = ProtocolArtifactStatus.FAIL + self.results["diff"] = diff + + +class ProtocolExecutionDiagram(ProtocolExecutionDownstreamArtifact): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def diagram_filename(self, filename_prefix: str) -> str: + return filename_prefix + ".execution.diagram" + + def generate_artifact( + self, + harness: "ProtocolHarness", + ): + self.filename = ( + os.path.join( + harness.full_output_dir, + self.diagram_filename(harness.filename_prefix()), + ) + if self.filename is None + else self.filename + ) + try: + self.protocol_execution_artifact.execution.to_dot().render( + self.filename, + cleanup=True, + overwrite_source=True, + ) + self.results["filename"] = self.filename + self.status = ProtocolArtifactStatus.PASS + except Exception as e: + self.results["exception"] = str(e) + self.status = ProtocolArtifactStatus.FAIL + + +class ProtocolSampleTrace(ProtocolExecutionDownstreamArtifact): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def generate_artifact(self, harness: "ProtocolHarness"): + try: + results = glob.glob(f"{harness.full_output_dir}/sample_graph*") + sample_trace_dir = os.path.join(harness.full_output_dir, "sample_traces") + os.makedirs(sample_trace_dir, exist_ok=True) + for i, result in enumerate(results): + # extension = mimetypes.guess_extension(magic.Magic(mime=True).from_file(result)) + shutil.move(result, sample_trace_dir) + self.filename = sample_trace_dir + self.status = ProtocolArtifactStatus.PASS + self.results["filename"] = self.filename + except Exception as e: + self.status = ProtocolArtifactStatus.FAIL + self.results["exception"] = str(e) + l.exception(f"Protocol Sample Trace failed: {e}") + + +class ProtocolSpecialization(ProtocolArtifact): + specialization: BehaviorSpecialization + + def __init__(self, specialization: BehaviorSpecialization) -> None: + super().__init__() + self.specialization = specialization + + def write_output(self, filename_prefix: str): + pass + + def generate_artifact(self, harness: "ProtocolHarness"): + try: + results = self.specialization.data + specialization_classname = self.specialization.__class__.__name__ + specialization_dir = os.path.join( + harness.full_output_dir, specialization_classname + ) + os.makedirs(specialization_dir, exist_ok=True) + for i, result in enumerate(results): + # extension = mimetypes.guess_extension(magic.Magic(mime=True).from_file(result)) + extension = "json" + result_file = os.path.join( + specialization_dir, + f"{specialization_classname}_result_{i}.{extension}", + ) + with open(result_file, "w") as f: + f.write(str(result)) + self.status = ProtocolArtifactStatus.PASS + self.results["filename"] = specialization_dir + except Exception as e: + self.status = ProtocolArtifactStatus.FAIL + self.results["exception"] = str(e) + l.exception(f"Protocol Specialization {self.specialization} failed: {e}") + + +class ProtocolHarness: + namespace: str = None + protocol_name: str = None + protocol_long_name: str = None + protocol_version: str = None + protocol_description: str = None + entry_point: Callable + artifacts: List[ProtocolArtifact] = None + base_artifacts: List[ProtocolArtifact] = None + output_dir: str = None + base_dir: str = None + full_output_dir: str = None + libraries: List[str] = None + parameter_values: List[ParameterValue] = None + execution_id: str = None + agent: Union[sbol3.Agent, str] = None + execution_kwargs: Dict[str, Any] = None + _results: Dict[str, Any] = None + clean_output: bool = False + + def __init__(self, *args, **kwargs): + self.namespace = ( + kwargs["namespace"] if "namespace" in kwargs else "https://labop.io/" + ) + sbol3.set_namespace(self.namespace) + self.protocol_name = ( + kwargs["protocol_name"] if "protocol_name" in kwargs else "default_name" + ) + self.protocol_long_name = ( + kwargs["protocol_long_name"] + if "protocol_long_name" in kwargs + else "default name" + ) + + self.protocol_version = ( + kwargs["protocol_version"] if "protocol_version" in kwargs else "1.0" + ) + + self.protocol_description = ( + kwargs["protocol_description"] + if "protocol_description" in kwargs + else "default description" + ) + + self.clean_output = ( + kwargs["clean_output"] if "clean_output" in kwargs else self.clean_output + ) + + self.execution_kwargs = ( + kwargs["execution_kwargs"] if "execution_kwargs" in kwargs else {} + ) + + self.base_dir = kwargs["base_dir"] if "base_dir" in kwargs else "." + self.output_dir = ( + kwargs["output_dir"] if "output_dir" in kwargs else "artifacts" + ) + self.full_output_dir = os.path.join(self.base_dir, self.output_dir) + + # Store outputs in full_output_dir + os.makedirs(self.full_output_dir, exist_ok=True) + l.info(f"Writing protocol artifacts to: {self.full_output_dir}") + + self.entry_point = kwargs["entry_point"] + self.artifacts = kwargs["artifacts"] if "artifacts" in kwargs else [] + + self.libraries = ( + kwargs["libraries"] + if "libraries" in kwargs + else [ + "liquid_handling", + "plate_handling", + "spectrophotometry", + "sample_arrays", + ] + ) + self.parameter_values = ( + kwargs["parameter_values"] if "parameter_values" in kwargs else [] + ) + self.execution_id = ( + kwargs["execution_id"] if "execution_id" in kwargs else self.execution_id + ) + self.agent = ( + kwargs["agent"] if "agent" in kwargs else sbol3.Agent("labop_harness") + ) + + if "base_artifacts" in kwargs: + self.base_artifacts = kwargs["base_artifacts"] + else: + protocol_artifact = ProtocolNTuples( + namespace=self.namespace, + protocol_name=self.protocol_name, + protocol_long_name=self.protocol_long_name, + protocol_version=self.protocol_version, + protocol_description=self.protocol_description, + ) + + dataset_filename = os.path.join( + self.full_output_dir, self.dataset_filename() + ) + + specializations = [ + a for a in self.artifacts if isinstance(a, ProtocolSpecialization) + ] + + protocol_execution_artifact = ProtocolExecutionNTuples( + protocol_artifact=protocol_artifact, + agent=self.agent, + execution_id=self.execution_id, + parameter_values=self.parameter_values, + specializations=specializations, + dataset_filename=dataset_filename, + execution_kwargs=self.execution_kwargs, + ) + sample_traces = [ + a for a in self.artifacts if isinstance(a, ProtocolSampleTrace) + ] + if len(sample_traces) == 0: + sample_traces = [ + ProtocolSampleTrace( + protocol_execution_artifact=protocol_execution_artifact + ) + ] + self.base_artifacts = ( + kwargs["base_artifacts"] + if "base_artifacts" in kwargs + else [ + protocol_artifact, + ProtocolDiagram(protocol_artifact=protocol_artifact), + protocol_execution_artifact, + ProtocolExecutionDiagram( + protocol_execution_artifact=protocol_execution_artifact + ), + ] + + sample_traces + ) + for a in self.artifacts: + if isinstance(a, ProtocolRubric): + a.protocol_artifact = protocol_artifact + for a in self.artifacts: + if isinstance(a, ProtocolExecutionRubric): + a.protocol_execution_artifact = protocol_execution_artifact + + self.all_artifacts = self.base_artifacts + self.artifacts + self._results = {} + + def filename_prefix(self) -> str: + return self.entry_point.__name__ + + def dataset_filename(self) -> str: + return self.filename_prefix() + ".data.xslx" + + def artifacts_summary(self, verbose=False) -> str: + summary = "" + for a in self.base_artifacts + self.artifacts: + summary += f" - {a.__class__.__name__}" + if verbose: + summary += f": {a.configuration_summary(verbose=verbose)}\n" + else: + summary += "\n" + summary = f""" +{'*'*80} + + LabOP Protocol Harness + + Configuration: + -------------- + Artifacts: +{summary} +{'*'*80} + """ + return summary + + def artifacts_results_summary(self, verbose=False) -> str: + summary = "" + for a in self.base_artifacts + self.artifacts: + summary += f" {'-'*76}\n - {a.__class__.__name__} ({a.status}): \n" + if verbose: + summary += f" {'-'*76}\n{a.results_summary()}\n" + summary = f""" +{'*'*80} + + Harness Results Summary + + Artifacts: +{summary} +{'*'*80} + """ + return summary + + def artifacts_of_type(self, artifact_type) -> List[ProtocolArtifact]: + try: + ea = [a for a in self.all_artifacts if isinstance(a, artifact_type)] + return ea + except Exception as e: + l.exception(f"Could not find an artifacts of type {artifact_type}: {e}") + raise e + + def run(self, verbose=False): + self.initialize(verbose=verbose) + self.main(verbose=verbose) + self.finalize(verbose=verbose) + + def initialize(self, verbose=False): + if self.clean_output: + l.warn(f"Deleting contents of output directory: {self.full_output_dir}") + for root, dirs, files in os.walk(self.full_output_dir): + for f in files: + os.unlink(os.path.join(root, f)) + for d in dirs: + shutil.rmtree(os.path.join(root, d)) + l.info(self.artifacts_summary(verbose=verbose)) + + def errors(self) -> List[ProtocolArtifact]: + return [ + a for a in self.all_artifacts if a.status == ProtocolArtifactStatus.FAIL + ] + + def main(self, verbose=False): + artifact_order = [ + ProtocolNTuples, + ProtocolRubric, + ProtocolDiagram, + ProtocolExecutionNTuples, + ProtocolExecutionRubric, + ProtocolExecutionDiagram, + ProtocolSampleTrace, + ProtocolSpecialization, + ] + for a_type in artifact_order: + for a in self.artifacts_of_type(a_type): + a.generate_artifact(self) + + def finalize(self, verbose=False): + summary = self.artifacts_results_summary(verbose=verbose) + + l.info(summary) + results_file = os.path.join(self.full_output_dir, "harness.out") + with open(results_file, "w") as f: + f.write(self.artifacts_results_summary(verbose=True)) + + +class ProtocolLoader: + filename: str = None + entrypoint_name: str = None + module = None + + def __init__(self, filename: str, entrypoint_name: str): + self.filename = filename + self.entrypoint_name = entrypoint_name + + self.module = self.load_module() + + def load_module(self): + loader = SourceFileLoader(self.entrypoint_name, self.filename) + spec = spec_from_loader(loader.name, loader) + module = module_from_spec(spec) + loader.exec_module(module) + return module + + def generate_protocol(self, doc: sbol3.Document, protocol: Protocol) -> Protocol: + entrypoint = getattr(self.module, self.entrypoint_name) + return entrypoint(doc, protocol) diff --git a/labop/lab_interface.py b/labop/execution/lab_interface.py similarity index 96% rename from labop/lab_interface.py rename to labop/execution/lab_interface.py index 5f2a7d2f..04612448 100644 --- a/labop/lab_interface.py +++ b/labop/execution/lab_interface.py @@ -4,8 +4,8 @@ import xarray as xr from numpy import nan -from .data import serialize_sample_format -from .strings import Strings +from ..data import serialize_sample_format +from ..strings import Strings class LabInterface: diff --git a/labop/lib/liquid_handling.py b/labop/lib/liquid_handling.py index 81d844af..809d9146 100644 --- a/labop/lib/liquid_handling.py +++ b/labop/lib/liquid_handling.py @@ -89,6 +89,7 @@ p.add_input("direction", "http://bioprotocols.org/uml#ValueSpecification") p.add_input("diluent", sbol3.SBOL_COMPONENT) p.add_input("amount", sbol3.OM_MEASURE) # Must be volume +p.add_input("dilution_factor", sbol3.OM_MEASURE, optional=True) doc.add(p) diff --git a/labop/lib/liquid_handling.ttl b/labop/lib/liquid_handling.ttl index 8babd003..70ab21ad 100644 --- a/labop/lib/liquid_handling.ttl +++ b/labop/lib/liquid_handling.ttl @@ -71,7 +71,8 @@ ns1:ownedParameter