From 558ebc8ab5d7c8ea90bdc4e37d68a9b7d78bbd87 Mon Sep 17 00:00:00 2001 From: Daniel Bryce Date: Thu, 19 Oct 2023 07:47:29 -0500 Subject: [PATCH] setup protocol harness --- .gitignore | 1 + challenging-interlab-growth-curve.py | 2 +- docs/reference/library.md | 4 +- .../multicolor-particle-calibration.py | 130 +- .../multicolor-particle-calibration-small.py | 3 +- .../single-particle-calibration.py | 15 +- .../singlecolor-particle-calibration.py | 15 +- .../golden_gate_assembly.py | 278 ++-- .../protocols/growth-curve/growth_curve.py | 797 +++++----- .../iGEM/challenging-interlab-growth-curve.py | 24 +- examples/protocols/iGEM/interlab-endpoint.py | 1230 +++++++-------- examples/protocols/iGEM/interlab-exp1.py | 1220 +++++++-------- examples/protocols/iGEM/interlab-exp1_MI.py | 2 +- examples/protocols/iGEM/interlab-exp2.py | 1226 +++++++-------- examples/protocols/iGEM/interlab-exp2_MI.py | 2 +- .../protocols/iGEM/interlab-exp2_from1.py | 2 +- .../protocols/iGEM/interlab-exp3_challenge.py | 1342 +++++++++-------- .../iGEM/interlab-growth-curve.ipynb | 2 +- .../protocols/iGEM/interlab-growth-curve.py | 2 +- .../protocols/iGEM/interlab-timepoint-B.py | 2 +- .../protocols/iGEM/interlab-timepoint-B_AV.py | 2 +- .../iGEM/revised-interlab-growth-curve.py | 19 +- examples/protocols/ludox/LUDOX_protocol.py | 71 +- .../ludox/artifacts/test_LUDOX_markdown.md | 115 +- .../opentrons_ludox_example.py | 55 +- .../opentrons-pcr/opentrons_pcr_example.py | 17 +- .../opentrons-toy/opentrons_toy_protocol.py | 53 +- .../pH_calibration/pH_calibration.py | 7 +- interlab-growth-curve.ipynb | 2 +- interlab-growth-curve.py | 2 +- labop/__init__.py | 11 +- labop/activity_edge_flow.py | 3 +- labop/call_behavior_execution.py | 22 +- labop/constants.py | 35 + labop/execution/__init__.py | 4 + .../execution}/behavior_dynamics.py | 4 +- labop/{ => execution}/execution_context.py | 7 +- labop/{ => execution}/execution_engine.py | 29 +- .../{ => execution}/execution_engine_utils.py | 3 +- labop/execution/harness.py | 728 +++++++++ labop/{ => execution}/lab_interface.py | 4 +- labop/lib/liquid_handling.py | 1 + labop/lib/liquid_handling.ttl | 30 +- labop/parameter_value.py | 3 + labop/primitive.py | 15 +- labop/protocol.py | 89 +- labop/protocol_execution.py | 20 +- labop/sample_mask.py | 2 +- labop/utils/harness.py | 305 ---- labop_convert/behavior_specialization.py | 5 +- .../emeraldcloud/ecl_specialization.py | 202 ++- .../markdown/markdown_specialization.py | 170 ++- .../opentrons/opentrons_specialization.py | 3 +- notebooks/Autoprotocol.ipynb | 2 +- notebooks/ExecutionRecord.ipynb | 2 +- notebooks/Opentrons.ipynb | 7 +- notebooks/UItemplate.ipynb | 2 +- notebooks/data_link.ipynb | 2 +- notebooks/labop_author_demo.ipynb | 6 +- notebooks/labop_demo.ipynb | 6 +- notebooks/markdown.ipynb | 2 +- notebooks/ph_calibration.ipynb | 2 +- revised-interlab-growth-curve.py | 2 +- test/DISABLED_execution.py | 7 +- test/test_LUDOX_protocol.py | 95 +- test/test_decision_nodes.py | 160 +- test/test_examples.py | 31 +- test/test_igem_examples.py | 89 +- test/test_inputs.py | 2 +- test/test_opentrons.py | 97 +- test/test_outputs.py | 2 +- test/test_protocol_outputs.py | 50 +- test/test_sampledata.py | 2 +- test/test_samplemap.py | 2 +- test/test_subprotocols.py | 51 +- test/testfiles/decision_node_test.nt | 10 +- uml/activity_edge.py | 8 +- uml/behavior.py | 24 +- uml/object_node.py | 3 + 79 files changed, 4839 insertions(+), 4164 deletions(-) create mode 100644 labop/execution/__init__.py rename {labop_convert => labop/execution}/behavior_dynamics.py (99%) rename labop/{ => execution}/execution_context.py (99%) rename labop/{ => execution}/execution_engine.py (97%) rename labop/{ => execution}/execution_engine_utils.py (96%) create mode 100644 labop/execution/harness.py rename labop/{ => execution}/lab_interface.py (96%) delete mode 100644 labop/utils/harness.py 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 , , , - ; + , + ; sbol:description "Serial Dilution" ; sbol:displayId "SerialDilution" ; sbol:hasNamespace . @@ -957,6 +958,33 @@ ns1:integerValue 1 ; sbol:displayId "LiteralInteger2" . + a ns1:OrderedPropertyValue, + sbol:Identified ; + ns1:indexValue 4 ; + ns1:propertyValue ; + sbol:displayId "OrderedPropertyValue5" . + + a ns1:Parameter, + sbol:Identified ; + ns1:direction ns1:in ; + ns1:isOrdered true ; + ns1:isUnique true ; + ns1:lowerValue ; + ns1:type om:Measure ; + ns1:upperValue ; + sbol:displayId "Parameter1" ; + sbol:name "dilution_factor" . + + a ns1:LiteralInteger, + sbol:Identified ; + ns1:integerValue 1 ; + sbol:displayId "LiteralInteger1" . + + a ns1:LiteralInteger, + sbol:Identified ; + ns1:integerValue 0 ; + sbol:displayId "LiteralInteger2" . + a ns1:OrderedPropertyValue, sbol:Identified ; ns1:indexValue 0 ; diff --git a/labop/parameter_value.py b/labop/parameter_value.py index 12acac9e..62c0f230 100644 --- a/labop/parameter_value.py +++ b/labop/parameter_value.py @@ -21,6 +21,9 @@ def __hash__(self): def get_parameter(self): return self.parameter.lookup().property_value + def get_name(self): + return self.get_parameter().name + @staticmethod def parameter_value_map( parameter_values: List["ParameterValue"], diff --git a/labop/primitive.py b/labop/primitive.py index 90d8ce37..18625f99 100644 --- a/labop/primitive.py +++ b/labop/primitive.py @@ -12,7 +12,6 @@ from . import inner from .dataset import Dataset -from .lab_interface import LabInterface from .library import loaded_libraries from .sample_array import SampleArray from .sample_data import SampleData @@ -136,7 +135,7 @@ def compute_output( """ Compute the value for parameter given the inputs. This default function will be overridden for specific primitives. :param self: - :param inputs: list of labop.ParameterValue + :param inputs: list of ParameterValue :param parameter: Parameter needing value :return: value """ @@ -312,6 +311,7 @@ def measure_absorbance_compute_output( ): samples = input_map["samples"] wl = input_map["wavelength"] + from labop.execution.lab_interface import LabInterface measurements = LabInterface.measure_absorbance( samples, wl.value, sample_format @@ -337,6 +337,7 @@ def measure_fluorescence_compute_output( exwl = input_map["excitationWavelength"] emwl = input_map["emissionWavelength"] bandpass = input_map["emissionBandpassWidth"] + from labop.execution.lab_interface import LabInterface measurements = LabInterface.measure_fluorescence( samples, @@ -405,7 +406,7 @@ def compute_metadata_compute_output( and parameter.type == "http://bioprotocols.org/labop#SampleMetadata" ): for_samples = input_map["for_samples"] - metadata = labop.SampleMetadata.from_sample_graph(for_samples, engine) + metadata = SampleMetadata.from_sample_graph(for_samples, engine) return metadata def transfer_by_map_compute_output( @@ -424,9 +425,7 @@ def transfer_by_map_compute_output( spec = source.container_type contents = self.transfer_out(source, target, plan, sample_format) name = f"{parameter.name}" - result = labop.SampleArray( - name=name, container_type=spec, contents=contents - ) + result = SampleArray(name=name, container_type=spec, contents=contents) elif ( parameter.name == "destinationResult" and parameter.type == "http://bioprotocols.org/labop#SampleCollection" @@ -438,9 +437,7 @@ def transfer_by_map_compute_output( spec = source.container_type contents = self.transfer_in(source, target, plan, sample_format) name = f"{parameter.name}" - result = labop.SampleArray( - name=name, container_type=spec, contents=contents - ) + result = SampleArray(name=name, container_type=spec, contents=contents) return result primitive_to_output_function = { diff --git a/labop/protocol.py b/labop/protocol.py index 522c85d8..a6a3d372 100644 --- a/labop/protocol.py +++ b/labop/protocol.py @@ -21,6 +21,7 @@ ObjectNode, ValueSpecification, ) +from uml.call_behavior_action import CallBehaviorAction from uml.utils import WellFormednessError, WellFormednessIssue from . import inner @@ -102,6 +103,22 @@ def primitive_step(self, primitive: Primitive, **input_pin_map): self.last_step = pe # update the last step return pe + def make_decision_input_activity( + self, + decision_input_behavior: Behavior, + decision_input_source: ActivityNode = None, + ) -> CallBehaviorAction: + input_pin_map = ( + {"decision_input": decision_input_source} + if decision_input_source is not None + else {} + ) + + decision_input = self.execute_primitive( + decision_input_behavior, **input_pin_map + ) + return decision_input + def make_decision_node( self, primary_incoming_node: ActivityNode, @@ -121,37 +138,36 @@ def make_decision_node( """ assert primary_incoming_node - primary_incoming_flow = ( - ControlFlow(source=primary_incoming_node) - if isinstance(primary_incoming_node, ControlNode) - else ObjectFlow(source=primary_incoming_node) - ) - self.edges.append(primary_incoming_flow) - decision_input = None - - if decision_input_behavior: - input_pin_map = {} - decision_input_control = None - if decision_input_source: - input_pin_map["decision_input"] = decision_input_source - if primary_incoming_node: - if isinstance(primary_incoming_node, ObjectNode): - input_pin_map["primary_input"] = primary_incoming_node - else: - # Make a ControlFlow so that decision_input executes first - decision_input_control = ControlFlow(source=primary_incoming_node) - - decision_input = self.execute_primitive( - decision_input_behavior, **input_pin_map + decision_input = ( + self.make_decision_input_activity( + decision_input_behavior, + decision_input_source=decision_input_source, ) - if decision_input_control: - decision_input_control.target = decision_input - self.edges.append(decision_input_control) + if decision_input_behavior is not None + else None + ) + # Link decision input flow decision_input_flow = None - if decision_input_source: - decision_input_flow = ObjectFlow(source=decision_input_source) + + if decision_input: + # if decision_input_source is not None: + # # link decision_input_source to decision_input + # decision_input_flow = ObjectFlow( + # source=decision_input_source, target=decision_input.input_pin("decision_input") + # ) + # self.edges.append(decision_input_flow) + + # Order decision input after primary incoming node + decision_input_control = ControlFlow( + source=primary_incoming_node, target=decision_input + ) + self.edges.append(decision_input_control) + elif decision_input_source is not None: + decision_input_flow = ObjectFlow( + source=decision_input_source, target=decision + ) self.edges.append(decision_input_flow) decision = DecisionNode( @@ -161,15 +177,20 @@ def make_decision_node( self.nodes.append(decision) if decision_input: - # Flow that communicates the return value of the decision_input behavior execution to the decision + # Link Flow that communicates the return value of the decision_input behavior execution to the decision decision_input_to_decision_flow = ObjectFlow( source=decision_input.output_pin("return"), target=decision ) self.edges.append(decision_input_to_decision_flow) - primary_incoming_flow.target = decision - if decision_input_flow: - decision_input_flow.target = decision + # Control nodes and CallBehaviorAction nodes provide control flow. ActivityParameterNode and Pins provide object flows + primary_incoming_flow = ( + ControlFlow(source=primary_incoming_node, target=decision) + if isinstance(primary_incoming_node, ControlNode) + or isinstance(primary_incoming_node, CallBehaviorAction) + else ObjectFlow(source=primary_incoming_node) + ) + self.edges.append(primary_incoming_flow) # Make edges for outgoing_targets if outgoing_targets: @@ -256,3 +277,9 @@ def remove_duplicates(self): def auto_advance(self): return True + + def get_behaviors(self) -> List[Behavior]: + activities = [ + n.get_behavior() for n in self.nodes if isinstance(n, ActivityNode) + ] + return activities diff --git a/labop/protocol_execution.py b/labop/protocol_execution.py index e27d3a39..38f7bda0 100644 --- a/labop/protocol_execution.py +++ b/labop/protocol_execution.py @@ -26,10 +26,6 @@ from . import inner from .behavior_execution import BehaviorExecution from .call_behavior_execution import CallBehaviorExecution -from .execution_engine_utils import ( - JSONProtocolExecutionExtractor, - ProtocolExecutionExtractor, -) from .material import Material from .protocol import Protocol from .sample_data import SampleData @@ -39,6 +35,9 @@ class ProtocolExecution(inner.ProtocolExecution, BehaviorExecution): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + def get_protocol(self) -> Protocol: + return self.protocol.lookup() + def get_ordered_executions(self): protocol = self.protocol.lookup() try: @@ -341,8 +340,15 @@ def to_dot( def backtrace( self, stack=None, - extractor: ProtocolExecutionExtractor = JSONProtocolExecutionExtractor(), + extractor: "ProtocolExecutionExtractor" = None, ): + from labop.execution.execution_engine_utils import ( + JSONProtocolExecutionExtractor, + ) + + if extractor is None: + extractor = JSONProtocolExecutionExtractor() + stack = self.executions if stack is None else stack if len(stack) == 0: return set([]), [] @@ -358,6 +364,10 @@ def to_json(self): """ Convert Protocol Execution to JSON """ + from labop.execution.execution_engine_utils import ( + JSONProtocolExecutionExtractor, + ) + p_json = self.backtrace(extractor=JSONProtocolExecutionExtractor())[1] return json.dumps(p_json) diff --git a/labop/sample_mask.py b/labop/sample_mask.py index 3eb39ce3..91b3e888 100644 --- a/labop/sample_mask.py +++ b/labop/sample_mask.py @@ -10,7 +10,7 @@ from .data import deserialize_sample_format, serialize_sample_format from .sample_collection import SampleCollection from .strings import Strings -from .utils import contiguous_coordinates +from .utils.plate_coordinates import contiguous_coordinates class SampleMask(inner.SampleMask, SampleCollection): diff --git a/labop/utils/harness.py b/labop/utils/harness.py deleted file mode 100644 index b75d8619..00000000 --- a/labop/utils/harness.py +++ /dev/null @@ -1,305 +0,0 @@ -import logging -import os -from abc import ABC -from datetime import datetime -from typing import Callable, List - -import sbol3 -from isort import file - -from labop.execution_engine import ExecutionEngine -from labop.library import import_library -from labop.parameter_value import ParameterValue -from labop.protocol import Protocol -from labop.utils.helpers import prepare_document -from labop_convert import BehaviorSpecialization -from labop_convert.behavior_specialization import BehaviorSpecialization - -l = logging.Logger(__file__) -l.setLevel(logging.INFO) -ConsoleOutputHandler = logging.StreamHandler() -l.addHandler(ConsoleOutputHandler) - - -class ProtocolArtifact(ABC): - summary: str = "" - - def summary(self): - return "" - - def results_summary(self): - return self._summary - - -class ProtocolDiagram(ProtocolArtifact): - pass - - -class ProtocolNTuples(ProtocolArtifact): - pass - - -class ProtocolExecutionNTuples(ProtocolArtifact): - pass - - -class ProtocolExecutionDiagram(ProtocolArtifact): - pass - - -class ProtocolSampleTrace(ProtocolArtifact): - pass - - -class ProtocolSpecialization(ProtocolArtifact): - specialization: BehaviorSpecialization - - def __init__(self, specialization: BehaviorSpecialization) -> None: - super().__init__() - self.specialization = specialization - - def write_output(self, filename_prefix: str): - pass - - -class ProtocolHarness: - entry_point: Callable - artifacts: List[ProtocolArtifact] = [] - base_artifacts: List[ProtocolArtifact] = [ - ProtocolNTuples(), - ProtocolDiagram(), - ProtocolExecutionDiagram(), - ProtocolSampleTrace(), - ProtocolExecutionNTuples(), - ] - namespace: str - protocol_name: str - protocol_long_name: str - protocol_version: str - protocol_description: str - output_dir: str = "artifacts" - libraries: List[str] = [ - "liquid_handling", - "plate_handling", - "spectrophotometry", - "sample_arrays", - ] - parameter_values: List[ParameterValue] = [] - execution_id: str = None - agent: sbol3.Agent = sbol3.Agent("labop_harness") - _doc: sbol3.Document = None - _protocol: Protocol = None - - def __init__(self, *args, **kwargs): - self.entry_point = kwargs["entry_point"] - self.artifacts = ( - kwargs["artifacts"] if "artifacts" in kwargs else self.artifacts - ) - self.artifacts = ( - kwargs["base_artifacts"] - if "base_artifacts" in kwargs - else self.base_artifacts - ) - self.namespace = kwargs["namespace"] - self.protocol_name = kwargs["protocol_name"] - self.protocol_long_name = kwargs["protocol_long_name"] - self.protocol_version = kwargs["protocol_version"] - self.protocol_description = kwargs["protocol_description"] - self.output_dir = ( - kwargs["output_dir"] if "output_dir" in kwargs else self.output_dir - ) - self.libraries = ( - kwargs["libraries"] if "libraries" in kwargs else self.libraries - ) - self.parameter_values = ( - kwargs["parameter_values"] - if "parameter_values" in kwargs - else self.parameter_values - ) - self.execution_id = ( - kwargs["execution_id"] if "execution_id" in kwargs else self.execution_id - ) - self.agent = kwargs["agent"] if "agent" in kwargs else self.agent - self._doc = None - self._protocol = None - - def import_libraries(self): - for library in self.libraries: - import_library(library) - - def generate_protocol(self) -> Protocol: - self.import_libraries() - self._protocol = Protocol(self.protocol_name) - self._protocol.name = self.protocol_long_name - self._protocol.version = self.protocol_version - self._protocol.description = self.protocol_description - self._doc.add(self._protocol) - self._protocol = self.entry_point(self._doc, self._protocol) - l.info("Validating and writing protocol") - v = self._doc.validate() - assert len(v) == 0, "".join(f"\n {e}" for e in v) - - return self._protocol - - def prepare_document(self) -> sbol3.Document: - self._doc = prepare_document(namespace=self.namespace) - return self._doc - - def filename_prefix(self) -> str: - return self.entry_point.__name__ - - def ntuples_filename(self) -> str: - return self.filename_prefix() + ".nt" - - def diagram_filename(self) -> str: - return self.filename_prefix() + ".diagram" - - def dataset_filename(self) -> str: - return self.filename_prefix() + ".data.xslx" - - def read_protocol(self, filename: str = None): - filename = self.ntuples_filename() if filename is None else filename - self.prepare_document() - self._doc.read(filename, "nt") - self._protocol = doc.find(f"{self.namespace}{self.protocol_name}") - - return self._protocol, self._doc - - def execution_engine( - self, - specializations: List[BehaviorSpecialization], - dataset_filename: str, - ) -> ExecutionEngine: - return ExecutionEngine( - out_dir=self.output_dir, - specializations=specializations, - failsafe=False, - sample_format="xarray", - dataset_file=dataset_filename, - ) - - 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 artifacts_summary(self) -> str: - summary = "" - for a in self.artifacts: - summary += f" - {a.__class__.__name__}: {a.summary()}\n" - return summary - - def artifacts_results_summary(self) -> str: - summary = "" - for a in self.artifacts: - summary += f" - {a.__class__.__name__}: {a.results_summary()}\n" - return summary - - def run(self, base_dir="."): - l.info( - f""" -{'*'*80} - - LabOP Protocol Harness - - Configuration: - -------------- - Artifacts: -{self.artifacts_summary()} -{'*'*80} - """ - ) - full_output_dir = os.path.join(base_dir, self.output_dir) - # Store outputs in full_output_dir - os.makedirs(full_output_dir, exist_ok=True) - l.info("Writing protocol artifacts to: {full_output_dir}") - - all_artifacts = self.base_artifacts + self.artifacts - - generate_nt_file = any( - a for a in all_artifacts if isinstance(a, ProtocolNTuples) - ) - nt_filename = os.path.join(full_output_dir, self.ntuples_filename()) - - # 1) Get the self._protocol either by generating it or reading it - if generate_nt_file: - self.prepare_document() - self.generate_protocol() - else: - l.info(f"Bypassing Protocol Generation, looking for existing .nt file ...") - if os.path.exists(nt_filename): - l.info(f"Found .nt file: {nt_filename}") - self.read_protocol() - - # 2) Create any protocol-specific artifacts - if generate_nt_file: - with open(nt_filename, "w") as f: - l.info(f"Saving protocol [{nt_filename}].") - f.write(self._doc.write_string(sbol3.SORTED_NTRIPLES).strip()) - if any(a for a in all_artifacts if isinstance(a, ProtocolDiagram)): - self._protocol.to_dot().render( - os.path.join(full_output_dir, self.diagram_filename()), - cleanup=True, - ) - - # 3) Generate execution-specific artifacts - sample_trace = any( - a for a in all_artifacts if isinstance(a, ProtocolSampleTrace) - ) - dataset_filename = ( - os.path.join(full_output_dir, self.dataset_filename()) - if sample_trace - else None - ) - - specializations = [ - a for a in all_artifacts if isinstance(a, ProtocolSpecialization) - ] - - ee = self.execution_engine( - [s.specialization for s in specializations], dataset_filename - ) - - execution = ee.execute( - self._protocol, - self.agent, - id=self.get_execution_id(), - parameter_values=self.parameter_values, - ) - - if any(a for a in all_artifacts if isinstance(a, ProtocolExecutionNTuples)): - with open(nt_filename, "w") as f: - f.write(self._doc.write_string(sbol3.SORTED_NTRIPLES).strip()) - - for specialization in specializations: - results = specialization.output() - specialization_classname = specialization.__class__.__name__ - specialization_dir = os.path.join(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(result) - - l.info( - f""" -{'*'*80} - - Harness Results Summary - - Artifacts: -{self.artifacts_results_summary()} -{'*'*80} - """ - ) diff --git a/labop_convert/behavior_specialization.py b/labop_convert/behavior_specialization.py index 03671da2..4b1b20ea 100644 --- a/labop_convert/behavior_specialization.py +++ b/labop_convert/behavior_specialization.py @@ -6,7 +6,6 @@ import tyto -from labop import Protocol, ProtocolExecution from uml import CallBehaviorAction l = logging.getLogger(__file__) @@ -82,7 +81,7 @@ def output(self) -> List[Any]: results = self.data return results - def process(self, record, execution: ProtocolExecution, timepoint="start"): + def process(self, record, execution: "ProtocolExecution", timepoint="start"): try: node = record.node.lookup() if not isinstance(node, CallBehaviorAction): @@ -90,6 +89,8 @@ def process(self, record, execution: ProtocolExecution, timepoint="start"): elif node.get_parent().identity in self.mapped_subprotocols: return + from labop import Protocol + # Subprotocol specializations behavior = node.behavior.lookup() if ( diff --git a/labop_convert/emeraldcloud/ecl_specialization.py b/labop_convert/emeraldcloud/ecl_specialization.py index 2ecfc510..82588c3a 100644 --- a/labop_convert/emeraldcloud/ecl_specialization.py +++ b/labop_convert/emeraldcloud/ecl_specialization.py @@ -106,7 +106,7 @@ def handle_process_failure(self, record, exception): super().handle_process_failure(record, exception) self.script_steps.append(f"# Failure processing record: {record.identity}") - def on_begin(self, ex: labop.ProtocolExecution): + def on_begin(self, ex: "ProtocolExecution"): protocol = self.execution.protocol.lookup() self.data = [] @@ -142,13 +142,13 @@ def _compile_script(self): return script def define_container( - self, record: labop.ActivityNodeExecution, ex: labop.ProtocolExecution + self, record: "ActivityNodeExecution", ex: "ProtocolExecution" ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() - spec = parameter_value_map["specification"]["value"] - samples = parameter_value_map["samples"]["value"] + spec = parameter_value_map["specification"] + samples = parameter_value_map["samples"] name = spec.name if spec.name else spec.display_id container_types = self.resolve_container_spec(spec) @@ -167,17 +167,17 @@ def define_container( def vortex( self, - record: labop.ActivityNodeExecution, - execution: labop.ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() duration = None if "duration" in parameter_value_map: duration_measure = ecl_measure( - parameter_value_map["duration"]["value"], use_star=True + parameter_value_map["duration"], use_star=True ) - samples = parameter_value_map["samples"]["value"] + samples = parameter_value_map["samples"] spec = samples.get_container_type() if str(spec) in self.resolutions: sample = self.resolutions[str(spec)] @@ -197,26 +197,20 @@ def vortex( else: self.script_steps += [text] - def time_wait( - self, record: labop.ActivityNodeExecution, ex: labop.ProtocolExecution - ): + def time_wait(self, record: "ActivityNodeExecution", ex: "ProtocolExecution"): results = {} call = record.call.lookup() parameter_value_map = call.parameter_value_map() - value = parameter_value_map["amount"]["value"].value - units = parameter_value_map["amount"]["value"].unit + value = parameter_value_map["amount"].value + units = parameter_value_map["amount"].unit self.script_steps += [f"time.sleep(value)"] - def provision( - self, record: labop.ActivityNodeExecution, ex: labop.ProtocolExecution - ): + def provision(self, record: "ActivityNodeExecution", ex: "ProtocolExecution"): results = {} call = record.call.lookup() parameter_value_map = call.parameter_value_map() - destination = parameter_value_map["destination"]["value"] - resource = source = self.resolutions[ - parameter_value_map["resource"]["value"].identity - ] + destination = parameter_value_map["destination"] + resource = source = self.resolutions[parameter_value_map["resource"].identity] if type(destination) is labop.SampleMask: dest_container = destination.source.lookup().container_type.lookup() @@ -225,7 +219,7 @@ def provision( dest_container = destination.container_type.lookup() dest_wells = None - amount = ecl_measure(parameter_value_map["amount"]["value"]) + amount = ecl_measure(parameter_value_map["amount"]) text = ecl_transfer( source, f'"{dest_container}"', amount, dest_wells=dest_wells ) @@ -235,15 +229,13 @@ def provision( # else: self.script_steps += [text] - def transfer_to( - self, record: labop.ActivityNodeExecution, ex: labop.ProtocolExecution - ): + def transfer_to(self, record: "ActivityNodeExecution", ex: "ProtocolExecution"): results = {} call = record.call.lookup() parameter_value_map = call.parameter_value_map() - source = parameter_value_map["source"]["value"] - destination = parameter_value_map["destination"]["value"] - amount = ecl_measure(parameter_value_map["amount"]["value"]) + source = parameter_value_map["source"] + destination = parameter_value_map["destination"] + amount = ecl_measure(parameter_value_map["amount"]) if type(source) is labop.SampleMask: source_container = source.source.lookup().container_type.lookup() @@ -278,35 +270,33 @@ def transfer_to( # else: self.script_steps += [text] - def transfer_by_map( - self, record: labop.ActivityNodeExecution, ex: labop.ProtocolExecution - ): + def transfer_by_map(self, record: "ActivityNodeExecution", ex: "ProtocolExecution"): results = {} call = record.call.lookup() parameter_value_map = call.parameter_value_map() - destination = parameter_value_map["destination"]["value"] - source = parameter_value_map["source"]["value"] - plan = parameter_value_map["plan"]["value"] - temperature = parameter_value_map["temperature"]["value"] - value = parameter_value_map["amount"]["value"].value + destination = parameter_value_map["destination"] + source = parameter_value_map["source"] + plan = parameter_value_map["plan"] + temperature = parameter_value_map["temperature"] + value = parameter_value_map["amount"].value def plate_coordinates( - self, record: labop.ActivityNodeExecution, ex: labop.ProtocolExecution + self, record: "ActivityNodeExecution", ex: "ProtocolExecution" ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() - source = parameter_value_map["source"]["value"] - coords = parameter_value_map["coordinates"]["value"] - samples = parameter_value_map["samples"]["value"] + source = parameter_value_map["source"] + coords = parameter_value_map["coordinates"] + samples = parameter_value_map["samples"] def measure_absorbance( - self, record: labop.ActivityNodeExecution, ex: labop.ProtocolExecution + self, record: "ActivityNodeExecution", ex: "ProtocolExecution" ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() - wavelength = ecl_measure(parameter_value_map["wavelength"]["value"]) - samples = parameter_value_map["samples"]["value"] + wavelength = ecl_measure(parameter_value_map["wavelength"]) + samples = parameter_value_map["samples"] if type(samples) is labop.SampleMask: samples = samples.source.lookup() @@ -324,21 +314,21 @@ def measure_absorbance( self.script_steps += [text] def measure_fluorescence( - self, record: labop.ActivityNodeExecution, ex: labop.ProtocolExecution + self, record: "ActivityNodeExecution", ex: "ProtocolExecution" ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() - excitation = ecl_measure(parameter_value_map["excitationWavelength"]["value"]) - emission = ecl_measure(parameter_value_map["emissionWavelength"]["value"]) - bandpass = ecl_measure(parameter_value_map["emissionBandpassWidth"]["value"]) - samples = parameter_value_map["samples"]["value"] + excitation = ecl_measure(parameter_value_map["excitationWavelength"]) + emission = ecl_measure(parameter_value_map["emissionWavelength"]) + bandpass = ecl_measure(parameter_value_map["emissionBandpassWidth"]) + samples = parameter_value_map["samples"] timepoints = ( - parameter_value_map["timepoints"]["value"] + parameter_value_map["timepoints"] if "timepoints" in parameter_value_map else None ) - measurements = parameter_value_map["measurements"]["value"] + measurements = parameter_value_map["measurements"] if type(samples) is labop.SampleMask: samples = samples.source.lookup() @@ -353,80 +343,70 @@ def measure_fluorescence( ]""" self.script_steps += [text] - def define_rack( - self, record: labop.ActivityNodeExecution, ex: labop.ProtocolExecution - ): + def define_rack(self, record: "ActivityNodeExecution", ex: "ProtocolExecution"): call = record.call.lookup() parameter_value_map = call.parameter_value_map() - spec = parameter_value_map["specification"]["value"] - slots = parameter_value_map["slots"]["value"] + spec = parameter_value_map["specification"] + slots = parameter_value_map["slots"] def load_container_in_rack( - self, record: labop.ActivityNodeExecution, ex: labop.ProtocolExecution + self, record: "ActivityNodeExecution", ex: "ProtocolExecution" ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() container: labop.ContainerSpec = parameter_value_map["container"]["value"] coords: str = ( - parameter_value_map["coordinates"]["value"] + parameter_value_map["coordinates"] if "coordinates" in parameter_value_map else "A1" ) - slots: labop.SampleCollection = parameter_value_map["slots"]["value"] - samples: labop.SampleMask = parameter_value_map["samples"]["value"] + slots: "SampleCollection" = parameter_value_map["slots"] + samples: labop.SampleMask = parameter_value_map["samples"] def load_container_on_instrument( - self, record: labop.ActivityNodeExecution, ex: labop.ProtocolExecution + self, record: "ActivityNodeExecution", ex: "ProtocolExecution" ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() - container_spec: labop.ContainerSpec = parameter_value_map["specification"][ - "value" - ] + container_spec: labop.ContainerSpec = parameter_value_map["specification"] slots: str = ( - parameter_value_map["slots"]["value"] - if "slots" in parameter_value_map - else "A1" + parameter_value_map["slots"] if "slots" in parameter_value_map else "A1" ) - instrument: sbol3.Agent = parameter_value_map["instrument"]["value"] - samples: labop.SampleArray = parameter_value_map["samples"]["value"] + instrument: sbol3.Agent = parameter_value_map["instrument"] + samples: labop.SampleArray = parameter_value_map["samples"] - def load_racks( - self, record: labop.ActivityNodeExecution, ex: labop.ProtocolExecution - ): + def load_racks(self, record: "ActivityNodeExecution", ex: "ProtocolExecution"): call = record.call.lookup() node = record.node.lookup() parameter_value_map = call.parameter_value_map() coords: str = ( - parameter_value_map["coordinates"]["value"] + parameter_value_map["coordinates"] if "coordinates" in parameter_value_map else "1" ) - rack: labop.ContainerSpec = parameter_value_map["rack"]["value"] + rack: labop.ContainerSpec = parameter_value_map["rack"] - def configure_robot( - self, record: labop.ActivityNodeExecution, ex: labop.ProtocolExecution - ): + def configure_robot(self, record: "ActivityNodeExecution", ex: "ProtocolExecution"): call = record.call.lookup() parameter_value_map = call.parameter_value_map() - instrument = parameter_value_map["instrument"]["value"] - mount = parameter_value_map["mount"]["value"] + instrument = parameter_value_map["instrument"] + mount = parameter_value_map["mount"] def pcr( self, - record: labop.ActivityNodeExecution, - execution: labop.ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() - cycles = parameter_value_map["cycles"]["value"] - annealing_temp = parameter_value_map["annealing_temp"]["value"] - extension_temp = parameter_value_map["extension_temp"]["value"] - denaturation_temp = parameter_value_map["denaturation_temp"]["value"] - annealing_time = parameter_value_map["annealing_time"]["value"] - extension_time = parameter_value_map["extension_time"]["value"] - denaturation_time = parameter_value_map["denaturation_time"]["value"] + cycles = parameter_value_map["cycles"] + annealing_temp = parameter_value_map["annealing_temp"] + extension_temp = parameter_value_map["extension_temp"] + denaturation_temp = parameter_value_map["denaturation_temp"] + annealing_time = parameter_value_map["annealing_time"] + extension_time = parameter_value_map["extension_time"] + denaturation_time = parameter_value_map["denaturation_time"] def get_instrument_deck(self, instrument: sbol3.Agent) -> str: for deck, agent in self.configuration.items(): @@ -438,15 +418,15 @@ def get_instrument_deck(self, instrument: sbol3.Agent) -> str: def serial_dilution( self, - record: labop.ActivityNodeExecution, - execution: labop.ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() - source = parameter_value_map["samples"]["value"] - destination = parameter_value_map["samples"]["value"] - amount = ecl_measure(parameter_value_map["amount"]["value"]) + source = parameter_value_map["samples"] + destination = parameter_value_map["samples"] + amount = ecl_measure(parameter_value_map["amount"]) if isinstance(source, labop.SampleMask): source = source.source.lookup() @@ -483,17 +463,17 @@ def serial_dilution( def resuspend( self, - record: labop.ActivityNodeExecution, - execution: labop.ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): pass # call = record.call.lookup() # parameter_value_map = call.parameter_value_map() - # source = parameter_value_map["source"]["value"] - # destination = parameter_value_map["destination"]["value"] - # amount = ecl_measure(parameter_value_map["amount"]["value"]) + # source = parameter_value_map["source"] + # destination = parameter_value_map["destination"] + # amount = ecl_measure(parameter_value_map["amount"]) # if isinstance(source, labop.SampleMask): # source = source.source.lookup() @@ -524,17 +504,15 @@ def resuspend( # ] """ # ] - def prepare_solution( - self, record: labop.ActivityNodeExecution, execution: labop.Protocol - ): + def prepare_solution(self, record: "ActivityNodeExecution", execution: "Protocol"): call = record.call.lookup() parameter_value_map = call.parameter_value_map() - spec = parameter_value_map["specification"]["value"] + spec = parameter_value_map["specification"] self.current_independent_subprotocol = spec.name - buffer_container = parameter_value_map["buffer_container"]["value"] - buffer = parameter_value_map["buffer"]["value"] + buffer_container = parameter_value_map["buffer_container"] + buffer = parameter_value_map["buffer"] if ( buffer_container.identity in self.resolutions and not self.create_stock_solutions @@ -544,18 +522,14 @@ def prepare_solution( resource = self.resolutions[buffer.identity] else: resource = f'"{buffer_container.name}"' - buffer_vol = ecl_measure( - parameter_value_map["buffer_volume"]["value"], use_star=True - ) + buffer_vol = ecl_measure(parameter_value_map["buffer_volume"], use_star=True) - reagent = parameter_value_map["reagent"]["value"] + reagent = parameter_value_map["reagent"] if reagent.identity in self.resolutions: reagent_resource = self.resolutions[reagent.identity] else: reagent_resource = f'"{reagent.name}"' - reagent_mass = ecl_measure( - parameter_value_map["reagent_mass"]["value"], use_star=True - ) + reagent_mass = ecl_measure(parameter_value_map["reagent_mass"], use_star=True) if self.create_stock_solutions: # Generate a stock solution recipe @@ -578,18 +552,18 @@ def prepare_solution( ] def finalize_prepare_solution( - self, record: labop.ActivityNodeExecution, execution: labop.Protocol + self, record: "ActivityNodeExecution", execution: "Protocol" ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() self.current_independent_subprotocol = None - spec = parameter_value_map["specification"]["value"] + spec = parameter_value_map["specification"] # self.independent_subprotocol_steps[spec.name] += ["}]"] def prepare_reagents( self, - record: labop.ActivityNodeExecution, - execution: labop.ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): pass @@ -659,7 +633,7 @@ def ecl_measure(measure: sbol3.Measure, use_star=False): raise ValueError(tyto.OM.get_term_by_uri(measure.unit) + " is not a supported unit") -def ecl_coordinates(samples: labop.SampleCollection, sample_format=Strings.XARRAY): +def ecl_coordinates(samples: "SampleCollection", sample_format=Strings.XARRAY): if type(samples) is labop.SampleMask: coordinates = flatten_coordinates( samples.sample_coordinates(sample_format=sample_format, as_list=True), diff --git a/labop_convert/markdown/markdown_specialization.py b/labop_convert/markdown/markdown_specialization.py index 7c1f2aa5..8ea3bab0 100644 --- a/labop_convert/markdown/markdown_specialization.py +++ b/labop_convert/markdown/markdown_specialization.py @@ -12,18 +12,11 @@ import tyto import xarray as xr -from labop import ( - ActivityNodeExecution, - ContainerSpec, - Dataset, - ParameterValue, - ProtocolExecution, - SampleArray, - SampleCollection, - SampleMask, - Strings, - deserialize_sample_format, -) +from labop import SampleMask +from labop.data import deserialize_sample_format +from labop.sample_array import SampleArray +from labop.sample_collection import SampleCollection +from labop.strings import Strings from labop_convert.behavior_specialization import DefaultBehaviorSpecialization from uml import ( PARAMETER_IN, @@ -73,7 +66,7 @@ def __init__( self.sample_format = sample_format self.output_subprotocol_inputs = output_subprotocol_inputs - def initialize_protocol(self, execution: ProtocolExecution, out_dir=None): + def initialize_protocol(self, execution: "ProtocolExecution", out_dir=None): super().initialize_protocol(execution, out_dir=out_dir) print(f"Initializing execution {execution.display_id}") # Defines sections of the markdown document @@ -243,7 +236,9 @@ def _materials_markdown(self, protocol, subprotocol_executions): markdown += x.materials return markdown - def _parameter_value_markdown(self, pv: ParameterValue, is_output=False): + def _parameter_value_markdown(self, pv: "ParameterValue", is_output=False): + from labop import Dataset + parameter = pv.parameter.lookup().property_value value = pv.value if isinstance(value, sbol3.Measure): @@ -262,7 +257,7 @@ def _parameter_value_markdown(self, pv: ParameterValue, is_output=False): def _parameter_markdown(self, p: Parameter): return f"* `{p.name}`\n" - def _steps_markdown(self, execution: ProtocolExecution, subprotocol_executions): + def _steps_markdown(self, execution: "ProtocolExecution", subprotocol_executions): markdown = "\n\n## Steps\n" markdown = "" for x in subprotocol_executions: @@ -272,7 +267,7 @@ def _steps_markdown(self, execution: ProtocolExecution, subprotocol_executions): markdown += str(i + 1) + ". " + step + "\n" return markdown - def on_end(self, execution: ProtocolExecution): + def on_end(self, execution: "ProtocolExecution"): protocol = execution.protocol.lookup() subprotocol_executions = execution.get_subprotocol_executions() execution.header += self._header_markdown(protocol) @@ -334,7 +329,9 @@ def on_end(self, execution: ProtocolExecution): self.data = execution.markdown - def reporting_step(self, execution: ProtocolExecution): + def reporting_step(self, execution: "ProtocolExecution"): + from labop import Dataset + output_parameters = [] for i in execution.parameter_values: parameter = i.parameter.lookup() @@ -352,8 +349,8 @@ def reporting_step(self, execution: ProtocolExecution): def define_container( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): results = {} call = record.call.lookup() @@ -404,8 +401,8 @@ def define_container( def define_containers( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): results = {} call = record.call.lookup() @@ -425,6 +422,9 @@ def define_containers( # primitive_execution.py samples.initial_contents = quote(json.dumps({})) samples.format = "json" + + from labop import ContainerSpec + assert type(containers) is ContainerSpec try: # Assume that a simple container class is specified, rather @@ -454,8 +454,8 @@ def define_containers( def provision_container( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): results = {} call = record.call.lookup() @@ -490,8 +490,8 @@ def provision_container( def plate_coordinates( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): results = {} call = record.call.lookup() @@ -513,8 +513,8 @@ def plate_coordinates( def measure_absorbance( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): results = {} call = record.call.lookup() @@ -568,8 +568,8 @@ def measure_absorbance( def measure_fluorescence( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): results = {} call = record.call.lookup() @@ -616,8 +616,8 @@ def measure_fluorescence( def vortex( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -626,7 +626,7 @@ def vortex( duration_measure = parameter_value_map["duration"] duration_scalar = duration_measure.value duration_units = tyto.OM.get_term_by_uri(duration_measure.unit) - samples = parameter_value_map["samples"]["value"] + samples = parameter_value_map["samples"] # Add to markdown text = f"Vortex `{samples.name}`" @@ -637,8 +637,8 @@ def vortex( def discard( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -666,9 +666,11 @@ def discard( def transfer( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): + from labop import SampleArray + call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -807,8 +809,8 @@ def transfer( def transfer_by_map( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -888,8 +890,8 @@ def transfer_by_map( def culture( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -939,8 +941,8 @@ def culture( def incubate( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -963,8 +965,8 @@ def incubate( def hold( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -981,8 +983,8 @@ def hold( def hold_on_ice( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -998,8 +1000,8 @@ def hold_on_ice( def dilute_to_target_od( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -1039,8 +1041,8 @@ def dilute_to_target_od( def dilute( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -1090,8 +1092,8 @@ def dilute( def transform( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -1149,8 +1151,8 @@ def transform( def serial_dilution( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): # FIXME: # num transfers: from samplegraph @@ -1161,10 +1163,10 @@ def serial_dilution( call = record.call.lookup() parameter_value_map = call.parameter_value_map() - samples = parameter_value_map["samples"]["value"] - direction = parameter_value_map["direction"]["value"] - amount = parameter_value_map["amount"]["value"] - diluent = parameter_value_map["diluent"]["value"] + samples = parameter_value_map["samples"] + direction = parameter_value_map["direction"] + amount = parameter_value_map["amount"] + diluent = parameter_value_map["diluent"] sample_array = samples.to_data_array() dilution_factor = 2 # FIXME need to calculate from amount and sample graph @@ -1179,7 +1181,7 @@ def serial_dilution( samples_coordinates = samples.sample_coordinates( sample_format=self.sample_format ) - if isinstance(samples, labop.SampleMask): + if isinstance(samples, SampleMask): samples = samples.source.lookup() # Get samples container type @@ -1210,8 +1212,8 @@ def serial_dilution( def evaporative_seal( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -1231,8 +1233,8 @@ def evaporative_seal( def unseal( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -1251,8 +1253,8 @@ def unseal( def pool_samples( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -1286,8 +1288,8 @@ def pool_samples( def quick_spin( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -1307,15 +1309,15 @@ def quick_spin( def subprotocol_specialization( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): pass def embedded_image( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -1329,8 +1331,8 @@ def embedded_image( def culture_plates( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -1352,8 +1354,8 @@ def culture_plates( def pick_colonies( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -1373,8 +1375,8 @@ def pick_colonies( def excel_metadata( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -1388,8 +1390,8 @@ def excel_metadata( def join_metadata( self, - record: ActivityNodeExecution, - execution: ProtocolExecution, + record: "ActivityNodeExecution", + execution: "ProtocolExecution", ): call = record.call.lookup() parameter_value_map = call.parameter_value_map() @@ -1397,7 +1399,7 @@ def join_metadata( dataset = parameter_value_map["dataset"] enhanced_dataset = parameter_value_map["enhanced_dataset"] - def dataset_to_text(self, dataset: Dataset): + def dataset_to_text(self, dataset: "Dataset"): # Assumes that the data file is the same name as the markdown file, aside from the extension if self.out_file: xlsx_file = self.out_file.split(".")[0] + ".xlsx" @@ -1415,12 +1417,14 @@ def measurement_to_text(measure: sbol3.Measure): def get_sample_names( - inputs: Union[SampleArray, sbol3.Component], + inputs: Union["SampleArray", sbol3.Component], error_msg, coordinates=None, ) -> List[str]: # Since some behavior inputs may be specified as either a SampleArray or directly as a list # of Components, this provides a convenient way to unpack a list of sample names + from labop import SampleArray + input_names = [] if isinstance(inputs, SampleArray): if inputs.initial_contents: @@ -1458,7 +1462,7 @@ def repeat_for_remaining_samples(names: List[str], repeat_msg: str): return f" {repeat_msg} {remaining}." -def get_sample_label(sample: SampleCollection, record: ActivityNodeExecution) -> str: +def get_sample_label(sample: SampleCollection, record: "ActivityNodeExecution") -> str: # Lookup sample container to get the container name, and use that # as the sample label if isinstance(sample, SampleMask): @@ -1469,6 +1473,8 @@ def get_sample_label(sample: SampleCollection, record: ActivityNodeExecution) -> def write_sample_contents( sample_array: Union[dict, List[sbol3.Component]], replicates=1 ) -> str: + from labop import SampleArray + if isinstance(sample_array, SampleArray): old_contents = read_sample_contents(sample_array) initial_contents = [] diff --git a/labop_convert/opentrons/opentrons_specialization.py b/labop_convert/opentrons/opentrons_specialization.py index 2aba999e..8dbec81c 100644 --- a/labop_convert/opentrons/opentrons_specialization.py +++ b/labop_convert/opentrons/opentrons_specialization.py @@ -83,7 +83,8 @@ ContO[ "Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap" ]: "opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap", - ContO["Corning 96 Well Plate"]: "corning_96_wellplate_360ul_flat", + ContO["Corning 96 Well Plate"]: "corning_96_wellplate_360ul", + ContO["Corning 96 Well Plate 360 uL Flat"]: "corning_96_wellplate_360ul_flat", ContO["Bio-Rad 96 Well Plate 200 µL PCR"]: "biorad_96_wellplate_200ul_pcr", # ContO["NEST 96 Well Plate"]: "nest_96_wellplate_200ul_flat", } diff --git a/notebooks/Autoprotocol.ipynb b/notebooks/Autoprotocol.ipynb index 51ffffe7..8a030751 100644 --- a/notebooks/Autoprotocol.ipynb +++ b/notebooks/Autoprotocol.ipynb @@ -20,7 +20,7 @@ "from labop_convert.autoprotocol.autoprotocol_specialization import AutoprotocolSpecialization\n", "from labop_convert.autoprotocol.strateos_api import StrateosAPI, StrateosConfig\n", "from autoprotocol import container_type as ctype\n", - "from labop.execution_engine import ExecutionEngine\n", + "from labop.execution.execution_engine import ExecutionEngine\n", "from container_api.client_api import matching_containers, strateos_id\n", "\n", "%load_ext autoreload\n", diff --git a/notebooks/ExecutionRecord.ipynb b/notebooks/ExecutionRecord.ipynb index 8689446f..0b6c319f 100644 --- a/notebooks/ExecutionRecord.ipynb +++ b/notebooks/ExecutionRecord.ipynb @@ -12,7 +12,7 @@ "import labop\n", "import tyto\n", "import uml\n", - "from labop.execution_engine import ManualExecutionEngine\n", + "from labop.execution.execution_engine import ManualExecutionEngine\n", "from labop_jupyter import LabOPJupyterExecutionUI\n", "%load_ext autoreload\n", "%autoreload 2" diff --git a/notebooks/Opentrons.ipynb b/notebooks/Opentrons.ipynb index b3d4fdf8..7166cb31 100644 --- a/notebooks/Opentrons.ipynb +++ b/notebooks/Opentrons.ipynb @@ -46,11 +46,12 @@ "# %matplotlib inline\n", "from IPython.display import Image\n", "\n", - "from labop.execution_engine import ExecutionEngine\n", + "from labop.execution.execution_engine import ExecutionEngine\n", "# from labop_check.labop_check import check_doc\n", "from labop_convert.markdown.markdown_specialization import MarkdownSpecialization\n", "from labop_convert.opentrons.opentrons_specialization import OT2Specialization\n", "from labop.utils.opentrons import run_ot2_sim, make_demo_script\n", + "from labop.constants import PREFIX_MAP\n", "\n", "out_dir = os.path.join(os.path.abspath(\"\"), \"out\")\n", "if not os.path.exists(out_dir):\n", @@ -98,12 +99,12 @@ "# plate = protocol.load_labware('corning_96_wellplate_360ul_flat', location='1')\n", "plate_spec = labop.ContainerSpec('sample_plate', name='sample plate', \n", " queryString='cont:Corning96WellPlate360uLFlat', \n", - " prefixMap=labop.constants.PREFIX_MAP)\n", + " prefixMap=PREFIX_MAP)\n", "plate = protocol.primitive_step('EmptyContainer', specification=plate_spec)\n", "load_plate = protocol.primitive_step('LoadRackOnInstrument', rack=plate_spec, coordinates='1')\n", "\n", "# tiprack = protocol.load_labware('opentrons_96_tiprack_300ul', location='2')\n", - "tiprack_spec = labop.ContainerSpec('tiprack', queryString='cont:Opentrons96TipRack300uL', prefixMap=labop.constants.PREFIX_MAP)\n", + "tiprack_spec = labop.ContainerSpec('tiprack', queryString='cont:Opentrons96TipRack300uL', prefixMap=\n", "tiprack = protocol.primitive_step('LoadRackOnInstrument', rack=tiprack_spec, coordinates='2')\n", "\n", "# left_pipette = protocol.load_instrument(\n", diff --git a/notebooks/UItemplate.ipynb b/notebooks/UItemplate.ipynb index 3550fc91..413e2e91 100644 --- a/notebooks/UItemplate.ipynb +++ b/notebooks/UItemplate.ipynb @@ -20,7 +20,7 @@ "import labop\n", "import tyto\n", "import uml\n", - "from labop.execution_engine import ExecutionEngine\n", + "from labop.execution.execution_engine import ExecutionEngine\n", "\n", "%load_ext autoreload\n", "%autoreload 2" diff --git a/notebooks/data_link.ipynb b/notebooks/data_link.ipynb index 6878cd04..f9399207 100644 --- a/notebooks/data_link.ipynb +++ b/notebooks/data_link.ipynb @@ -16,7 +16,7 @@ "import rdflib as rdfl\n", "from IPython.display import Markdown\n", "\n", - "from labop.execution_engine import ExecutionEngine\n", + "from labop.execution.execution_engine import ExecutionEngine\n", "from labop_check.labop_check import check_doc\n", "\n", "import logging\n", diff --git a/notebooks/labop_author_demo.ipynb b/notebooks/labop_author_demo.ipynb index 2c8098e4..77c066d4 100644 --- a/notebooks/labop_author_demo.ipynb +++ b/notebooks/labop_author_demo.ipynb @@ -59,10 +59,10 @@ "from labop.constants import ddh2o, ludox\n", "\n", "\n", - "from labop.execution_engine import ExecutionEngine\n", + "from labop.execution.execution_engine import ExecutionEngine\n", "# from labop_check.labop_check import check_doc\n", "from labop_convert.markdown.markdown_specialization import MarkdownSpecialization\n", - "\n", + "from labop.constants import PREFIX_MAP\n", "%load_ext autoreload\n", "%autoreload 2" ] @@ -213,7 +213,7 @@ " (om:hasNumericalValue only xsd:decimal[>= \"200\"^^xsd:decimal])))\"\"\"\n", "\n", "\n", - "spec = labop.ContainerSpec(queryString=PLATE_SPECIFICATION, prefixMap=labop.constants.labop.constants.PREFIX_MAP, name='plateRequirement')" + "spec = labop.ContainerSpec(queryString=PLATE_SPECIFICATION, prefixMap=PREFIX_MAP, name='plateRequirement')" ] }, { diff --git a/notebooks/labop_demo.ipynb b/notebooks/labop_demo.ipynb index 8ab6c8ed..464d1f7d 100644 --- a/notebooks/labop_demo.ipynb +++ b/notebooks/labop_demo.ipynb @@ -19,10 +19,10 @@ "from IPython.display import Markdown\n", "\n", "\n", - "from labop.execution_engine import ExecutionEngine\n", + "from labop.execution.execution_engine import ExecutionEngine\n", "# from labop_check.labop_check import check_doc\n", "from labop_convert.markdown.markdown_specialization import MarkdownSpecialization\n", - "from labop.constants import ddh2o, ludox\n", + "from labop.constants import ddh2o, ludox, PREFIX_MAP\n", "\n", "%load_ext autoreload\n", "%autoreload 2" @@ -168,7 +168,7 @@ " ((om:hasUnit value om:microlitre) and\n", " (om:hasNumericalValue only xsd:decimal[>= \"200\"^^xsd:decimal])))\"\"\"\n", "\n", - "spec = labop.ContainerSpec('plate_requirement', name='plateRequirement', queryString=PLATE_SPECIFICATION, prefixMap=labop.constants.PREFIX_MAP)" + "spec = labop.ContainerSpec('plate_requirement', name='plateRequirement', queryString=PLATE_SPECIFICATION, prefixMap=PREFIX_MAP)" ] }, { diff --git a/notebooks/markdown.ipynb b/notebooks/markdown.ipynb index 017412a3..694d8d22 100644 --- a/notebooks/markdown.ipynb +++ b/notebooks/markdown.ipynb @@ -15,7 +15,7 @@ "\n", "from labop_convert.markdown.markdown_specialization import MarkdownSpecialization\n", "\n", - "from labop.execution_engine import ExecutionEngine\n", + "from labop.execution.execution_engine import ExecutionEngine\n", "from IPython.display import Markdown\n", "\n", "%load_ext autoreload\n", diff --git a/notebooks/ph_calibration.ipynb b/notebooks/ph_calibration.ipynb index 5118fd31..2e7dd279 100644 --- a/notebooks/ph_calibration.ipynb +++ b/notebooks/ph_calibration.ipynb @@ -8,7 +8,7 @@ "source": [ "from examples.pH_calibration import pH_calibration as pH_calibration\n", "import sbol3\n", - "from labop.execution_engine import ManualExecutionEngine\n", + "from labop.execution.execution_engine import ManualExecutionEngine\n", "import labop\n", "import uml\n", "import tyto\n", diff --git a/revised-interlab-growth-curve.py b/revised-interlab-growth-curve.py index bcf4dbb3..d3a73e5c 100644 --- a/revised-interlab-growth-curve.py +++ b/revised-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 import MarkdownSpecialization if "unittest" in sys.modules: diff --git a/test/DISABLED_execution.py b/test/DISABLED_execution.py index 4cad3853..a5d46ee8 100644 --- a/test/DISABLED_execution.py +++ b/test/DISABLED_execution.py @@ -6,7 +6,7 @@ import tyto import labop -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine l = logging.getLogger(__file__) l.setLevel(logging.ERROR) @@ -64,7 +64,10 @@ def test_execute_protocol(self): ) ] execution = ee.execute( - protocol, agent, id="test_execution", parameter_values=parameter_values + protocol, + agent, + id="test_execution", + parameter_values=parameter_values, ) # dot = execution.to_dot() diff --git a/test/test_LUDOX_protocol.py b/test/test_LUDOX_protocol.py index a80e8169..7aac7390 100644 --- a/test/test_LUDOX_protocol.py +++ b/test/test_LUDOX_protocol.py @@ -1,22 +1,11 @@ -import filecmp import logging import os -import tempfile import unittest -from importlib.machinery import SourceFileLoader -from importlib.util import module_from_spec, spec_from_loader - -import sbol3 import labop -from labop.utils.helpers import file_diff -# Save testfiles as artifacts when running in CI environment, -# else save them to a local temp directory -if "GH_TMPDIR" in os.environ: - TMPDIR = os.environ["GH_TMPDIR"] -else: - TMPDIR = tempfile.gettempdir() +logger = logging.getLogger("LUDOX_protocol") +logger.setLevel(logging.INFO) OUT_DIR = os.path.join(os.path.dirname(__file__), "out") @@ -28,52 +17,50 @@ ) -def load_ludox_protocol(protocol_filename): - loader = SourceFileLoader("ludox_protocol", protocol_filename) - spec = spec_from_loader(loader.name, loader) - module = module_from_spec(spec) - loader.exec_module(module) - return module - - -protocol_def = load_ludox_protocol(protocol_def_file) - - class TestProtocolEndToEnd(unittest.TestCase): - def test_create_protocol(self): - protocol: labop.Protocol - doc: sbol3.Document - logger = logging.getLogger("LUDOX_protocol") - logger.setLevel(logging.INFO) - protocol, doc = protocol_def.ludox_protocol() - - ######################################## - # 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) + def create_protocol(self, doc, protocol: labop.Protocol) -> labop.Protocol: + protocol = labop.execution.harness.ProtocolLoader( + protocol_def_file, "ludox_protocol" + ).generate_protocol(doc, protocol) + return protocol - temp_name = os.path.join(TMPDIR, "igem_ludox_test.nt") + def test_create_protocol(self): + harness = labop.execution.harness.ProtocolHarness( + clean_output=True, + base_dir=os.path.dirname(__file__), + entry_point=self.create_protocol, + namespace="https://bbn.com/scratch/", + 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. + """, + artifacts=[ + labop.execution.harness.ProtocolRubric( + filename=os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "testfiles", + "igem_ludox_test.nt", + ), + # overwrite_rubric=True, # Used to update rubric + ) + ], + ) - # At some point, rdflib began inserting an extra newline into - # N-triple serializations, which breaks file comparison. - # Here we strip extraneous newlines, to maintain reverse compatibility - with open(temp_name, "w") as f: - f.write(doc.write_string(sbol3.SORTED_NTRIPLES).strip()) - print(f"Wrote file as {temp_name}") + harness.run(verbose=True) - comparison_file = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - "testfiles", - "igem_ludox_test.nt", + assert len(harness.errors()) == 0, harness.artifacts_results_summary( + verbose=True ) - # with open(comparison_file, "w") as f: - # f.write(doc.write_string(sbol3.SORTED_NTRIPLES).strip()) - print(f"Comparing against {comparison_file}") - diff = "".join(file_diff(comparison_file, temp_name)) - print(f"Difference: {diff}") - assert filecmp.cmp(temp_name, comparison_file), "Files are not identical" - print("File identical with test file") if __name__ == "__main__": diff --git a/test/test_decision_nodes.py b/test/test_decision_nodes.py index 515da976..cef6445c 100644 --- a/test/test_decision_nodes.py +++ b/test/test_decision_nodes.py @@ -1,20 +1,22 @@ -import filecmp import logging import os import tempfile import unittest -from importlib.machinery import SourceFileLoader -from importlib.util import module_from_spec, spec_from_loader import sbol3 -from labop import ExecutionEngine, Primitive, Protocol, SampleData -from labop.utils.helpers import file_diff +# from labop.utils.helpers import file_diff +import labop +from labop.execution.harness import ( + ProtocolExecutionRubric, + ProtocolHarness, + ProtocolLoader, +) from uml import ActivityParameterNode, CallBehaviorAction, InputPin -OUT_DIR = os.path.join(os.path.dirname(__file__), "out") -if not os.path.exists(OUT_DIR): - os.mkdir(OUT_DIR) +# OUT_DIR = os.path.join(os.path.dirname(__file__), "out") +# if not os.path.exists(OUT_DIR): +# os.mkdir(OUT_DIR) # Save testfiles as artifacts when running in CI environment, # else save them to a local temp directory @@ -29,32 +31,15 @@ ) -def load_ludox_protocol(protocol_filename): - loader = SourceFileLoader("ludox_protocol", protocol_filename) - spec = spec_from_loader(loader.name, loader) - module = module_from_spec(spec) - loader.exec_module(module) - return module - - -protocol_def = load_ludox_protocol(protocol_def_file) - - class TestProtocolEndToEnd(unittest.TestCase): - def test_create_protocol(self): - protocol: Protocol - doc: sbol3.Document + def create_protocol(self, doc: sbol3.Document, protocol: labop.Protocol): logger = logging.getLogger("decision_protocol") logger.setLevel(logging.INFO) - doc = sbol3.Document() - sbol3.set_namespace("https://bbn.com/scratch/") - protocol = Protocol("decision_node_test") - doc.add(protocol) initial = protocol.initial() final = protocol.final() - pH_meter_calibrated = Primitive("pHMeterCalibrated") + pH_meter_calibrated = labop.Primitive("pHMeterCalibrated") pH_meter_calibrated.description = "Determine if the pH Meter is calibrated." pH_meter_calibrated.add_output( "return", "http://www.w3.org/2001/XMLSchema#boolean" @@ -78,52 +63,23 @@ def pH_meter_calibrated_compute_output( ], ) - agent = sbol3.Agent("test_agent") - ee = ExecutionEngine( - out_dir=OUT_DIR, use_ordinal_time=True, use_defined_primitives=False - ) - parameter_values = [] - execution = ee.execute( - protocol, - agent, - id="test_execution", - parameter_values=parameter_values, - ) + return protocol - ######################################## - # 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(TMPDIR, "decision_node_test.nt") - doc.write(temp_name, sbol3.SORTED_NTRIPLES) - print(f"Wrote file as {temp_name}") - - comparison_file = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - "testfiles", - "decision_node_test.nt", - ) - doc.write(comparison_file, sbol3.SORTED_NTRIPLES) - print(f"Comparing against {comparison_file}") - diff = "".join(file_diff(comparison_file, temp_name)) - # print(f"Difference: {diff}") - assert filecmp.cmp(temp_name, comparison_file), "Files are not identical" - print("File identical with test file") - - def test_ludox_calibration_decision(self): - protocol: Protocol - doc: sbol3.Document + def generate_ludox_calibration_decision( + self, doc: sbol3.Document, protocol: labop.Protocol + ) -> labop.Protocol: logger = logging.getLogger("LUDOX_decision_protocol") logger.setLevel(logging.INFO) - protocol, doc = protocol_def.ludox_protocol() - measurment_is_nominal = Primitive("measurementNominal") + protocol = ProtocolLoader( + protocol_def_file, "ludox_protocol" + ).generate_protocol(doc, protocol) + + measurment_is_nominal = labop.Primitive("measurementNominal") measurment_is_nominal.description = ( "Determine if the measurments are acceptable." ) - measurment_is_nominal.add_input("decision_input", SampleData) + measurment_is_nominal.add_input("decision_input", labop.SampleData) measurment_is_nominal.add_output( "return", "http://www.w3.org/2001/XMLSchema#boolean" ) @@ -188,16 +144,86 @@ def measurement_is_nominal_compute_output( sbol3.OM_MEASURE, measure2.output_pin("measurements"), ) + + # primary incoming node is a measurement primitive (because its not a control node, the primary incoming edge is an object flow) + # The protocol is getting two object edges into the decision node (from the measurement and its output pin). There should not be an object edge from the measurement. The outputs are object flows, but are used as control flows. decision = protocol.make_decision_node( + # The first parameter is the primary_incoming activity passing control flow through the decision measure, # .output_pin("measurements"), # primary_incoming + # The decision input behavior is the behavior used to select the flow of the decision node decision_input_behavior=measurment_is_nominal, + # The decision input source determines the object flow that influences the decision input behavior output decision_input_source=measure.output_pin("measurements"), outgoing_targets=[ (True, protocol.final()), (False, measure2), ], ) - # protocol.to_dot().view() + return protocol + + def test_create_protocol(self): + namespace = "https://bbn.com/scratch/" + harness = ProtocolHarness( + clean_output=True, + base_dir=os.path.join( + os.path.dirname(__file__), "out", "out_create_protocol" + ), + namespace=namespace, + execution_id="test_execution", + protocol_name="decision_node_test", + protocol_long_name=None, + protocol_description=None, + entry_point=self.create_protocol, + agent="test_agent", + execution_kwargs={ + "use_ordinal_time": True, + "use_defined_primitives": False, + }, + artifacts=[ + ProtocolExecutionRubric( + filename=os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "testfiles", + "decision_node_test.nt", + ), + overwrite_rubric=True, # Used to update rubric + ) + ], + ) + harness.run(verbose=True) + + assert len(harness.errors()) == 0, harness.artifacts_results_summary( + verbose=True + ) + + # execution_artifact = harness.execution_artifact() + # protocol_artifact = execution_artifact.protocol_artifact + + # agent = sbol3.Agent("test_agent") + # ee = ExecutionEngine( + # out_dir=OUT_DIR, use_ordinal_time=True, use_defined_primitives=False + # ) + # parameter_values = [] + # execution = ee.execute( + # protocol, + # agent, + # id="test_execution", + # parameter_values=parameter_values, + # ) + + def test_ludox_calibration_decision(self): + harness = ProtocolHarness( + clean_output=True, + base_dir=os.path.join( + os.path.dirname(__file__), "out", "out_calibration_decision" + ), + entry_point=self.generate_ludox_calibration_decision, + artifacts=[], + ) + harness.run() + assert len(harness.errors()) == 0, harness.artifacts_results_summary( + verbose=True + ) if __name__ == "__main__": diff --git a/test/test_examples.py b/test/test_examples.py index 821a8999..985fbf1d 100644 --- a/test/test_examples.py +++ b/test/test_examples.py @@ -10,7 +10,7 @@ from parameterized import parameterized -from labop.utils.harness import ProtocolHarness +from labop.execution import ProtocolHarness l = logging.Logger(__file__) l.setLevel(logging.INFO) @@ -45,7 +45,34 @@ def discover_example_protocols(example_directory: str) -> List[str]: expected_failures = { "../examples/protocols/opentrons/opentrons-pcr/opentrons_pcr_example.py": { "description": "Fails because there is a missing primer layout csv." - } + }, + "../examples/protocols/pH_calibration/pH_calibration.py": { + "description": "Fails because it cannot import module." + }, + "../examples/protocols/iGEM/interlab-timepoint-B_AV.py": { + "description": "Fails because it cannot import module." + }, + "../examples/protocols/iGEM/interlab-exp1_MI.py": { + "description": "Fails because it cannot import module." + }, + "../examples/protocols/iGEM/interlab-growth-curve.py": { + "description": "Fails because it cannot import module." + }, + "../examples/protocols/iGEM/interlab-exp2_MI.py": { + "description": "Fails because it cannot import module." + }, + "../examples/protocols/iGEM/revised-interlab-growth-curve.py": { + "description": "Fails due to several well-formedness errors" + }, + "../examples/protocols/iGEM/interlab-timepoint-B.py": { + "description": "Fails because it cannot import module." + }, + "../examples/protocols/iGEM/interlab-exp2_from1.py": { + "description": "Fails because it cannot import module." + }, + "../examples/protocols/iGEM/challenging-interlab-growth-curve.py": { + "description": "Fails due to several well-formedness errors" + }, } diff --git a/test/test_igem_examples.py b/test/test_igem_examples.py index dcc65fc7..a4e8b5c1 100644 --- a/test/test_igem_examples.py +++ b/test/test_igem_examples.py @@ -3,15 +3,17 @@ from importlib.machinery import SourceFileLoader from importlib.util import module_from_spec, spec_from_loader -import sbol3 +import labop +import labop_convert +from labop.execution.harness import ProtocolSpecialization -def fullpath(fname: str) -> str: - return os.path.join(os.path.dirname(__file__), f"../examples/{fname}") +def fullpath(fname: str, path: str = "../examples/protocols/iGEM/") -> str: + return os.path.join(os.path.dirname(__file__), f"{path}{fname}") -def load_protocol(protocol_filename): - testfile_path = fullpath(protocol_filename) +def load_protocol(protocol_filename, path="../examples/protocols/iGEM/"): + testfile_path = fullpath(protocol_filename, path=path) module_name = protocol_filename.replace("-", "_") loader = SourceFileLoader(module_name, testfile_path) spec = spec_from_loader(loader.name, loader) @@ -21,21 +23,86 @@ def load_protocol(protocol_filename): class TestIgemExamples(unittest.TestCase): + def get_harness( + self, + filename: str, + path: str = "../examples/protocols/iGEM/", + use_custom_specialization: bool = True, + ) -> labop.execution.harness.ProtocolHarness: + out_dir = filename.split(".")[0].replace("-", "_") + protocol_module = load_protocol(filename, path=path) + if use_custom_specialization: + specialization = protocol_module.InterlabCustomSpecialization( + specialization=labop_convert.MarkdownSpecialization( + "test_LUDOX_markdown.md" + ) + ) + else: + specialization = ProtocolSpecialization( + specialization=labop_convert.MarkdownSpecialization( + "test_LUDOX_markdown.md" + ) + ) + + harness = labop.execution.harness.ProtocolHarness( + clean_output=True, + protocol_name=out_dir, + base_dir=os.path.join(os.path.dirname(__file__), "out", out_dir), + entry_point=protocol_module.generate_protocol, + agent="test_agent", + libraries=[ + "liquid_handling", + "plate_handling", + "spectrophotometry", + "sample_arrays", + "culturing", + ], + artifacts=[specialization], + execution_kwargs={ + "sample_format": "json", + "track_samples": False, + }, + execution_id="test_execution", + ) + return harness + def test_example1(self): - load_protocol("interlab-endpoint.py") + harness = self.get_harness("interlab-endpoint.py") + harness.run() + assert len(harness.errors()) == 0, harness.artifacts_results_summary( + verbose=True + ) def test_example2(self): - load_protocol("interlab-exp1.py") + harness = self.get_harness("interlab-exp1.py") + harness.run() + assert len(harness.errors()) == 0, harness.artifacts_results_summary( + verbose=True + ) def test_example3(self): - load_protocol("interlab-exp2.py") + harness = self.get_harness("interlab-exp2.py") + harness.run() + assert len(harness.errors()) == 0, harness.artifacts_results_summary( + verbose=True + ) def test_example4(self): - load_protocol("interlab-exp3_challenge.py") + harness = self.get_harness("interlab-exp3_challenge.py") + harness.run() + assert len(harness.errors()) == 0, harness.artifacts_results_summary( + verbose=True + ) def test_example5(self): - load_protocol( - "protocols/multicolor-particle-calibration/multicolor-particle-calibration.py" + harness = self.get_harness( + "multicolor-particle-calibration.py", + path="../examples/protocols/calibration/multicolor-particle-calibration/", + use_custom_specialization=False, + ) + harness.run() + assert len(harness.errors()) == 0, harness.artifacts_results_summary( + verbose=True ) diff --git a/test/test_inputs.py b/test/test_inputs.py index a18e6639..1f65abf0 100644 --- a/test/test_inputs.py +++ b/test/test_inputs.py @@ -5,7 +5,7 @@ import labop import uml -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop_convert import MarkdownSpecialization from labop_convert.behavior_specialization import DefaultBehaviorSpecialization diff --git a/test/test_opentrons.py b/test/test_opentrons.py index f5833c67..db4ccf19 100644 --- a/test/test_opentrons.py +++ b/test/test_opentrons.py @@ -1,5 +1,3 @@ -import filecmp -import logging import os import tempfile import unittest @@ -9,7 +7,9 @@ import sbol3 import labop -from labop.execution_engine import ExecutionEngine +from build.lib.labop_convert.opentrons import opentrons_specialization +from labop.execution.execution_engine import ExecutionEngine +from labop.execution.harness import ProtocolSpecialization from labop.utils.helpers import file_diff from labop_convert.opentrons.opentrons_specialization import OT2Specialization @@ -42,64 +42,49 @@ def load_protocol(protocol_def_fn, protocol_filename): class TestProtocolEndToEnd(unittest.TestCase): - def test_create_protocol(self): - protocol: labop.Protocol - doc: sbol3.Document - logger = logging.getLogger("opentrons_toy_protocol") - logger.setLevel(logging.INFO) - protocol, doc = protocol_def.opentrons_toy_protocol() - - protocol.to_dot().render( - filename=os.path.join(OUT_DIR, protocol.display_name), format="png" - ) + def create_protocol( + self, doc: sbol3.Document, protocol: labop.Protocol + ) -> labop.Protocol: + protocol = labop.execution.harness.ProtocolLoader( + protocol_def_file, "opentrons_toy_protocol" + ).generate_protocol(doc, protocol) + return protocol - agent = sbol3.Agent("ot2_machine", name="OT2 machine") - - # Execute the protocol - # In order to get repeatable timings, we use ordinal time in the test - # where each timepoint is one second after the previous time point - ee = ExecutionEngine( - use_ordinal_time=True, - specializations=[OT2Specialization(os.path.join(OUT_DIR, "opentrons_toy"))], - failsafe=False, - ) - parameter_values = [] - execution = ee.execute( - protocol, - agent, - id="test_execution_1", - parameter_values=parameter_values, + def test_create_protocol(self): + harness = labop.execution.harness.ProtocolHarness( + clean_output=True, + base_dir=os.path.dirname(__file__), + entry_point=self.create_protocol, + agent="ot2_machine", + execution_id="test_execution_1", + namespace="https://labop.io/scratch/", + protocol_name="OT2_demo", + protocol_long_name="OT2 demo", + protocol_description="Example Opentrons Protocol as LabOP", + execution_kwargs={ + "use_ordinal_time": True, + "failsafe": False, + }, + artifacts=[ + ProtocolSpecialization( + specialization=OT2Specialization("opentrons_toy") + ), + labop.execution.harness.ProtocolExecutionRubric( + filename=os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "testfiles", + "opentrons_toy.nt", + ), + # overwrite_rubric=True, # Used to update rubric + ), + ], ) - ######################################## - # 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) - - nt_file = "opentrons_toy.nt" - temp_name = os.path.join(TMPDIR, nt_file) - - # At some point, rdflib began inserting an extra newline into - # N-triple serializations, which breaks file comparison. - # Here we strip extraneous newlines, to maintain reverse compatibility - with open(temp_name, "w") as f: - f.write(doc.write_string(sbol3.SORTED_NTRIPLES).strip()) - print(f"Wrote file as {temp_name}") + harness.run(verbose=True) - comparison_file = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - "testfiles", - nt_file, + assert len(harness.errors()) == 0, harness.artifacts_results_summary( + verbose=True ) - # with open(comparison_file, "w") as f: - # f.write(doc.write_string(sbol3.SORTED_NTRIPLES).strip()) - print(f"Comparing against {comparison_file}") - diff = "".join(file_diff(comparison_file, temp_name)) - print(f"Difference:\n{diff}") - assert filecmp.cmp(temp_name, comparison_file), "Files are not identical" - print("File identical with test file") if __name__ == "__main__": diff --git a/test/test_outputs.py b/test/test_outputs.py index b3ac1f28..a9555244 100644 --- a/test/test_outputs.py +++ b/test/test_outputs.py @@ -5,7 +5,7 @@ import tyto import labop -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop_convert import MarkdownSpecialization from labop_convert.behavior_specialization import DefaultBehaviorSpecialization from uml import LiteralReference diff --git a/test/test_protocol_outputs.py b/test/test_protocol_outputs.py index 7e57efd7..da54fcf1 100644 --- a/test/test_protocol_outputs.py +++ b/test/test_protocol_outputs.py @@ -1,13 +1,14 @@ +import os import unittest import sbol3 import tyto import labop -from labop.execution_engine import ExecutionEngine -from labop_convert import MarkdownSpecialization -from labop_convert.behavior_specialization import DefaultBehaviorSpecialization -from uml import LiteralReference +import labop_convert +import uml +from labop.execution.harness import ProtocolExecutionNTuples +from labop.protocol_execution import ProtocolExecution PARAMETER_IN = "http://bioprotocols.org/uml#in" PARAMETER_OUT = "http://bioprotocols.org/uml#out" @@ -18,12 +19,9 @@ class TestProtocolOutputs(unittest.TestCase): - def setUp(self): - protocol_name = "test_protocol" - protocol, doc = labop.Protocol.initialize_protocol( - display_id=protocol_name, name=protocol_name - ) - + def generate_protocol( + self, doc: sbol3.Document, protocol: labop.Protocol + ) -> labop.Protocol: plate_spec = labop.ContainerSpec( "my_absorbance_measurement_plate", name="my absorbance measurement plate", @@ -47,6 +45,13 @@ def setUp(self): self.protocol = protocol self.output = measure_absorbance.output_pin("measurements") + self.protocol.designate_output( + "measurements", + "http://bioprotocols.org/labop#SampleData", + source=self.output, + ) + return protocol + # @unittest.expectedFailure # def test_protocol_outputs_not_designated(self): # # TODO: catch output parameters that aren't explicitly designated @@ -57,19 +62,22 @@ def setUp(self): def test_protocol_outputs(self): # This test confirms generation of designated output objects - self.protocol.designate_output( - "measurements", - "http://bioprotocols.org/labop#SampleData", - source=self.output, - ) - - agent = sbol3.Agent("test_agent") - ee = ExecutionEngine( - specializations=[MarkdownSpecialization("test_LUDOX_markdown.md")] + harness = labop.execution.harness.ProtocolHarness( + base_dir=os.path.join(os.path.dirname(__file__), "out"), + entry_point=self.generate_protocol, + artifacts=[ + labop.execution.harness.ProtocolSpecialization( + specialization=labop_convert.MarkdownSpecialization( + "test_LUDOX_markdown.md" + ) + ) + ], ) - ex = ee.execute(self.protocol, agent, id="test_execution", parameter_values=[]) + harness.run() - self.assertTrue(isinstance(ex.parameter_values[0].value, LiteralReference)) + execution_artifacts = harness.artifacts_of_type(ProtocolExecutionNTuples) + ex = next(iter(execution_artifacts)).execution + self.assertTrue(isinstance(ex.parameter_values[0].value, uml.LiteralReference)) self.assertTrue( isinstance(ex.parameter_values[0].value.value.lookup(), labop.Dataset) ) diff --git a/test/test_sampledata.py b/test/test_sampledata.py index 3d210a59..1897e985 100644 --- a/test/test_sampledata.py +++ b/test/test_sampledata.py @@ -15,7 +15,7 @@ import labop import uml from labop import Protocol -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop.utils.helpers import file_diff from labop.utils.plate_coordinates import get_sample_list diff --git a/test/test_samplemap.py b/test/test_samplemap.py index 8aa51e9d..0bacb371 100644 --- a/test/test_samplemap.py +++ b/test/test_samplemap.py @@ -10,7 +10,7 @@ import xarray as xr from labop import ContainerSpec, Protocol, SampleMap, Strings, serialize_sample_format -from labop.execution_engine import ExecutionEngine +from labop.execution.execution_engine import ExecutionEngine from labop.strings import Strings from labop.utils.helpers import file_diff from labop.utils.plate_coordinates import ( diff --git a/test/test_subprotocols.py b/test/test_subprotocols.py index 83833f16..7c20a82d 100644 --- a/test/test_subprotocols.py +++ b/test/test_subprotocols.py @@ -1,19 +1,16 @@ +import os import unittest import sbol3 import labop -from labop.execution_engine import ExecutionEngine +from labop.execution.harness import ProtocolExecutionNTuples class TestSubprotocols(unittest.TestCase): - def test_subexecutions(self): - protocol, doc = labop.Protocol.initialize_protocol( - display_id="protocol", - name="Protocol", - namespace="http://bbn.com/scratch/", - ) - + def generate_protocol( + self, doc: sbol3.Document, protocol: labop.Protocol + ) -> labop.Protocol: subprotocol1 = labop.Protocol.create_protocol(display_id="sub1", name="sub1") subprotocol2 = labop.Protocol.create_protocol(display_id="sub2", name="sub2") primitive1 = labop.Primitive("primitive1") @@ -26,16 +23,26 @@ def test_subexecutions(self): protocol.primitive_step(primitive1) protocol.primitive_step(subprotocol2) - ee = ExecutionEngine() - ee.specializations[0]._behavior_func_map[ - primitive1.identity - ] = lambda call, ex: None # Register custom primitives in the execution engine - ex = ee.execute( - protocol, - sbol3.Agent("test_agent"), - id="test_execution1", - parameter_values=[], + return protocol + + def test_subexecutions(self): + harness = labop.execution.harness.ProtocolHarness( + namespace="http://bbn.com/scratch/", + base_dir=os.path.join(os.path.dirname(__file__), "out"), + entry_point=self.generate_protocol, + execution_id="test_execution1", ) + execution_artifacts = harness.artifacts_of_type(ProtocolExecutionNTuples) + + # execution_artifact.execution_engine.specializations[0]._behavior_func_map[ + # primitive1.identity + # ] = ( + # lambda call, ex: None + # ) # Register custom primitives in the execution engine + harness.run() + ea = next(iter(execution_artifacts)) + ex = ea.execution + ee = ea.execution_engine ordered_executions = ex.get_ordered_executions() self.assertListEqual( @@ -53,11 +60,17 @@ def test_subexecutions(self): ], ) if not ee.is_asynchronous: + # FIXME There is no synchronous mode at this time and the subprotocol executions are part of the top-level execution + # Asynchronous execution will not include subprotocol executions, rather just the tokens inside them that execution. + protocol = ex.get_protocol() + behaviors = protocol.get_behaviors() + subprotocols = [b for b in behaviors if isinstance(b, labop.Protocol)] + subprotocols.sort(key=lambda x: x.identity) subprotocol_executions = ex.get_subprotocol_executions() self.assertListEqual( - [x.protocol.lookup() for x in subprotocol_executions], - [subprotocol1, subprotocol2], + [x.get_protocol for x in subprotocol_executions], + subprotocols, ) diff --git a/test/testfiles/decision_node_test.nt b/test/testfiles/decision_node_test.nt index 096c7279..d8c23c5e 100644 --- a/test/testfiles/decision_node_test.nt +++ b/test/testfiles/decision_node_test.nt @@ -15,12 +15,12 @@ . . . - . + . "ControlFlow2" . . . . - . + . "ControlFlow3" . . . @@ -283,7 +283,7 @@ . . . - . + . . "ActivityNodeExecution4" . . @@ -319,7 +319,7 @@ . . . - . + . . "CallBehaviorExecution2" . . @@ -392,4 +392,4 @@ . "2000-01-01T00:00:04"^^ . . - "2000-01-01T00:00:00"^^ . + "2000-01-01T00:00:00"^^ . \ No newline at end of file diff --git a/uml/activity_edge.py b/uml/activity_edge.py index 810a40b2..77126b3f 100644 --- a/uml/activity_edge.py +++ b/uml/activity_edge.py @@ -7,6 +7,8 @@ import graphviz +from uml.activity_node import ActivityNode + from . import inner from .action import Action from .input_pin import InputPin @@ -21,10 +23,10 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._where_defined = self.get_where_defined() - def get_source(self): + def get_source(self) -> ActivityNode: return self.source.lookup() if self.source else self.source - def get_target(self): + def get_target(self) -> ActivityNode: return self.target.lookup() if self.target else self.target def gv_sanitize(self, id: str): @@ -58,7 +60,7 @@ def to_dot(self, dot: graphviz.Graph, namespace=""): source = self.get_source() target = self.get_target() src_id = source.label(namespace=namespace) - dest_id = self.get_target().label(namespace=namespace) + dest_id = target.label(namespace=namespace) edge_id = self.label() # edge.identity.replace(":", "_") if isinstance(target, CallBehaviorAction): dest_id = f"{dest_id}:node" diff --git a/uml/behavior.py b/uml/behavior.py index 85c47d85..d824cac9 100644 --- a/uml/behavior.py +++ b/uml/behavior.py @@ -115,8 +115,8 @@ def get_input(self, name) -> Parameter: else: return found[0] - def get_output(self, name) -> Parameter: - """Return a specific input Parameter for this Behavior + def get_output(self, name: str) -> Parameter: + """Return a specific output Parameter for this Behavior Note: assumes that type is all either in or out Returns @@ -139,6 +139,26 @@ def get_output(self, name) -> Parameter: else: return found[0] + def get_outputs( + self, ordered=False, required=False + ) -> Iterable[Union[Parameter, OrderedPropertyValue]]: + return self.get_parameters( + ordered=ordered, + required=required, + input_only=False, + output_only=True, + ) + + def get_inputs( + self, ordered=False, required=False + ) -> Iterable[Union[Parameter, OrderedPropertyValue]]: + return self.get_parameters( + ordered=ordered, + required=required, + input_only=True, + output_only=False, + ) + def get_parameters( self, ordered=False, required=False, input_only=False, output_only=False ) -> Iterable[Union[Parameter, OrderedPropertyValue]]: diff --git a/uml/object_node.py b/uml/object_node.py index 22157c5e..b54f4a53 100644 --- a/uml/object_node.py +++ b/uml/object_node.py @@ -37,3 +37,6 @@ def enabled( bool if self is enabled """ return all([len(edge_values[e]) > 0 for e in edge_values]) or engine.permissive + + def get_name(self) -> str: + return self.name if self.name else self.get_parameter().name