diff --git a/opentrons-ai-server/Pipfile b/opentrons-ai-server/Pipfile index 4586798349a..5c7cdd10e16 100644 --- a/opentrons-ai-server/Pipfile +++ b/opentrons-ai-server/Pipfile @@ -20,6 +20,7 @@ asgi-correlation-id = "==4.3.3" gspread = "==6.1.4" google-auth = "==2.36.0" google-auth-oauthlib = "==1.2.1" +anthropic = "*" [dev-packages] docker = "==7.1.0" diff --git a/opentrons-ai-server/Pipfile.lock b/opentrons-ai-server/Pipfile.lock index a4b9ba0dca5..9e3821d1d23 100644 --- a/opentrons-ai-server/Pipfile.lock +++ b/opentrons-ai-server/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "56aef120fbddf42f146e054b7d59ee0f59be75aa6e43f332f86b7ba8fa2499e0" + "sha256": "0b186e696fa0bf94be8a3edfa27b4f680b067a108088447add8e3ee9e4cabd91" }, "pipfile-spec": 6, "requires": { @@ -26,85 +26,85 @@ }, "aiohttp": { "hashes": [ - "sha256:024409c1b1d6076d0ed933dcebd7e4fc6f3320a227bfa0c1b6b93a8b5a146f04", - "sha256:04b24497b3baf15035730de5f207ade88a67d4483a5f16ced7ece348933a5b47", - "sha256:08474e71772a516ba2e2167b4707af8361d2c452b3d8a5364c984f4867869499", - "sha256:0e7a0762cc29cd3acd01a4d2b547b3af7956ad230ebb80b529a8e4f3e4740fe8", - "sha256:104deb7873681273c5daa13c41924693df394043a118dae90387d35bc5531788", - "sha256:104ea21994b1403e4c1b398866f1187c1694fa291314ad7216ec1d8ec6b49f38", - "sha256:113bf06b029143e94a47c4f36e11a8b7e396e9d1f1fc8cea58e6b7e370cfed38", - "sha256:12071dd2cc95ba81e0f2737bebcb98b2a8656015e87772e84e8fb9e635b5da6e", - "sha256:170fb2324826bb9f08055a8291f42192ae5ee2f25b2966c8f0f4537c61d73a7b", - "sha256:21b4545e8d96870da9652930c5198366605ff8f982757030e2148cf341e5746b", - "sha256:229ae13959a5f499d90ffbb4b9eac2255d8599315027d6f7c22fa9803a94d5b1", - "sha256:2ec5efbc872b00ddd85e3904059d274f284cff314e13f48776050ca2c58f451d", - "sha256:31b91ff3a1fcb206a1fa76e0de1f08c9ffb1dc0deb7296fa2618adfe380fc676", - "sha256:329f5059e0bf6983dceebac8e6ed20e75eaff6163b3414f4a4cb59e0d7037672", - "sha256:37f8cf3c43f292d9bb3e6760476c2b55b9663a581fad682a586a410c43a7683e", - "sha256:3e1ed8d152cccceffb1ee7a2ac227c16372e453fb11b3aeaa56783049b85d3f6", - "sha256:3ed360d6672a9423aad39902a4e9fe305464d20ed7931dbdba30a4625782d875", - "sha256:40dc9446cff326672fcbf93efdb8ef7e949824de1097624efe4f61ac7f0d2c43", - "sha256:4d218d3eca40196384ad3b481309c56fd60e664128885d1734da0a8aa530d433", - "sha256:4e4e155968040e32c124a89852a1a5426d0e920a35f4331e1b3949037bfe93a3", - "sha256:4f698aa61879df64425191d41213dfd99efdc1627e6398e6d7aa5c312fac9702", - "sha256:508cfcc99534b1282595357592d8367b44392b21f6eb5d4dc021f8d0d809e94d", - "sha256:577c7429f8869fa30186fc2c9eee64d75a30b51b61f26aac9725866ae5985cfd", - "sha256:57e17c6d71f2dc857a8a1d09be1be7802e35d90fb4ba4b06cf1aab6414a57894", - "sha256:5ecc2fb1a0a9d48cf773add34196cddf7e488e48e9596e090849751bf43098f4", - "sha256:600b1d9f86a130131915e2f2127664311b33902c486b21a747d626f5144b4471", - "sha256:62502b8ffee8c6a4b5c6bf99d1de277d42bf51b2fb713975d9b63b560150b7ac", - "sha256:62a2f5268b672087c45b33479ba1bb1d5a48c6d76c133cfce3a4f77410c200d1", - "sha256:6362f50a6f0e5482c4330d2151cb682779230683da0e155c15ec9fc58cb50b6a", - "sha256:6533dd06df3d17d1756829b68b365b1583929b54082db8f65083a4184bf68322", - "sha256:6c5a6958f4366496004cf503d847093d464814543f157ef3b738bbf604232415", - "sha256:72cd984f7f14e8c01b3e38f18f39ea85dba84e52ea05e37116ba5e2a72eef396", - "sha256:76d6ee8bb132f8ee0fcb0e205b4708ddb6fba524eb515ee168113063d825131b", - "sha256:7867d0808614f04e78e0a8d5a2c1f8ac6bc626a0c0e2f62be48be6b749e2f8b2", - "sha256:7d664e5f937c08adb7908ea9f391fbf2928a9b09cb412ac0aba602bde9e499e4", - "sha256:85ae6f182be72c3531915e90625cc65afce4df8a0fc4988bd52d8a5d5faaeb68", - "sha256:89a96a0696dc67d548f69cb518c581a7a33cc1f26ab42229dea1709217c9d926", - "sha256:8b323b5d3aef7dd811424c269322eec58a977c0c8152e650159e47210d900504", - "sha256:8c47a0ba6c2b3d3e5715f8338d657badd21f778c6be16701922c65521c5ecfc9", - "sha256:8fef105113d56e817cb9bcc609667ee461321413a7b972b03f5b4939f40f307c", - "sha256:900ff74d78eb580ae4aa5883242893b123a0c442a46570902500f08d6a7e6696", - "sha256:9095580806d9ed07c0c29b23364a0b1fb78258ef9f4bddf7e55bac0e475d4edf", - "sha256:91d3991fad8b65e5dbc13cd95669ea689fe0a96ff63e4e64ac24ed724e4f8103", - "sha256:9231d610754724273a6ac05a1f177979490bfa6f84d49646df3928af2e88cfd5", - "sha256:97056d3422594e0787733ac4c45bef58722d452f4dc6615fee42f59fe51707dd", - "sha256:a896059b6937d1a22d8ee8377cdcd097bd26cd8c653b8f972051488b9baadee9", - "sha256:aabc4e92cb153636d6be54e84dad1b252ddb9aebe077942b6dcffe5e468d476a", - "sha256:ad14cdc0fba4df31c0f6e06c21928c5b924725cbf60d0ccc5f6e7132636250e9", - "sha256:ae36ae52b0c22fb69fb8b744eff82a20db512a29eafc6e3a4ab43b17215b219d", - "sha256:b3e4fb7f5354d39490d8209aefdf5830b208d01c7293a2164e404312c3d8bc55", - "sha256:b40c304ab01e89ad0aeeecf91bbaa6ae3b00e27b796c9e8d50b71a4a7e885cc8", - "sha256:b7349205bb163318dcc102329d30be59a647a3d24c82c3d91ed35b7e7301ea7e", - "sha256:b8b95a63a8e8b5f0464bd8b1b0d59d2bec98a59b6aacc71e9be23df6989b3dfb", - "sha256:bb2e82e515e268b965424ecabebd91834a41b36260b6ef5db015ee12ddb28ef3", - "sha256:c0315978b2a4569e03fb59100f6a7e7d23f718a4521491f5c13d946d37549f3d", - "sha256:c1828e10c3a49e2b234b87600ecb68a92b8a8dcf8b99bca9447f16c4baaa1630", - "sha256:c1c49bc393d854d4421ebc174a0a41f9261f50d3694d8ca277146cbbcfd24ee7", - "sha256:c415b9601ff50709d6050c8a9281733a9b042b9e589265ac40305b875cf9c463", - "sha256:c54c635d1f52490cde7ef3a423645167a8284e452a35405d5c7dc1242a8e75c9", - "sha256:c5e6a1f8b0268ffa1c84d7c3558724956002ba8361176e76406233e704bbcffb", - "sha256:c98a596ac20e8980cc6f34c0c92a113e98eb08f3997c150064d26d2aeb043e5a", - "sha256:cd0834e4260eab78671b81d34f110fbaac449563e48d419cec0030d9a8e58693", - "sha256:cdad66685fcf2ad14ce522cf849d4a025f4fd206d6cfc3f403d9873e4c243b03", - "sha256:d1ea006426edf7e1299c52a58b0443158012f7a56fed3515164b60bfcb1503a9", - "sha256:d33b4490026968bdc7f0729b9d87a3a6b1e09043557d2fc1c605c6072deb2f11", - "sha256:d5cae4cd271e20b7ab757e966cc919186b9f02535418ab36c471a5377ef4deaa", - "sha256:dd505a1121ad5b666191840b7bd1d8cb917df2647deeca6f3474331b72452362", - "sha256:e1668ef2f3a7ec9881f4b6a917e5f97c87a343fa6b0d5fc826b7b0297ddd0887", - "sha256:e7bcfcede95531589295f56e924702cef7f9685c9e4e5407592e04ded6a65bf3", - "sha256:ebf610c37df4f09c71c9bbf8309b4b459107e6fe889ac0d7e16f6e4ebd975f86", - "sha256:f3bf5c132eb48002bcc3825702d241d35b4e9585009e65e9dcf9c4635d0b7424", - "sha256:f40380c96dd407dfa84eb2d264e68aa47717b53bdbe210a59cc3c35a4635f195", - "sha256:f57a0de48dda792629e7952d34a0c7b81ea336bb9b721391c7c58145b237fe55", - "sha256:f6b925c7775ab857bdc1e52e1f5abcae7d18751c09b751aeb641a5276d9b990e", - "sha256:f8f0d79b923070f25674e4ea8f3d61c9d89d24d9598d50ff32c5b9b23c79a25b", - "sha256:feca9fafa4385aea6759c171cd25ea82f7375312fca04178dae35331be45e538" + "sha256:08ebe7a1d6c1e5ca766d68407280d69658f5f98821c2ba6c41c63cabfed159af", + "sha256:0a90a0dc4b054b5af299a900bf950fe8f9e3e54322bc405005f30aa5cacc5c98", + "sha256:0cba0b8d25aa2d450762f3dd6df85498f5e7c3ad0ddeb516ef2b03510f0eea32", + "sha256:0ebdf5087e2ce903d8220cc45dcece90c2199ae4395fd83ca616fcc81010db2c", + "sha256:10a5f91c319d9d4afba812f72984816b5fcd20742232ff7ecc1610ffbf3fc64d", + "sha256:122768e3ae9ce74f981b46edefea9c6e5a40aea38aba3ac50168e6370459bf20", + "sha256:14eb6c628432720e41b4fab1ada879d56cfe7034159849e083eb536b4c2afa99", + "sha256:177b000efaf8d2f7012c649e8aee5b0bf488677b1162be5e7511aa4f9d567607", + "sha256:1c2496182e577042e0e07a328d91c949da9e77a2047c7291071e734cd7a6e780", + "sha256:1e33a7eddcd07545ccf5c3ab230f60314a17dc33e285475e8405e26e21f02660", + "sha256:2793d3297f3e49015140e6d3ea26142c967e07998e2fb00b6ee8d041138fbc4e", + "sha256:2914061f5ca573f990ec14191e6998752fa8fe50d518e3405410353c3f44aa5d", + "sha256:2adb967454e10e69478ba4a8d8afbba48a7c7a8619216b7c807f8481cc66ddfb", + "sha256:2b02a68b9445c70d7f5c8b578c5f5e5866b1d67ca23eb9e8bc8658ae9e3e2c74", + "sha256:3129151378f858cdc4a0a4df355c9a0d060ab49e2eea7e62e9f085bac100551b", + "sha256:32334f35824811dd20a12cc90825d000e6b50faaeaa71408d42269151a66140d", + "sha256:33af11eca7bb0f5c6ffaf5e7d9d2336c2448f9c6279b93abdd6f3c35f9ee321f", + "sha256:34f37c59b12bc3afc52bab6fcd9cd3be82ff01c4598a84cbea934ccb3a9c54a0", + "sha256:3666c750b73ce463a413692e3a57c60f7089e2d9116a2aa5a0f0eaf2ae325148", + "sha256:374baefcb1b6275f350da605951f5f02487a9bc84a574a7d5b696439fabd49a3", + "sha256:382f853516664d2ebfc75dc01da4a10fdef5edcb335fe7b45cf471ce758ecb18", + "sha256:3b1f4844909321ef2c1cee50ddeccbd6018cd8c8d1ddddda3f553e94a5859497", + "sha256:3f617a48b70f4843d54f52440ea1e58da6bdab07b391a3a6aed8d3b311a4cc04", + "sha256:435f7a08d8aa42371a94e7c141205a9cb092ba551084b5e0c57492e6673601a3", + "sha256:44b69c69c194ffacbc50165911cf023a4b1b06422d1e1199d3aea82eac17004e", + "sha256:486273d3b5af75a80c31c311988931bdd2a4b96a74d5c7f422bad948f99988ef", + "sha256:4a23475d8d5c56e447b7752a1e2ac267c1f723f765e406c81feddcd16cdc97bc", + "sha256:4c979fc92aba66730b66099cd5becb42d869a26c0011119bc1c2478408a8bf7a", + "sha256:4d7fad8c456d180a6d2f44c41cfab4b80e2e81451815825097db48b8293f59d5", + "sha256:50e0aee4adc9abcd2109c618a8d1b2c93b85ac277b24a003ab147d91e068b06d", + "sha256:556564d89e2f4a6e8fe000894c03e4e84cf0b6cfa5674e425db122633ee244d1", + "sha256:5587da333b7d280a312715b843d43e734652aa382cba824a84a67c81f75b338b", + "sha256:57993f406ce3f114b2a6756d7809be3ffd0cc40f33e8f8b9a4aa1b027fd4e3eb", + "sha256:5d6e069b882c1fdcbe5577dc4be372eda705180197140577a4cddb648c29d22e", + "sha256:5d878a0186023ac391861958035174d0486f3259cabf8fd94e591985468da3ea", + "sha256:5d90b5a3b0f32a5fecf5dd83d828713986c019585f5cddf40d288ff77f366615", + "sha256:5e9a766c346b2ed7e88937919d84ed64b4ef489dad1d8939f806ee52901dc142", + "sha256:64e8f5178958a9954043bc8cd10a5ae97352c3f2fc99aa01f2aebb0026010910", + "sha256:66e58a2e8c7609a3545c4b38fb8b01a6b8346c4862e529534f7674c5265a97b8", + "sha256:68d1f46f9387db3785508f5225d3acbc5825ca13d9c29f2b5cce203d5863eb79", + "sha256:6ad9a7d2a3a0f235184426425f80bd3b26c66b24fd5fddecde66be30c01ebe6e", + "sha256:6e8e19a80ba194db5c06915a9df23c0c06e0e9ca9a4db9386a6056cca555a027", + "sha256:73a664478ae1ea011b5a710fb100b115ca8b2146864fa0ce4143ff944df714b8", + "sha256:766d0ebf8703d28f854f945982aa09224d5a27a29594c70d921c43c3930fe7ac", + "sha256:783741f534c14957fbe657d62a34b947ec06db23d45a2fd4a8aeb73d9c84d7e6", + "sha256:79efd1ee3827b2f16797e14b1e45021206c3271249b4d0025014466d416d7413", + "sha256:83a70e22e0f6222effe7f29fdeba6c6023f9595e59a0479edacfbd7de4b77bb7", + "sha256:85de9904bc360fd29a98885d2bfcbd4e02ab33c53353cb70607f2bea2cb92468", + "sha256:8d954ba0eae7f33884d27dc00629ca4389d249eb8d26ca07c30911257cae8c96", + "sha256:9075313f8e41b481e4cb10af405054564b0247dc335db5398ed05f8ec38787e2", + "sha256:97fba98fc5d9ccd3d33909e898d00f2494d6a9eec7cbda3d030632e2c8bb4d00", + "sha256:994cb893936dd2e1803655ae8667a45066bfd53360b148e22b4e3325cc5ea7a3", + "sha256:9aa4e68f1e4f303971ec42976fb170204fb5092de199034b57199a1747e78a2d", + "sha256:9b6d15adc9768ff167614ca853f7eeb6ee5f1d55d5660e3af85ce6744fed2b82", + "sha256:9bbb2dbc2701ab7e9307ca3a8fa4999c5b28246968e0a0202a5afabf48a42e22", + "sha256:9c8d1db4f65bbc9d75b7b271d68fb996f1c8c81a525263862477d93611856c2d", + "sha256:a7b0a1618060e3f5aa73d3526ca2108a16a1b6bf86612cd0bb2ddcbef9879d06", + "sha256:afa55e863224e664a782effa62245df73fdfc55aee539bed6efacf35f6d4e4b7", + "sha256:b339d91ac9060bd6ecdc595a82dc151045e5d74f566e0864ef3f2ba0887fec42", + "sha256:b470de64d17156c37e91effc109d3b032b39867000e2c126732fe01d034441f9", + "sha256:b4ec8afd362356b8798c8caa806e91deb3f0602d8ffae8e91d2d3ced2a90c35e", + "sha256:c28c1677ea33ccb8b14330560094cc44d3ff4fad617a544fd18beb90403fe0f1", + "sha256:c681f34e2814bc6e1eef49752b338061b94a42c92734d0be9513447d3f83718c", + "sha256:cccb2937bece1310c5c0163d0406aba170a2e5fb1f0444d7b0e7fdc9bd6bb713", + "sha256:cdc6f8dce09281ae534eaf08a54f0d38612398375f28dad733a8885f3bf9b978", + "sha256:d23854e5867650d40cba54d49956aad8081452aa80b2cf0d8c310633f4f48510", + "sha256:d2d942421cf3a1d1eceae8fa192f1fbfb74eb9d3e207d35ad2696bd2ce2c987c", + "sha256:d2f991c18132f3e505c108147925372ffe4549173b7c258cf227df1c5977a635", + "sha256:d3a2bcf6c81639a165da93469e1e0aff67c956721f3fa9c0560f07dd1e505116", + "sha256:d84930b4145991214602372edd7305fc76b700220db79ac0dd57d3afd0f0a1ca", + "sha256:de3b4d5fb5d69749104b880a157f38baeea7765c93d9cd3837cedd5b84729e10", + "sha256:e57a10aacedcf24666f4c90d03e599f71d172d1c5e00dcf48205c445806745b0", + "sha256:f1d06c8fd8b453c3e553c956bd3b8395100401060430572174bb7876dd95ad49", + "sha256:f833a80d9de9307d736b6af58c235b17ef7f90ebea7b9c49cd274dec7a66a2f1", + "sha256:fb0544a0e8294a5a5e20d3cacdaaa9a911d7c0a9150f5264aef36e7d8fdfa07e", + "sha256:ff5d22eece44528023254b595c670dfcf9733ac6af74c4b6cb4f6a784dc3870c" ], "markers": "python_version >= '3.9'", - "version": "==3.11.0" + "version": "==3.11.2" }, "aiosignal": { "hashes": [ @@ -122,6 +122,15 @@ "markers": "python_version >= '3.8'", "version": "==0.7.0" }, + "anthropic": { + "hashes": [ + "sha256:94671cc80765f9ce693f76d63a97ee9bef4c2d6063c044e983d21a2e262f63ba", + "sha256:ea17093ae0ce0e1768b0c46501d6086b5bcd74ff39d68cd2d6396374e9de7c09" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.39.0" + }, "anyio": { "hashes": [ "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", @@ -781,11 +790,11 @@ }, "httpcore": { "hashes": [ - "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f", - "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f" + "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", + "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd" ], "markers": "python_version >= '3.8'", - "version": "==1.0.6" + "version": "==1.0.7" }, "httptools": { "hashes": [ @@ -868,6 +877,85 @@ "markers": "python_version >= '3.7'", "version": "==3.1.4" }, + "jiter": { + "hashes": [ + "sha256:0302f0940b1455b2a7fb0409b8d5b31183db70d2b07fd177906d83bf941385d1", + "sha256:097676a37778ba3c80cb53f34abd6943ceb0848263c21bf423ae98b090f6c6ba", + "sha256:0a7d5e85766eff4c9be481d77e2226b4c259999cb6862ccac5ef6621d3c8dcce", + "sha256:0e2b445e5ee627fb4ee6bbceeb486251e60a0c881a8e12398dfdff47c56f0723", + "sha256:12fd88cfe6067e2199964839c19bd2b422ca3fd792949b8f44bb8a4e7d21946a", + "sha256:191fbaee7cf46a9dd9b817547bf556facde50f83199d07fc48ebeff4082f9df4", + "sha256:1e44fff69c814a2e96a20b4ecee3e2365e9b15cf5fe4e00869d18396daa91dab", + "sha256:1e47a554de88dff701226bb5722b7f1b6bccd0b98f1748459b7e56acac2707a5", + "sha256:25d0e5bf64e368b0aa9e0a559c3ab2f9b67e35fe7269e8a0d81f48bbd10e8963", + "sha256:262e96d06696b673fad6f257e6a0abb6e873dc22818ca0e0600f4a1189eb334f", + "sha256:2b7de0b6f6728b678540c7927587e23f715284596724be203af952418acb8a2d", + "sha256:3298af506d4271257c0a8f48668b0f47048d69351675dd8500f22420d4eec378", + "sha256:3d8bae77c82741032e9d89a4026479061aba6e646de3bf5f2fc1ae2bbd9d06e0", + "sha256:3dc9939e576bbc68c813fc82f6620353ed68c194c7bcf3d58dc822591ec12490", + "sha256:448cf4f74f7363c34cdef26214da527e8eeffd88ba06d0b80b485ad0667baf5d", + "sha256:47ac4c3cf8135c83e64755b7276339b26cd3c7ddadf9e67306ace4832b283edf", + "sha256:4aa919ebfc5f7b027cc368fe3964c0015e1963b92e1db382419dadb098a05192", + "sha256:576eb0f0c6207e9ede2b11ec01d9c2182973986514f9c60bc3b3b5d5798c8f50", + "sha256:5970cf8ec943b51bce7f4b98d2e1ed3ada170c2a789e2db3cb484486591a176a", + "sha256:5ae2d01e82c94491ce4d6f461a837f63b6c4e6dd5bb082553a70c509034ff3d4", + "sha256:5c08adf93e41ce2755970e8aa95262298afe2bf58897fb9653c47cd93c3c6cdc", + "sha256:60b49c245cd90cde4794f5c30f123ee06ccf42fb8730a019a2870cd005653ebd", + "sha256:627164ec01d28af56e1f549da84caf0fe06da3880ebc7b7ee1ca15df106ae172", + "sha256:6592f4067c74176e5f369228fb2995ed01400c9e8e1225fb73417183a5e635f0", + "sha256:65df9dbae6d67e0788a05b4bad5706ad40f6f911e0137eb416b9eead6ba6f044", + "sha256:701d90220d6ecb3125d46853c8ca8a5bc158de8c49af60fd706475a49fee157e", + "sha256:70a497859c4f3f7acd71c8bd89a6f9cf753ebacacf5e3e799138b8e1843084e3", + "sha256:75bf3b7fdc5c0faa6ffffcf8028a1f974d126bac86d96490d1b51b3210aa0f3f", + "sha256:7824c3ecf9ecf3321c37f4e4d4411aad49c666ee5bc2a937071bdd80917e4533", + "sha256:7ba52e6aaed2dc5c81a3d9b5e4ab95b039c4592c66ac973879ba57c3506492bb", + "sha256:7ba9a358d59a0a55cccaa4957e6ae10b1a25ffdabda863c0343c51817610501d", + "sha256:7ded4e4b75b68b843b7cea5cd7c55f738c20e1394c68c2cb10adb655526c5f1b", + "sha256:80dae4f1889b9d09e5f4de6b58c490d9c8ce7730e35e0b8643ab62b1538f095c", + "sha256:81d968dbf3ce0db2e0e4dec6b0a0d5d94f846ee84caf779b07cab49f5325ae43", + "sha256:8a9803396032117b85ec8cbf008a54590644a062fedd0425cbdb95e4b2b60479", + "sha256:8dbbd52c50b605af13dbee1a08373c520e6fcc6b5d32f17738875847fea4e2cd", + "sha256:8f212eeacc7203256f526f550d105d8efa24605828382cd7d296b703181ff11d", + "sha256:935f10b802bc1ce2b2f61843e498c7720aa7f4e4bb7797aa8121eab017293c3d", + "sha256:93c20d2730a84d43f7c0b6fb2579dc54335db742a59cf9776d0b80e99d587382", + "sha256:9463b62bd53c2fb85529c700c6a3beb2ee54fde8bef714b150601616dcb184a6", + "sha256:9cd3cccccabf5064e4bb3099c87bf67db94f805c1e62d1aefd2b7476e90e0ee2", + "sha256:9ecbf4e20ec2c26512736284dc1a3f8ed79b6ca7188e3b99032757ad48db97dc", + "sha256:9f9568cd66dbbdab67ae1b4c99f3f7da1228c5682d65913e3f5f95586b3cb9a9", + "sha256:ad04a23a91f3d10d69d6c87a5f4471b61c2c5cd6e112e85136594a02043f462c", + "sha256:ad36a1155cbd92e7a084a568f7dc6023497df781adf2390c345dd77a120905ca", + "sha256:af29c5c6eb2517e71ffa15c7ae9509fa5e833ec2a99319ac88cc271eca865519", + "sha256:b096ca72dd38ef35675e1d3b01785874315182243ef7aea9752cb62266ad516f", + "sha256:b1a0508fddc70ce00b872e463b387d49308ef02b0787992ca471c8d4ba1c0fa1", + "sha256:bc1b55314ca97dbb6c48d9144323896e9c1a25d41c65bcb9550b3e0c270ca560", + "sha256:be6de02939aac5be97eb437f45cfd279b1dc9de358b13ea6e040e63a3221c40d", + "sha256:c1288bc22b9e36854a0536ba83666c3b1fb066b811019d7b682c9cf0269cdf9f", + "sha256:c244261306f08f8008b3087059601997016549cb8bb23cf4317a4827f07b7d74", + "sha256:c65a3ce72b679958b79d556473f192a4dfc5895e8cc1030c9f4e434690906076", + "sha256:c915e1a1960976ba4dfe06551ea87063b2d5b4d30759012210099e712a414d9f", + "sha256:d9e247079d88c00e75e297e6cb3a18a039ebcd79fefc43be9ba4eb7fb43eb726", + "sha256:da8589f50b728ea4bf22e0632eefa125c8aa9c38ed202a5ee6ca371f05eeb3ff", + "sha256:dacca921efcd21939123c8ea8883a54b9fa7f6545c8019ffcf4f762985b6d0c8", + "sha256:de3674a5fe1f6713a746d25ad9c32cd32fadc824e64b9d6159b3b34fd9134143", + "sha256:df0a1d05081541b45743c965436f8b5a1048d6fd726e4a030113a2699a6046ea", + "sha256:e0c91a0304373fdf97d56f88356a010bba442e6d995eb7773cbe32885b71cdd8", + "sha256:e550e29cdf3577d2c970a18f3959e6b8646fd60ef1b0507e5947dc73703b5627", + "sha256:e80052d3db39f9bb8eb86d207a1be3d9ecee5e05fdec31380817f9609ad38e60", + "sha256:e81ccccd8069110e150613496deafa10da2f6ff322a707cbec2b0d52a87b9671", + "sha256:f0aacaa56360139c53dcf352992b0331f4057a0373bbffd43f64ba0c32d2d155", + "sha256:f114a4df1e40c03c0efbf974b376ed57756a1141eb27d04baee0680c5af3d424", + "sha256:f20de711224f2ca2dbb166a8d512f6ff48c9c38cc06b51f796520eb4722cc2ce", + "sha256:f22cf8f236a645cb6d8ffe2a64edb5d2b66fb148bf7c75eea0cb36d17014a7bc", + "sha256:f281aae41b47e90deb70e7386558e877a8e62e1693e0086f37d015fa1c102289", + "sha256:f3ea649e7751a1a29ea5ecc03c4ada0a833846c59c6da75d747899f9b48b7282", + "sha256:f52ce5799df5b6975439ecb16b1e879d7655e1685b6e3758c9b1b97696313bfb", + "sha256:f7605d24cd6fab156ec89e7924578e21604feee9c4f1e9da34d8b67f63e54892", + "sha256:f84c9996664c460f24213ff1e5881530abd8fafd82058d39af3682d5fd2d6316", + "sha256:f892e547e6e79a1506eb571a676cf2f480a4533675f834e9ae98de84f9b941ac" + ], + "markers": "python_version >= '3.8'", + "version": "==0.7.1" + }, "joblib": { "hashes": [ "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", @@ -2752,11 +2840,11 @@ }, "botocore-stubs": { "hashes": [ - "sha256:1456af3358be1a0e49dd8428bfb81863406659d9fad871362bf18a098eeac90a", - "sha256:dd83003963ca957a6e4835d192d7f163fb55312ce3d3f798f625ac9438616e4f" + "sha256:0251f63257eb8d5f4b414669e25f98898a2bac58fd6ffa1c9df6cf3dd823abd9", + "sha256:e536390ff6934627af351accd8da10bec6cf75e40add465ab0a9c088d7be765c" ], "markers": "python_version >= '3.8'", - "version": "==1.35.59" + "version": "==1.35.62" }, "certifi": { "hashes": [ diff --git a/opentrons-ai-server/api/domain/anthropic_predict.py b/opentrons-ai-server/api/domain/anthropic_predict.py new file mode 100644 index 00000000000..abd94b631ba --- /dev/null +++ b/opentrons-ai-server/api/domain/anthropic_predict.py @@ -0,0 +1,206 @@ +import uuid +from pathlib import Path +from typing import Any, Dict, List + +import requests +import structlog +from anthropic import Anthropic +from anthropic.types import Message, MessageParam +from ddtrace import tracer + +from api.domain.config_anthropic import DOCUMENTS, PROMPT, SYSTEM_PROMPT +from api.settings import Settings + +settings: Settings = Settings() +logger = structlog.stdlib.get_logger(settings.logger_name) +ROOT_PATH: Path = Path(Path(__file__)).parent.parent.parent + + +class AnthropicPredict: + def __init__(self, settings: Settings) -> None: + self.settings: Settings = settings + self.client: Anthropic = Anthropic(api_key=settings.anthropic_api_key.get_secret_value()) + self.model_name: str = settings.anthropic_model_name + self.system_prompt: str = SYSTEM_PROMPT + self.path_docs: Path = ROOT_PATH / "api" / "storage" / "docs" + self._messages: List[MessageParam] = [ + { + "role": "user", + "content": [ + {"type": "text", "text": DOCUMENTS.format(doc_content=self.get_docs()), "cache_control": {"type": "ephemeral"}} # type: ignore + ], + } + ] + self.tools: List[Dict[str, Any]] = [ + { + "name": "simulate_protocol", + "description": "Simulates the python protocol on user input. Returned value is text indicating if protocol is successful.", + "input_schema": { + "type": "object", + "properties": { + "protocol": {"type": "string", "description": "protocol in python for simulation"}, + }, + "required": ["protocol"], + }, + } + ] + + @tracer.wrap() + def get_docs(self) -> str: + """ + Processes documents from a directory and returns their content wrapped in XML tags. + Each document is wrapped in tags with metadata subtags. + + Returns: + str: XML-formatted string containing all documents and their metadata + """ + logger.info("Getting docs", extra={"path": str(self.path_docs)}) + xml_output = [""] + for file_path in self.path_docs.iterdir(): + try: + content = file_path.read_text(encoding="utf-8") + document_xml = [ + "", + f" {file_path.name}", + " ", + f" {content}", + " ", + "", + ] + xml_output.extend(document_xml) + + except Exception as e: + logger.error("Error procesing file", extra={"file": file_path.name, "error": str(e)}) + continue + + xml_output.append("") + return "\n".join(xml_output) + + @tracer.wrap() + def generate_message(self, max_tokens: int = 4096) -> Message: + + response = self.client.messages.create( + model=self.model_name, + system=self.system_prompt, + max_tokens=max_tokens, + messages=self._messages, + tools=self.tools, # type: ignore + extra_headers={"anthropic-beta": "prompt-caching-2024-07-31"}, + ) + + logger.info( + "Token usage", + extra={ + "input_tokens": response.usage.input_tokens, + "output_tokens": response.usage.output_tokens, + "cache_read": getattr(response.usage, "cache_read_input_tokens", "---"), + "cache_create": getattr(response.usage, "cache_creation_input_tokens", "---"), + }, + ) + return response + + @tracer.wrap() + def predict(self, prompt: str) -> str | None: + try: + self._messages.append({"role": "user", "content": PROMPT.format(USER_PROMPT=prompt)}) + response = self.generate_message() + if response.content[-1].type == "tool_use": + tool_use = response.content[-1] + self._messages.append({"role": "assistant", "content": response.content}) + result = self.handle_tool_use(tool_use.name, tool_use.input) # type: ignore + self._messages.append( + { + "role": "user", + "content": [ + { + "type": "tool_result", + "tool_use_id": tool_use.id, + "content": result, + } + ], + } + ) + follow_up = self.generate_message() + response_text = follow_up.content[0].text # type: ignore + self._messages.append({"role": "assistant", "content": response_text}) + return response_text + + elif response.content[0].type == "text": + response_text = response.content[0].text + self._messages.append({"role": "assistant", "content": response_text}) + return response_text + + logger.error("Unexpected response type") + return None + except IndexError as e: + logger.error("Invalid response format", extra={"error": str(e)}) + return None + except Exception as e: + logger.error("Error in predict method", extra={"error": str(e)}) + return None + + @tracer.wrap() + def handle_tool_use(self, func_name: str, func_params: Dict[str, Any]) -> str: + if func_name == "simulate_protocol": + results = self.simulate_protocol(**func_params) + return results + + logger.error("Unknown tool", extra={"tool": func_name}) + raise ValueError(f"Unknown tool: {func_name}") + + @tracer.wrap() + def reset(self) -> None: + self._messages = [ + { + "role": "user", + "content": [ + {"type": "text", "text": DOCUMENTS.format(doc_content=self.get_docs()), "cache_control": {"type": "ephemeral"}} # type: ignore + ], + } + ] + + @tracer.wrap() + def simulate_protocol(self, protocol: str) -> str: + url = "https://Opentrons-simulator.hf.space/protocol" + protocol_name = str(uuid.uuid4()) + ".py" + data = {"name": protocol_name, "content": protocol} + hf_token: str = settings.huggingface_api_key.get_secret_value() + headers = {"Content-Type": "application/json", "Authorization": "Bearer {}".format(hf_token)} + response = requests.post(url, json=data, headers=headers) + + if response.status_code != 200: + logger.error("Simulation request failed", extra={"status": response.status_code, "error": response.text}) + return f"Error: {response.text}" + + response_data = response.json() + if "error_message" in response_data: + logger.error("Simulation error", extra={"error": response_data["error_message"]}) + return str(response_data["error_message"]) + elif "protocol_name" in response_data: + return str(response_data["run_status"]) + else: + logger.error("Unexpected response", extra={"response": response_data}) + return "Unexpected response" + + +def main() -> None: + """Intended for testing this class locally.""" + import sys + from pathlib import Path + + # # Add project root to Python path + root_dir = Path(__file__).parent.parent.parent + sys.path.insert(0, str(root_dir)) + + from rich import print + from rich.prompt import Prompt + + settings = Settings() + llm = AnthropicPredict(settings) + prompt = Prompt.ask("Type a prompt to send to the Anthropic API:") + completion = llm.predict(prompt) + print(completion) + + +if __name__ == "__main__": + main() diff --git a/opentrons-ai-server/api/domain/config_anthropic.py b/opentrons-ai-server/api/domain/config_anthropic.py new file mode 100644 index 00000000000..9d511012592 --- /dev/null +++ b/opentrons-ai-server/api/domain/config_anthropic.py @@ -0,0 +1,217 @@ +SYSTEM_PROMPT = """ +You are a friendly and knowledgeable AI assistant specializing in Opentrons protocol development. +You help scientists create and optimize protocols using the Opentrons Python API v2. + +Your key responsibilities: +1. Welcome scientists warmly and understand their protocol needs +2. Generate accurate Python protocols using standard Opentrons labware +3. Provide clear explanations and documentation +4. Flag potential safety or compatibility issues +5. Suggest protocol optimizations when appropriate + +Call protocol simulation tool to validate the code - only when it is called explicitly by the user. +For all other queries, provide direct responses. + +Important guidelines: +- Always verify labware compatibility before generating protocols +- Include appropriate error handling in generated code +- Provide clear setup instructions and prerequisites +- Flag any potential safety concerns +- Format code examples using standard Python conventions + +If you encounter requests outside your knowledge of Opentrons capabilities, +ask for clarification rather than making assumptions. +""" + +DOCUMENTS = """ +{doc_content} +""" + +PROMPT = """ +Here are the inputs you will work with: + + +{USER_PROMPT} + + + +Follow these instructions to handle the user's prompt: + +1. Analyze the user's prompt to determine if it's: + a) A request to generate a protocol + b) A question about the Opentrons Python API v2 + c) A common task (e.g., value changes, OT-2 to Flex conversion, slot correction) + d) An unrelated or unclear request + +2. If the prompt is unrelated or unclear, ask the user for clarification. For example: + I apologize, but your prompt seems unclear. Could you please provide more details? + + +3. If the prompt is a question about the API, answer it using only the information + provided in the section. Provide references and place them under the tag. + Format your response like this: + API answer: + [Your answer here, based solely on the provided API documentation] + + References + [References] + + +4. If the prompt is a request to generate a protocol, follow these steps: + + a) Check if the prompt contains all necessary information: + - Modules + - Adapters + - Labware + - Pipette mounts + - Well allocations, liquids, samples + - Commands (steps) + + b) If any crucial information is missing, ask for clarification: + + To generate an accurate protocol, I need more information about [missing elements]. + Please provide details about: + [List of missing elements] + + + c) If all necessary information is available, generate the protocol using the following structure: + + ```python + from opentrons import protocol_api + + metadata = {{ + 'protocolName': '[Protocol name based on user prompt]', + 'author': 'AI Assistant', + 'description': '[Brief description based on user prompt]' + }} + + requirements = {{ + 'robotType': '[Robot type based on user prompt, OT-2 or Flex, default is OT-2]', + 'apiLevel': '[apiLevel, default is 2.19 ]' + }} + + def run(protocol: protocol_api.ProtocolContext): + # Load modules (if any) + [Module loading code with comments] + + # Load adapters (if any) + [Adapter loading code with comments] + + # Load labware + [Labware loading code with comments] + + # Load pipettes + [Pipette loading code with comments] + + # For Flex protocols using API version 2.16 or later, load trash bin + trash = protocol.load_trash_bin('A3') + + # Protocol steps + [Step-by-step protocol commands with comments] + [Please make sure that the transfer function is used with the new_tip parameter correctly] + ``` + + d) Use the `transfer` function to handle iterations over wells and volumes. Provide lists of source and + destination wells to leverage the function's built-in iteration capabilities. + - The most important thing is to avoid unnecessary loops. Incorrect usages of the loops is as follows: + ```python + for src, dest in zip(source_wells, destination_wells): + pipette.transfer(volume, src, dest, new_tip='always') + ``` + This approach unnecessarily calls the transfer method multiple times and can lead to inefficiencies or errors. + + Correct usage is: + ```python + pipette.transfer(volume, source_wells, destination_wells, new_tip='always') + ``` + + The `transfer` function can handle lists of sources and destinations, automatically pairing them and iterating over them. + Even it can stretch if one of the lists is longer. So no need for explicit loops. + + - Next problem is proper use of `new_tip` parameter. Incorrect usage is using new_tip='once' inside a loop + when intending to reuse the same tip. + ```python + for src, dest in zip(source_wells, destination_wells): + pipette.transfer(volume, src, dest, new_tip='once') + ``` + Correct usage is: + ```python + pipette.transfer(volume, source_wells, destination_wells, new_tip='once') + ``` + + When new_tip='once', the pipette picks up a tip at the beginning of the transfer and uses it throughout. + Using it inside a loop can cause the pipette to attempt to pick up a tip that is already in use, leading to errors. + + + e) In the end, make sure you show generate well-written protocol with proper short but useful comments. + +5. Common model issues to avoid: + - Model outputs `p300_multi` instead of `p300_multi_gen2`. + - Model outputs `thermocyclerModuleV1` instead of `thermocyclerModuleV2`. + - Model outputs `opentrons_flex_96_tiprack_50ul` instead of `opentrons_flex_96_filtertiprack_50ul`. + - Model outputs `opentrons_96_pcr_adapter_nest_wellplate_100ul` instead of + `opentrons_96_pcr_adapter_nest_wellplate_100ul_pcr_full_skirt`. + - Do not forget to define `from opentrons import protocol_api`. + - PCR plate cannot go directly on the Temperature Module. Looking at the documentation and white paper, + you need an appropriate thermal adapter/block between the Temperature Module and the labware. + For PCR plates, you need to: + - First load a PCR thermal block adapter on the module using load_adapter() + - Then load the PCR plate onto the adapter + - If prompt contains CSV file but not provided, then create a CSV data structure as a placeholder. + - ProtocolContext.load_trash_bin method is not available in API version 2.15, must be higher >=2.16. + - If tip rack type is not specified, please use regular tip rack rather than filter tip rack. + - API for `Opentrons 96 PCR Heater-Shaker Adapter with NEST Well Plate 100 ul`is + opentrons_96_pcr_adapter_nest_wellplate_100ul_pcr_full_skirt. + - Include only apiLevel in the requirements dictionary. + - Make sure models does not generate errors such as "Variable 'diluent' is not defined". Define everything then use it. + - If the labware is already with `aluminumblock`, then no need to use `load_adapter`. For example, + `opentrons_96_aluminumblock_nest_wellplate_100ul`, `opentrons_24_aluminumblock_nest_1.5ml_snapcap`: + - Correct + ```python + temp_module = protocol.load_module('temperature module gen2', '4') + dilution_plate = temp_module.load_labware('opentrons_96_aluminumblock_nest_wellplate_100ul') + ``` + + - Incorrect + ```python + temp_module = protocol.load_module('temperature module gen2', 3) + temp_adapter = temp_module.load_adapter('opentrons_96_well_aluminum_block') + dilution_plate = temp_adapter.load_labware('opentrons_96_aluminumblock_nest_wellplate_100ul') + ``` + - when description says explicitly how many rows, you need to use it otherwise you encounter out of tips error: for example, + "For each of the 8 rows in the plate:" + - correct: + ```python + for i in range(8): + row = plate.rows()[i] + ``` + - incorrect: + ```python + for row in plate.rows(): + ``` + - Always check out_of_tips_error_219.md before generating the code + - Use load_trash_bin() for Flex. It is not supported on OT-2. + - By default 'A3' is trash for Flex, it must be defined as: trash = protocol.load_trash_bin('A3'). + - Trying to access .bottom on a list of well locations instead of a single well object. + - Keeping the same tip for all transfers refers `new_tip='once'`, but model outputs `new_tip='always'`. + - If tip racks are not defined, please define them by counting source and destination labware so that outof tips error will be avoided. + - The model generates a protocol that attempted to access non-existent wells (A7-A12) in a 24-well tuberack + which only has positions A1-D6, causing a KeyError when trying to reference well 'A7'. + - Model tries to close thermocycler before opening it. Attempted to access labware inside a closed thermocycler, + the thermocycler must be opened first. + - Required Validation Steps: + - Verify all variables are defined before use + - Confirm tip rack quantity matches transfer count + - Validate all well positions exist in labware + - Check module-labware compatibility + - Verify correct API version for all features used + +6. If slots are not defined, refer to deck_layout.md for proper slot definitions. + Make sure slots are different for different labware. If the source and destination are not defined, + then you define yourself but inform user with your choice, because user may want to change them. + +7. If the request lacks sufficient information to generate a protocol, use casual_examples.md + as a reference to generate a basic protocol. + +Remember to use only the information provided in the . Do not introduce any external information or assumptions. +""" diff --git a/opentrons-ai-server/api/handler/fast.py b/opentrons-ai-server/api/handler/fast.py index 9182f827a9a..b93eb6580ce 100644 --- a/opentrons-ai-server/api/handler/fast.py +++ b/opentrons-ai-server/api/handler/fast.py @@ -1,7 +1,7 @@ import asyncio import os import time -from typing import Annotated, Any, Awaitable, Callable, List, Literal, Union +from typing import Annotated, Any, Awaitable, Callable, List, Literal, Optional, Union import structlog from asgi_correlation_id import CorrelationIdMiddleware @@ -17,6 +17,7 @@ from starlette.middleware.base import BaseHTTPMiddleware from uvicorn.protocols.utils import get_path_with_query_string +from api.domain.anthropic_predict import AnthropicPredict from api.domain.fake_responses import FakeResponse, get_fake_response from api.domain.openai_predict import OpenAIPredict from api.handler.custom_logging import setup_logging @@ -43,7 +44,7 @@ auth: VerifyToken = VerifyToken() openai: OpenAIPredict = OpenAIPredict(settings) google_sheets_client = GoogleSheetsClient(settings) - +claude: AnthropicPredict = AnthropicPredict(settings) # Initialize FastAPI app with metadata app = FastAPI( @@ -179,7 +180,7 @@ async def create_chat_completion( body: ChatRequest, user: Annotated[User, Security(auth.verify)] ) -> Union[ChatResponse, ErrorResponse]: # noqa: B008 """ - Generate a chat completion response using OpenAI. + Generate a chat completion response using LLM. - **request**: The HTTP request containing the chat message. - **returns**: A chat response or an error message. @@ -196,7 +197,12 @@ async def create_chat_completion( fake: FakeResponse = get_fake_response(body.fake_key) return ChatResponse(reply=fake.chat_response.reply, fake=fake.chat_response.fake) return ChatResponse(reply="Default fake response. ", fake=body.fake) - response: Union[str, None] = openai.predict(prompt=body.message, chat_completion_message_params=body.history) + + response: Optional[str] = None + if "openai" in settings.model.lower(): + response = openai.predict(prompt=body.message, chat_completion_message_params=body.history) + else: + response = claude.predict(prompt=body.message) if response is None or response == "": return ChatResponse(reply="No response was generated", fake=bool(body.fake)) @@ -221,7 +227,7 @@ async def update_protocol( body: UpdateProtocol, user: Annotated[User, Security(auth.verify)] ) -> Union[ChatResponse, ErrorResponse]: # noqa: B008 """ - Generate an updated protocol using OpenAI. + Generate an updated protocol using LLM. - **request**: The HTTP request containing the existing protocol and other relevant parameters. - **returns**: A chat response or an error message. @@ -236,7 +242,11 @@ async def update_protocol( if body.fake: return ChatResponse(reply="Fake response", fake=bool(body.fake)) - response: Union[str, None] = openai.predict(prompt=body.prompt, chat_completion_message_params=None) + response: Optional[str] = None + if "openai" in settings.model.lower(): + response = openai.predict(prompt=body.prompt, chat_completion_message_params=None) + else: + response = claude.predict(prompt=body.prompt) if response is None or response == "": return ChatResponse(reply="No response was generated", fake=bool(body.fake)) @@ -261,7 +271,7 @@ async def create_protocol( body: CreateProtocol, user: Annotated[User, Security(auth.verify)] ) -> Union[ChatResponse, ErrorResponse]: # noqa: B008 """ - Generate an updated protocol using OpenAI. + Generate an updated protocol using LLM. - **request**: The HTTP request containing the chat message. - **returns**: A chat response or an error message. @@ -277,7 +287,11 @@ async def create_protocol( if body.fake: return ChatResponse(reply="Fake response", fake=body.fake) - response: Union[str, None] = openai.predict(prompt=str(body.model_dump()), chat_completion_message_params=None) + response: Optional[str] = None + if "openai" in settings.model.lower(): + response = openai.predict(prompt=str(body.model_dump()), chat_completion_message_params=None) + else: + response = claude.predict(prompt=str(body.model_dump())) if response is None or response == "": return ChatResponse(reply="No response was generated", fake=bool(body.fake)) diff --git a/opentrons-ai-server/api/settings.py b/opentrons-ai-server/api/settings.py index 9557b51614b..7d6bcf91459 100644 --- a/opentrons-ai-server/api/settings.py +++ b/opentrons-ai-server/api/settings.py @@ -25,6 +25,8 @@ class Settings(BaseSettings): log_level: str = "info" service_name: str = "local-ai-api" openai_model_name: str = "gpt-4-1106-preview" + anthropic_model_name: str = "claude-3-5-sonnet-20241022" + model: str = "claude" auth0_domain: str = "opentrons-dev.us.auth0.com" auth0_api_audience: str = "sandbox-ai-api" auth0_issuer: str = "https://identity.auth-dev.opentrons.com/" @@ -43,6 +45,7 @@ class Settings(BaseSettings): huggingface_api_key: SecretStr = SecretStr("default_huggingface_api_key") google_credentials_json: SecretStr = SecretStr("default_google_credentials_json") datadog_api_key: SecretStr = SecretStr("default_datadog_api_key") + anthropic_api_key: SecretStr = SecretStr("default_anthropic_api_key") @property def json_logging(self) -> bool: diff --git a/opentrons-ai-server/api/storage/docs/OT2ToFlex.md b/opentrons-ai-server/api/storage/docs/OT2ToFlex.md new file mode 100644 index 00000000000..9263cb453a7 --- /dev/null +++ b/opentrons-ai-server/api/storage/docs/OT2ToFlex.md @@ -0,0 +1,209 @@ +# Adapting OT-2 to Flex + +## Metadata and Requirements: + +- **API Level**: Flex requires an `apiLevel` of 2.15 or higher. If the OT-2 protocol specified `apiLevel` in the `metadata` dictionary, move it to the `requirements` dictionary. Ensure it is not specified in both places to avoid errors. + +- **Robot Type**: Specify `"robotType": "Flex"` in the `requirements` dictionary. If `robotType` is omitted, the API assumes the protocol is designed for the OT-2. + +### Example Conversion: + +#### Original OT-2 Code: + +```python +from opentrons import protocol_api + +metadata = { + "protocolName": "My Protocol", + "description": "This protocol uses the OT-2", + "apiLevel": "2.19" +} +``` + +#### Updated Flex Code: + +```python +from opentrons import protocol_api + +metadata = { + "protocolName": "My Protocol", + "description": "This protocol uses the Flex", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.19" +} +``` + +## Pipettes and Tip Rack Load Names: + +- Flex uses different pipettes and tip racks with unique load names. When converting, load Flex pipettes of the same or larger capacity than the OT-2 pipettes. + +- Using smaller capacity tips than in the OT-2 protocol may require further adjustments to avoid running out of tips, resulting in more steps and longer execution times. + +### Example Conversion: + +#### Original OT-2 Code: + +```python +def run(protocol: protocol_api.ProtocolContext): + tips = protocol.load_labware("opentrons_96_tiprack_300ul", 1) + left_pipette = protocol.load_instrument( + "p300_single_gen2", "left", tip_racks=[tips] + ) +``` + +#### Updated Flex Code: + +```python +def run(protocol: protocol_api.ProtocolContext): + tips = protocol.load_labware("opentrons_flex_96_tiprack_1000ul", "D1") + left_pipette = protocol.load_instrument( + "flex_1channel_1000", "left", tip_racks=[tips] + ) +``` + +## Trash Container: + +- OT-2 protocols have a fixed trash in slot 12. For Flex protocols using API version 2.16 or later, load a trash bin in slot A3 to match the OT-2 fixed trash position: + + ```python + trash = protocol.load_trash_bin("A3") + ``` + + **Note**: Load the trash before any commands that may require discarding tips; otherwise, the robot cannot find it. + +## Deck Slot Labels: + +- Update numeric labels for deck slots (matching OT-2) to coordinate labels (matching Flex). This is optional but recommended for clarity. + +### Deck Slot Correspondence: + +The correspondence between deck labels is based on the relative locations of the slots: + +``` +- 10 to A1 +- 11 to A2 +- Trash to A3 +- 7 to B1 +- 8 to B2 +- 9 to B3 +- 4 to C1 +- 5 to C2 +- 6 to C3 +- 1 to D1 +- 2 to D2 +- 3 to D3 +``` + +**Slots A4, B4, C4, and D4 on Flex have no equivalent on OT-2.** + +## Module Load Names: + +- If your OT-2 protocol uses older generations of the Temperature Module or Thermocycler Module, update the load names you pass to `load_module()` to ones compatible with Flex: + + - Temperature Module: `"temperature module gen2"` + - Thermocycler Module: `"thermocycler module gen2"` or `"thermocyclerModuleV2"` + +- The Heater-Shaker Module (`heaterShakerModuleV1`) is compatible with both Flex and OT-2. + +- **Magnetic Module**: Not compatible with Flex. For protocols that load `magnetic module`, `magdeck`, or `magnetic module gen2`, you need to modify the protocol to use the Magnetic Block and Flex Gripper instead. This requires reworking some protocol steps to achieve similar results. + +### Main Difference: + +- **OT-2**: Pipettes an entire plate's worth of liquid from the Heater-Shaker to the Magnetic Module and then engages the module. + +- **Flex**: The gripper moves the plate to the Magnetic Block in one step, eliminating the need for pipetting between modules. + +### Example Conversion for Magnetic Module: + +#### Original OT-2 Code: + +```python +hs_mod.set_and_wait_for_shake_speed(2000) +protocol.delay(minutes=5) +hs_mod.deactivate_shaker() + +for i in sample_plate.wells(): + # Mix, transfer, and blow-out all samples + pipette.pick_up_tip() + pipette.aspirate(100, hs_plate[i]) + pipette.dispense(100, hs_plate[i]) + pipette.aspirate(100, hs_plate[i]) + pipette.air_gap(10) + pipette.dispense(pipette.current_volume, mag_plate[i]) + pipette.aspirate(50, hs_plate[i]) + pipette.air_gap(10) + pipette.dispense(pipette.current_volume, mag_plate[i]) + pipette.blow_out(mag_plate[i].bottom(0.5)) + pipette.drop_tip() + +mag_mod.engage() +``` + +#### Updated Flex Code: + +```python +hs_mod.set_and_wait_for_shake_speed(2000) +protocol.delay(minutes=5) +hs_mod.deactivate_shaker() + +# Move entire plate using the gripper +hs_mod.open_labware_latch() +protocol.move_labware(sample_plate, mag_block, use_gripper=True) +``` + +## Flex vs. OT-2 Pipettes: + +When converting pipettes, consider the volume ranges: + +### OT-2 Pipettes: + +- **P20 Single-Channel GEN2**: 1–20 µL, `p20_single_gen2` +- **P20 Multi-Channel GEN2**: 1–20 µL, `p20_multi_gen2` +- **P300 Single-Channel GEN2**: 20–300 µL, `p300_single_gen2` +- **P300 Multi-Channel GEN2**: 20–300 µL, `p300_multi_gen2` +- **P1000 Single-Channel GEN2**: 100–1000 µL, `p1000_single_gen2` + +### Flex Pipettes: + +- **Flex 1-Channel Pipette**: 1–50 µL, `flex_1channel_50` +- **Flex 1-Channel Pipette**: 5–1000 µL, `flex_1channel_1000` +- **Flex 8-Channel Pipette**: 1–50 µL, `flex_8channel_50` +- **Flex 8-Channel Pipette**: 5–1000 µL, `flex_8channel_1000` +- **Flex 96-Channel Pipette**: 5–1000 µL, `flex_96channel_1000` + +## Tip Racks: + +### OT-2 Tip Racks: + +- `geb_96_tiprack_1000ul` +- `geb_96_tiprack_10ul` +- `opentrons_96_filtertiprack_1000ul` +- `opentrons_96_filtertiprack_10ul` +- `opentrons_96_filtertiprack_200ul` +- `opentrons_96_filtertiprack_20ul` +- `opentrons_96_tiprack_1000ul` +- `opentrons_96_tiprack_10ul` +- `opentrons_96_tiprack_20ul` +- `opentrons_96_tiprack_300ul` + +### Flex Tip Racks: + +- `opentrons_flex_96_filtertiprack_1000ul` +- `opentrons_flex_96_filtertiprack_200ul` +- `opentrons_flex_96_filtertiprack_50ul` +- `opentrons_flex_96_tiprack_1000ul` +- `opentrons_flex_96_tiprack_200ul` +- `opentrons_flex_96_tiprack_50ul` + +**Note**: When converting, match the pipette and tip rack volumes to ensure the protocol functions correctly. + +## Additional Notes: + +- **Trash Bin**: Remember to load the trash bin before any commands that may require discarding tips. + +- **Deck Slots**: Adjust deck slot labels to match the Flex coordinate system for clarity, although numeric labels are still valid. + +- **Verification**: After adapting the protocol, verify that the new design achieves similar results, especially if significant changes were made (e.g., replacing the Magnetic Module with the Magnetic Block). diff --git a/opentrons-ai-server/api/storage/docs/casual_examples.md b/opentrons-ai-server/api/storage/docs/casual_examples.md new file mode 100644 index 00000000000..4d1e778d5d5 --- /dev/null +++ b/opentrons-ai-server/api/storage/docs/casual_examples.md @@ -0,0 +1,979 @@ +The following examples show casual descriptions and their corresponding potential protocols. + +#### Example 1: PCR protocol + + +I want to run a PCR setup protocol with temperature control. I need to prepare 64 samples (that's 8 full columns) using both mastermix and samples. Let's keep the samples cold at 4°C and the mastermix at 10°C using temperature modules. + +Here's what I want to do: + +First, I'll use a multichannel P20 pipette mounted on the left side. I'll have three plates: one for samples (on the cold module), one for mastermix (on the slightly warmer module), and one destination plate where we'll mix everything together. + +The steps should go like this: + +1. Start by setting both temperature modules - 4°C for samples and 10°C for mastermix +2. Take one tip and use it to transfer 7 µL of mastermix to each destination well for all 64 samples +3. For the samples, I want to transfer 5 µL from each well to the corresponding destination well. Use fresh tips for each column, mix 9 times with 12 µL volume, and make sure to blow out into the destination well +4. When we're done, turn off both temperature modules + +Remember to work column by column since we're using a multichannel pipette, and we'll be handling the first 8 columns of each plate. + + + +from opentrons import protocol_api + +requirements = {"robotType": "OT-2", "apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): # Module loading +temp_mod_sample = protocol.load_module('temperature module gen2', 1) +temp_mod_mastermix = protocol.load_module('temperature module gen2', 3) + + # Load thermal adapters + sample_adapter = temp_mod_sample.load_adapter("opentrons_96_well_aluminum_block") + mastermix_adapter = temp_mod_mastermix.load_adapter("opentrons_96_well_aluminum_block") + + # Load labware onto the adapters + sample_plate = sample_adapter.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + mastermix_plate = mastermix_adapter.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + + dest_plate = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 7) + tiprack = protocol.load_labware('opentrons_96_filtertiprack_20ul', 4) + + # Pipette loading + p20_single = protocol.load_instrument('p20_multi_gen2', 'left', tip_racks=[tiprack]) + + # Well allocation + num_samples = 64 + num_cols = num_samples // 8 + sample_wells = sample_plate.columns()[:num_cols] + mastermix_wells = mastermix_plate.columns()[:num_cols] + dest_wells = dest_plate.columns()[:num_cols] + + # Volume and temperature parameters + sample_temp = 4 # Celsius + mastermix_temp = 10 # Celsius + mastermix_vol = 7 # uL + sample_vol = 5 # uL + mix_cycles = 9 + total_mix_vol = sample_vol + mastermix_vol # 12 uL total + + # Execute commands in order specified in description + # Command 1: Set sample temperature + temp_mod_sample.set_temperature(sample_temp) + + # Command 2: Set mastermix temperature + temp_mod_mastermix.set_temperature(mastermix_temp) + + # Command 3: Transfer mastermix + p20_single.transfer( + mastermix_vol, + mastermix_wells, + dest_wells, + new_tip='once' + ) + + # Command 4: Transfer samples and mix + p20_single.transfer( + sample_vol, + sample_wells, + dest_wells, + new_tip='always', + mix_after=(mix_cycles, total_mix_vol), + blow_out=True, + blowout_location='destination well' + ) + + # Command 5 and 6: Deactivate temperature modules + temp_mod_mastermix.deactivate() + temp_mod_sample.deactivate() + + + +#### Example 2: PCR protocol + + +I want to set up a PCR reaction plate using both single-channel and multi-channel pipettes. Here's what we need to do: + +First, using the single-channel pipette on the right: + +- We'll add 7 microliters of mastermix from tubes in the tube rack to specific wells in our destination plate. The source tubes and destination wells are listed in a CSV file. Let's use a fresh tip for each different mastermix tube we work with. + +Then, using the 8-channel pipette on the left: + +- We're going to transfer 3 microliters of samples in triplicate. Here's how: +- Take samples from column 1 of the source plate and transfer them to: + - Column 1 of the destination plate (change tip) + - Column 2 of the destination plate (change tip) + - Column 3 of the destination plate +- Repeat this same pattern for the remaining columns in the source plate, always making three copies of each column and changing tips between transfers. + + + +from opentrons import protocol_api + +requirements = { +'robotType': 'Flex', +'apiLevel': '2.15' +} + +def run(protocol: protocol_api.ProtocolContext): + + csv_samp = """ + Primer Tube,Destination well + A1,A1 + B1,B1 + C1,C1 + D1,D1 + A2,E1 + B2,F1 + C2,G1 + D2,H1 + A3,A2 + B3,B2 + C3,C2 + D3,D2 + A4,E2 + B4,F2 + C4,G2 + D4,H2 + A5,A3 + B5,B3 + C5,C3 + D5,D3 + A6,E3 + B6,F3 + C6,G3 + D6,H3 + """ + # Convert to list + csv_lines = [[val.strip() for val in line.split(',')] + for line in csv_samp.splitlines() + if line.split(',')[0].strip()][1:] + + NUM_COL = 3 + STRIDE = 3 + + # Load labware + tuberack = protocol.load_labware('opentrons_24_tuberack_nest_2ml_snapcap', 'C1') + dna_plate = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 'D3') + dest_plate = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 'D1') + + tiprack_single = protocol.load_labware('opentrons_flex_96_tiprack_50ul', 'A2') + tiprack_multi = protocol.load_labware('opentrons_flex_96_tiprack_50ul', 'B2') + + # Load pipette + single_pip = protocol.load_instrument("flex_1channel_50", 'right', tip_racks=[tiprack_single]) + multi_pip = protocol.load_instrument("flex_8channel_50", 'left', tip_racks=[tiprack_multi]) + + # transfer mastermix + for source_tube, dest_well in csv_lines: + single_pip.pick_up_tip() + single_pip.transfer(7, tuberack[source_tube], dest_plate[dest_well], new_tip='never') + single_pip.drop_tip() + + # Transfer in triplicate + col_ctr = 0 + for s in dna_plate.rows()[0][:NUM_COL]: + multi_pip.pick_up_tip() + multi_pip.transfer(3, s, dest_plate.rows()[0][col_ctr], new_tip='never') + multi_pip.drop_tip() + + multi_pip.pick_up_tip() + multi_pip.transfer(3, s, dest_plate.rows()[0][col_ctr+1], new_tip='never') + multi_pip.drop_tip() + + multi_pip.pick_up_tip() + multi_pip.transfer(3, s, dest_plate.rows()[0][col_ctr+2], new_tip='never') + multi_pip.drop_tip() + + col_ctr += STRIDE + + + +#### Example 3: Transfer reagent protocol + + +I want to do a series of liquid transfers using two different pipettes. Here's what we need to do: + +First, using the P20 single-channel pipette on the right: + +- Take 15 microliters from the first well of our reservoir and transfer it to every well in both of our destination plates. We can use the same tip for all these transfers. +- Then, transfer 20 microliters from each well of our 384-well source plate to the corresponding wells in our first destination plate (the 384-well plate). We can keep using the same tip for these transfers too. + +Next, using the P300 single-channel pipette on the left: + +- Transfer 100 microliters from each well of our 96-well source plate to the corresponding wells in our second destination plate (the 96-well plate). For this step, we'll need to use a fresh tip for each transfer. + + + +from opentrons import protocol_api + +# metadata + +metadata = { +"protocolName": "Reagent Transfer ", +"author": "OGA", +"description": "Transfer reagents from multile source labware to multiple destination labware", +"apiLevel": "2.16", +} + +def run(protocol: protocol_api.ProtocolContext): # labware +source_1 = protocol.load_labware("nest_1_reservoir_195ml", location=7) +source_2 = protocol.load_labware("biorad_384_wellplate_50ul", location=8) +source_3 = protocol.load_labware("biorad_96_wellplate_200ul_pcr", location=9) +destination_1 = protocol.load_labware("corning_384_wellplate_112ul_flat", location=1) +destination_2 = protocol.load_labware("corning_96_wellplate_360ul_flat", location=2) + + tiprack300 = protocol.load_labware("opentrons_96_tiprack_300ul", location=10) + tiprack20 = protocol.load_labware("opentrons_96_tiprack_20ul", location=11) + + # pipettes + p300s = protocol.load_instrument("p300_single_gen2", mount="left", tip_racks=[tiprack300]) + p20s = protocol.load_instrument("p20_single_gen2", mount="right", tip_racks=[tiprack20]) + + # volumes setup + transfer_vol_1 = 15 + transfer_vol_2 = 20 + transfer_vol_3 = 100 + + # wells setup + source_wells_1 = source_1.wells_by_name()['A1'] + source_wells_2 = source_2.wells() + source_wells_3 = source_3.wells() + destination_wells_1 = destination_1.wells() + destination_wells_2 = destination_2.wells() + all_destinations = destination_wells_1 + destination_wells_2 + + # commands + p20s.transfer(transfer_vol_1, source_wells_1, all_destinations, new_tip="once") + p20s.transfer(transfer_vol_2, source_wells_2, destination_wells_1, new_tip="once") + p300s.transfer(transfer_vol_3, source_wells_3, destination_wells_2, new_tip="always") + + + +#### Example 4: Transfer reagent protocol + + +I want to pool samples from multiple tube racks into a deep well plate. I'll be using a single-channel P300 pipette mounted on the right side. + +Here's what we need to do: + +1. Take 20 µL from each tube in the first tube rack and pool them all into well A1 of the deep well plate. We'll use a fresh tip for each tube. + +2. Then, take 20 µL from each tube in the second tube rack and pool them all into well B1 of the deep well plate. Again, use a fresh tip for each tube. + +3. Next, take 20 µL from each tube in the third tube rack and pool them all into well C1 of the deep well plate. Use a fresh tip for each tube. + +4. Finally, take 20 µL from each tube in the fourth tube rack and pool them all into well D1 of the deep well plate. Use a fresh tip for each tube. + + + +from opentrons import protocol_api + +metadata = { +'protocolName': 'Sample Aliquoting & Plate Prep', +'author': 'ChatGPT', +'apiLevel': '2.16' +} + +def run(protocol: protocol_api.ProtocolContext): + + # Load labware + source_labware1 = protocol.load_labware('opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap', 1) + source_labware2 = protocol.load_labware('opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap', 2) + source_labware3 = protocol.load_labware('opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap', 4) + source_labware4 = protocol.load_labware('opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap', 5) + destination_labware = protocol.load_labware('nest_96_wellplate_2ml_deep', 3) + + tiprack1 = protocol.load_labware('opentrons_96_filtertiprack_200ul', 7) + tiprack2 = protocol.load_labware('opentrons_96_filtertiprack_200ul', 8) + + # Load pipette + p300_single = protocol.load_instrument('p300_single_gen2', 'right', tip_racks=[tiprack1, tiprack2]) + + # Transfer samples + p300_single.transfer(20, source_labware1.wells(), destination_labware.wells_by_name()['A1'], new_tip='always') + p300_single.transfer(20, source_labware2.wells(), destination_labware.wells_by_name()['B1'], new_tip='always') + p300_single.transfer(20, source_labware3.wells(), destination_labware.wells_by_name()['C1'], new_tip='always') + p300_single.transfer(20, source_labware4.wells(), destination_labware.wells_by_name()['D1'], new_tip='always') + + + +#### Example 5: Reagent transfer protocol + + +I want to perform a series of liquid transfers using two different single-channel pipettes. Here's what we need to do: + +First, using the 50 µL pipette mounted on the left: + +- Take 15 µL from the reservoir and transfer it to every well in both our 384-well and 96-well destination plates. We can use the same tip for all these transfers. +- Then, transfer 20 µL from each well of our 384-well source plate to the corresponding wells in our 384-well destination plate. We can keep using the same tip for these transfers too. + +Finally, using the 1000 µL pipette mounted on the right: + +- Transfer 100 µL from each well of our 96-well source plate to the corresponding wells in our 96-well destination plate. For this step, we'll need to use a fresh tip for each transfer. + + + +from opentrons import protocol_api + +# metadata + +metadata = { +"protocolName": "Reagent Transfer ", +"author": "Opentrons Generative AI", +"description": "Transfer reagents from multile source labware to multiple destination labware", +} + +requirements = {"robotType": "Flex", "apiLevel": "2.16"} + +def run(protocol: protocol_api.ProtocolContext): # labware +source_1 = protocol.load_labware("nest_1_reservoir_195ml", location='B1') +source_2 = protocol.load_labware("biorad_384_wellplate_50ul", location='B2') +source_3 = protocol.load_labware("biorad_96_wellplate_200ul_pcr", location='B3') +destination_1 = protocol.load_labware("corning_384_wellplate_112ul_flat", location='D1') +destination_2 = protocol.load_labware("corning_96_wellplate_360ul_flat", location='D2') + + tip1000 = protocol.load_labware('opentrons_flex_96_filtertiprack_200ul', 'A1') + tip50 = protocol.load_labware("opentrons_flex_96_filtertiprack_50ul", location='A2') + + # pipettes + p1000s = protocol.load_instrument('flex_1channel_1000','right',tip_racks = [tip1000]) + p50s = protocol.load_instrument('flex_1channel_50','left',tip_racks = [tip50]) + + # load trash bin + trash = protocol.load_trash_bin('A3') + + # volumes setup + transfer_vol_1 = 15 + transfer_vol_2 = 20 + transfer_vol_3 = 100 + + # wells setup + source_wells_1 = source_1.wells_by_name()['A1'] + source_wells_2 = source_2.wells() + source_wells_3 = source_3.wells() + destination_wells_1 = destination_1.wells() + destination_wells_2 = destination_2.wells() + + # commands + p50s.transfer(transfer_vol_1, source_wells_1, destination_wells_1+destination_wells_2, new_tip="once") + p50s.transfer(transfer_vol_2, source_wells_2, destination_wells_1, new_tip="once") + p1000s.transfer(transfer_vol_3, source_wells_3, destination_wells_2, new_tip="always") + + + +#### Example 6: Reagent transfer protocol + + +I want to pool samples from two different plates into a reservoir using a single-channel pipette mounted on the left side. Here's what we need to do: + +First, let's pool samples from our first source plate: + +- Take 100 µL from each well in the first plate and transfer it to the first well of our reservoir +- We can use the same tip for all these transfers to save time + +Then, for our second source plate: + +- Again, take 100 µL from each well and add it to the same well in our reservoir where we pooled the first set +- Keep using the same tip for these transfers too + +Remember, we're treating these as two separate steps, but both are basically pooling samples from different source plates into the same destination well. + + + +from opentrons import protocol_api + +# metadata + +metadata = { +'protocolName': 'Reagent Transfer', +'author': 'Opentrons Generative AI', +} + +requirements = {"robotType": "Flex", "apiLevel": "2.16"} + +# protocol run function + +def run(protocol: protocol_api.ProtocolContext): # labware +source_1 = protocol.load_labware("corning_96_wellplate_360ul_flat", location='C1') +source_2 = protocol.load_labware("corning_96_wellplate_360ul_flat", location='C2') +destination_1 = protocol.load_labware("nest_1_reservoir_195ml", location='D1') + + tiprack200 = protocol.load_labware("opentrons_flex_96_filtertiprack_200ul", location='B2') + + # pipettes + p1000s = protocol.load_instrument("flex_1channel_1000", mount="left", tip_racks=[tiprack200]) + + # load trash bin + trash = protocol.load_trash_bin('A3') + + # volume setup + transfer_vol_1 = 100 + transfer_vol_2 = 100 + + # wells setup + source_wells_1 = source_1.wells() + source_wells_2 = source_2.wells() + destination_wells_1 = destination_1.wells_by_name()['A1'] + + # commands + p1000s.transfer(transfer_vol_1, source_wells_1, destination_wells_1, new_tip="once") + + p1000s.transfer(transfer_vol_2, source_wells_2, destination_wells_1, new_tip="once") + + + +#### Example 7: PCR protocol + + +I want to run a PCR protocol using three temperature-controlled modules: a thermocycler and two temperature modules (one for samples and one for mastermix). Here's what we need to do: + +First, let's set up our temperatures: + +- Set the thermocycler block to 22°C and its lid to 95°C +- Warm up the sample temperature module to 37°C +- Cool down the mastermix module to 10°C + +For the liquid handling steps, using our 96-channel pipette: + +1. Transfer 20 µL of mastermix, taking it from 5mm below the liquid surface and dispensing it 2mm from the bottom of the destination wells. We can use the same tip for this. + +2. Next, transfer 20 µL of sample, aspirating from 3mm above the well bottom and dispensing 7mm from the top of the destination wells. Do this at half the normal flow rate. Mix everything well - 5 cycles with the total 40 µL volume. When finished, pull the tips out slowly at 5 mm/s. Use the same tip for this transfer. + +For the PCR cycling: + +1. Move our plate to the thermocycler and close the lid +2. Run these steps: + - One cycle at 74°C for 65 seconds + - 25 cycles of: + - 60°C for 7 seconds + - 84°C for 19 seconds + - 57°C for 44 seconds + - One final cycle at 75°C for 8 minutes + - Hold everything at 4°C + +Finally: + +1. Open the thermocycler lid and move the plate back to its original position +2. We'll pause here - you'll need to seal the plate and put it in the fridge at 4°C +3. Turn off all the temperature modules + + + +from opentrons import protocol_api + +metadata = { +'protocol_name': 'PCR Amplification protocol', +'author': 'Opentrons Generative AI', +'description': 'PCR Amplification protocol with 25 cycles', +} + +requirements = {"robotType": "Flex", "apiLevel": "2.16"} + +def run(protocol: protocol_api.ProtocolContext): # Sample parameters +sample_volume_ul = 20 +master_mix_volume_ul = 20 +mix_cycles = 5 +total_mix_volume_ul = sample_volume_ul + master_mix_volume_ul +return_slot = 'C3' + + master_mix_temperature_c = 10 + sample_temperature_c = 37 + step1_cycles = 1 + step2_cycles = 25 + step3_cycles = 1 + + # Thermocycler parameters + lid_temperature_c = 95 + initial_block_temperature_c = 22 + final_hold_temperature_c = 4 + + # Modules + thermocycler_module = protocol.load_module('thermocyclerModuleV2') + sample_temperature_module = protocol.load_module('temperature module gen2', 'D1') + master_mix_temperature_module = protocol.load_module('temperature module gen2', 'D3') + + # Adapters + sample_adapter = sample_temperature_module.load_adapter('opentrons_96_well_aluminum_block') + master_mix_adapter = master_mix_temperature_module.load_adapter('opentrons_96_well_aluminum_block') + + # Labware + sample_plate = sample_adapter.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + master_mix_plate = master_mix_adapter.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + destination_plate = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 'C3') + tips_50ul = [ + protocol.load_labware( + 'opentrons_flex_96_filtertiprack_50ul', + slot, + adapter="opentrons_flex_96_tiprack_adapter" + ) + for slot in ['A2', 'B2', 'C2'] + ] + + # Pipette + pipette_96channel = protocol.load_instrument('flex_96channel_1000', 'left', tip_racks=tips_50ul) + # load trash bin + trash = protocol.load_trash_bin('A3') + + # Well allocation + sample_source_wells = sample_plate['A1'] + destination_wells = destination_plate['A1'] + master_mix_source_well = master_mix_plate['A1'] + + # Set thermocycler block and lid temperature + thermocycler_module.set_block_temperature(initial_block_temperature_c) + thermocycler_module.open_lid() + thermocycler_module.set_lid_temperature(lid_temperature_c) + + # Temperature module setup + sample_temperature_module.set_temperature(sample_temperature_c) + master_mix_temperature_module.set_temperature(master_mix_temperature_c) + + # Master mix transfer + pipette_96channel.transfer( + master_mix_volume_ul, + master_mix_source_well.top(-5), + destination_wells.bottom(2), + new_tip='once' + ) + + # Sample transfer + pipette_96channel.pick_up_tip() + pipette_96channel.aspirate(sample_volume_ul, sample_source_wells.bottom(3), rate=0.5) + pipette_96channel.dispense(sample_volume_ul, destination_wells.top(-7), rate=0.5) + pipette_96channel.mix(mix_cycles, total_mix_volume_ul) + pipette_96channel.move_to(destination_wells.top(), speed=5) + pipette_96channel.drop_tip() + + # Moving the plate to the thermocycler + protocol.move_labware(destination_plate, thermocycler_module, use_gripper=True) + + # PCR cycling + thermocycler_module.close_lid() + thermocycler_module.execute_profile( + steps=[ + {'temperature': 74, 'hold_time_seconds': 65} + ], + repetitions=step1_cycles, + block_max_volume=total_mix_volume_ul + ) + + thermocycler_module.execute_profile( + steps=[ + {'temperature': 60, 'hold_time_seconds': 7}, + {'temperature': 84, 'hold_time_seconds': 19}, + {'temperature': 57, 'hold_time_seconds': 44} + ], + repetitions=step2_cycles, + block_max_volume=total_mix_volume_ul + ) + + thermocycler_module.execute_profile( + steps=[{'temperature': 75, 'hold_time_seconds': 480}], + repetitions=step3_cycles, + block_max_volume=total_mix_volume_ul + ) + + thermocycler_module.set_block_temperature(final_hold_temperature_c) + thermocycler_module.open_lid() + + # Moving the plate back to its original location + protocol.move_labware(destination_plate, return_slot, use_gripper=True) + + # Optional: pause for manual intervention + protocol.pause("Pick up the destination plate, seal it, and refrigerate at 4C.") + + # Deactivate temperature modules at the end of the protocol + master_mix_temperature_module.deactivate() + sample_temperature_module.deactivate() + + + +#### Example 8: Serial dilution protocol + + +I want to perform a serial dilution using an 8-channel pipette mounted on the right side. Here's what we need to do: + +First, let's set up our key measurements: + +- We're doing a 1:3 dilution series with 10 dilution steps +- We'll work with a total volume of 150 µL in each well +- This means we'll transfer 50 µL between wells and add 100 µL of diluent +- We'll use a 10 µL air gap for all our transfers + +Here's the step-by-step process: + +1. Start by adding diluent to our plate: + + - Using one tip, transfer 100 µL of diluent (green liquid) from the reservoir to wells A2 through A11 + - Keep using the same tip and remember to use the air gap for each transfer + +2. Now for the serial dilution: + + - Get a fresh tip + - Starting with well A1 (which has our red sample), transfer 50 µL to well A2 + - Mix well - 5 times with 75 µL + - Continue this pattern down the row: + - Transfer 50 µL from A2 to A3, mix + - A3 to A4, mix + - And so on until you reach A11 + - Use the same tip for all these transfers and remember the air gap + +3. Finally, let's add our blank: + - Get a fresh tip + - Transfer 100 µL of diluent to well A12 + - Use the air gap for this transfer too + + + +metadata = { + 'protocolName': 'Customizable Serial Dilution', + 'author': 'Opentrons ', + 'source': 'Protocol Library' +} + +requirements = { +"robotType": "Flex", +"apiLevel": "2.19" +} + +def run(protocol): + + # Constants + DILUTION_FACTOR = 3 + NUM_DILUTIONS = 10 + TOTAL_MIXING_VOLUME = 150.0 + AIR_GAP_VOLUME = 10 + + # Calculated volumes + transfer_volume = TOTAL_MIXING_VOLUME / DILUTION_FACTOR + diluent_volume = TOTAL_MIXING_VOLUME - transfer_volume + + # Labware setup + trough = protocol.load_labware('nest_12_reservoir_15ml', 'D2') + plate = protocol.load_labware('nest_96_wellplate_200ul_flat', 'D3') + tip_name = "opentrons_flex_96_filtertiprack_1000ul" + tipracks = [ + protocol.load_labware(tip_name, slot) + for slot in ["C1", "D1"] + ] + + # Pipette setup + pipette = protocol.load_instrument('flex_8channel_1000', 'right', tipracks) + + # Waste setup + trash = protocol.load_trash_bin("A3") + + # Reagent setup + diluent = trough.wells()[0] + source = plate.columns()[0] + + # Define and load liquids + diluent_liquid = protocol.define_liquid( + name="Dilutent", + description="Diluent liquid is filled in the reservoir", + display_color="#33FF33" + ) + sample_liquid = protocol.define_liquid( + name="Sample", + description="Non-diluted samples are loaded in the 1st column", + display_color="#FF0000" + ) + + diluent.load_liquid(liquid=diluent_liquid, volume=0.8 * diluent.max_volume) + for well in source: + well.load_liquid(liquid=sample_liquid, volume=TOTAL_MIXING_VOLUME) + + # Set up dilution destinations + dilution_destination_sets = [[row] for row in plate.rows()[0][1:NUM_DILUTIONS+1]] + dilution_source_sets = [[row] for row in plate.rows()[0][:NUM_DILUTIONS]] + blank_set = [plate.rows()[0][NUM_DILUTIONS+1]] + + # 1. Distribute diluent + all_diluent_destinations = [well for wells in dilution_destination_sets for well in wells] + pipette.pick_up_tip() + for dest in all_diluent_destinations: + pipette.transfer( + diluent_volume, + diluent, + dest, + air_gap=AIR_GAP_VOLUME, + new_tip='never' + ) + pipette.drop_tip() + + # 2. Perform serial dilutions + pipette.pick_up_tip() + for source_set, dest_set in zip(dilution_source_sets, dilution_destination_sets): + for s, d in zip(source_set, dest_set): + pipette.transfer( + transfer_volume, + s, + d, + air_gap=AIR_GAP_VOLUME, + mix_after=(5, TOTAL_MIXING_VOLUME/2), + new_tip='never' + ) + pipette.drop_tip() + + # 3. Add blank + pipette.pick_up_tip() + for blank_well in blank_set: + pipette.transfer( + diluent_volume, + diluent, + blank_well, + air_gap=AIR_GAP_VOLUME, + new_tip='never' + ) + pipette.drop_tip() + + + +#### Example 9: Serial dilution + + +I want to perform a serial dilution protocol using a multi-channel P300 pipette mounted on the left side. We'll be working with a temperature-controlled setup and need to achieve a 1.5x dilution factor across 10 wells, with a total mixing volume of 150 µL per well. + +Here's what we need to do: + +First, let's calculate our volumes: + +- Transfer volume will be 150 µL divided by 1.5 +- Diluent volume will be 150 µL minus our transfer volume + +Now for the actual steps: + +1. Let's start by adding diluent to our dilution wells: + + - Take diluent from the first reservoir well and add our calculated diluent volume to wells 2 through 10 in the first row of our temperature-controlled plate + - Use a 10 µL air gap for each transfer + - Use fresh tips for each well + +2. Now for the serial dilution: + + - Starting from well 1, we'll transfer our calculated transfer volume to well 2 + - After each transfer, mix 5 times using (150 µL - 5 µL) + - Keep using a 10 µL air gap + - Use new tips for each transfer + - Continue this pattern, moving from well to well until we reach well 10 + +3. Finally, add a blank to the last well: + - Transfer our calculated diluent volume from the first reservoir well to well 10 + - Use a 10 µL air gap + - Use a fresh tip for this transfer + + + +metadata = { + 'protocolName': 'Serial Dilution for Eskil', + 'author': 'John C. Lynch', + 'source': 'Custom Protocol Request', + 'apiLevel': '2.19' +} + +def run(protocol): + + # Constants + PLATE_TYPE = 'opentrons_96_aluminumblock_nest_wellplate_100ul' + DILUTION_FACTOR = 1.5 + NUM_DILUTIONS = 10 + TOTAL_MIXING_VOLUME = 150 + + # Calculated volumes + transfer_volume = TOTAL_MIXING_VOLUME / DILUTION_FACTOR + diluent_volume = TOTAL_MIXING_VOLUME - transfer_volume + + # Load temperature module and labware + temp_module = protocol.load_module('temperature module gen2', '4') + reservoir = protocol.load_labware('nest_12_reservoir_15ml', '1') + dilution_plate = temp_module.load_labware(PLATE_TYPE) + + # Load tipracks + tipracks = [ + protocol.load_labware('opentrons_96_tiprack_300ul', slot) + for slot in ['2', '3'] + ] + + # Load pipette + pipette = protocol.load_instrument( + 'p300_multi_gen2', + mount='left', + tip_racks=tipracks + ) + + # 1. Distribute diluent + pipette.transfer( + diluent_volume, + reservoir.wells()[0], + dilution_plate.rows()[0][1:NUM_DILUTIONS], + air_gap=10, + new_tip='always' + ) + + # 2. Perform serial dilutions + sources = dilution_plate.rows()[0][:NUM_DILUTIONS-1] + dests = dilution_plate.rows()[0][1:NUM_DILUTIONS] + + pipette.transfer( + transfer_volume, + sources, + dests, + air_gap=10, + mix_after=(5, TOTAL_MIXING_VOLUME-5), + new_tip='always' + ) + + # 3. Add blank + pipette.transfer( + diluent_volume, + reservoir.wells()[0], + dilution_plate.rows()[0][-1], + air_gap=10, + new_tip='always' + ) + + + +#### Example 10 + + +I want to perform a serial dilution using a single-channel pipette mounted on the left side. Here's what we need to do: + +First, let's add our diluent: + +- Take 100 µL of diluent from the first well of our reservoir and distribute it to every well in our plate. + +Then, for the serial dilution: + +- For each of the 8 rows in our plate: + 1. Start by transferring 100 µL of our solution from well A2 of the reservoir to the first well of the row + 2. Mix it well - 3 times with 50 µL + 3. Then move along the row from left to right: + - Transfer 100 µL from each well to the next well + - Mix 3 times with 50 µL after each transfer + - Continue this pattern for 11 transfers to complete the row + 4. Repeat this process for all 8 rows + + + +from opentrons import protocol_api + +metadata = { +"protocolName": "Serial Dilution Tutorial – Flex 1-channel", +"description": """serial dilution""", +"author": "New API User" +} + +requirements = { +"robotType": "Flex", +"apiLevel": "2.16" +} + +def run(protocol: protocol_api.ProtocolContext): +tips = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "D1") +reservoir = protocol.load_labware("nest_12_reservoir_15ml", "D2") +plate = protocol.load_labware("nest_96_wellplate_200ul_flat", "D3") +trash = protocol.load_trash_bin("A3") +left_pipette = protocol.load_instrument("flex_1channel_1000", "left", tip_racks=[tips]) + + # distribute diluent + left_pipette.transfer(100, reservoir["A1"], plate.wells()) + + # loop through each row + for i in range(8): + + # save the destination row to a variable + row = plate.rows()[i] + + # transfer solution to first well in column + left_pipette.transfer(100, reservoir["A2"], row[0], mix_after=(3, 50)) + + # dilute the sample down the row + left_pipette.transfer(100, row[:11], row[1:], mix_after=(3, 50)) + + + +#### Example 11 + + +Liquid Transfer with Heater-Shaker + +prompt description: + +```text +Hi, please help me write a protocol for Flex. +Pipette: 96 channel +Decklayout: +Slot C1: Nest 195 1 well reservoir +Slot D1: Heater shaker module with corning 96 well flat bottom plate +Slot A2: 200 ul tiprack +Slot B2: 1000 ul tiprack for flex +Slot C2: 50 ul tiprack +Slot D2: PCR plate Steps: + +Open the labware latch and allow the user to load the plate +Close the labware latch +Using 200 ul tip transfer 70 ul liquid from slot c1 to d1. +Using 50 ul tip transfer 10 ul liquid from slot d2 to D1. +shake the plate at 2000 rpm for 1 minute +``` + + + + + +```python +from opentrons import protocol_api + +metadata = {{ + 'protocolName': 'Liquid Transfer with Heater Shaker', + 'author': 'User', + 'description': "Transfer liquids between reservoir, PCR plate, and heater shaker module's plate." +}} +requirements = {{"robotType": "Flex", "apiLevel": "2.19"}} + +def run(protocol: protocol_api.ProtocolContext): + # Load trash before commands + # use a waste chute or a trashbin depending on the setup + trash = protocol.load_trash_bin("A3") + #chute = protocol.load_waste_chute() + + # Modules + heater_shaker_module = protocol.load_module('heaterShakerModuleV1', 'D1') + heater_shaker_plate = heater_shaker_module.load_labware('corning_96_wellplate_360ul_flat') + + # Labware + reservoir = protocol.load_labware('nest_1_reservoir_195ml', 'C1') + pcr_plate = protocol.load_labware('nest_96_wellplate_200ul_flat', 'D2') + tiprack_200ul = protocol.load_labware('opentrons_flex_96_tiprack_200ul', 'A2', adapter = "opentrons_flex_96_tiprack_adapter") + tiprack_1000ul = protocol.load_labware('opentrons_flex_96_tiprack_1000ul', 'B2', adapter = "opentrons_flex_96_tiprack_adapter") + tiprack_50ul = protocol.load_labware('opentrons_flex_96_tiprack_50ul', 'C2', adapter = "opentrons_flex_96_tiprack_adapter") + + # Pipettes + pip96 = protocol.load_instrument('flex_96channel_1000', mount='left', tip_racks=[tiprack_200ul, tiprack_50ul]) + + # Steps + # 1. Open the labware latch and allow the user to load the plate + heater_shaker_module.open_labware_latch() + + + protocol.pause("Please put the Corning 96 well plate and press continue") + # 2. Close the labware latch + heater_shaker_module.close_labware_latch() + protocol.comment("Just a message is displayed. This step is") + # 3. Using 200 ul tip transfer 70 ul liquid from slot c1 to d1. + + pip96.transfer(70, reservoir['A1'], heater_shaker_plate['A1'], new_tip='always') + + # 4. Using 50 ul tip transfer 10 ul liquid from slot d2 to D1. + pip96.transfer(10, pcr_plate['A1'], heater_shaker_plate['A1'], new_tip='always') + + # 5. Shake the plate at 2000 rpm for 1 minute + heater_shaker_module.set_and_wait_for_shake_speed(rpm=2000) + protocol.delay(minutes=1) + heater_shaker_module.deactivate_shaker() +``` + + diff --git a/opentrons-ai-server/api/storage/docs/commands-v0.0.1.md b/opentrons-ai-server/api/storage/docs/commands-v0.0.1.md new file mode 100644 index 00000000000..000e874f843 --- /dev/null +++ b/opentrons-ai-server/api/storage/docs/commands-v0.0.1.md @@ -0,0 +1,1259 @@ +#### + +Note when working with temperature module +PCR plate does not go directly on the module. We need thermal adapter. +Temperature Module White Paper suggests using the "PCR block" and a water. + +Hence the following pattern: + +```python +temp_module = protocol.load_module('temperature module gen2', 1) +adapter = temp_module.load_adapter("opentrons_96_well_aluminum_block") +plate = adapter.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') +``` + +#### + +A good example of using a `transfer` method: +The following is correct: + +```python +p1000s.transfer(transfer_vol, src, dest_wells, new_tip='always') +``` + +The following is incorrect: + +```python +for src in src_wells: + p1000s.transfer(transfer_vol, src, dest_wells, new_tip='always') +``` + +Note that `transfer` function uses `for` operator implicitly. + +#### + +Using Flex 1-Channel 1000 uL Pipette on left mount, transfer 50 uL from wells A1, A2 in source labware 1 +to B6, B7 in source labware 2. Reuse the same tip for each transfer. + +The following is correct: + +```python +transfer_vol_1 = 50 # setup volume + +source_wells_1 = [source_1.wells_by_name()[wells] for wells in ['A1', 'A2']] # source setup wells +destination_wells_1 = [source_2.wells_by_name()[wells] for wells in ['B6', 'B7']] # destination setup wells + +p1000s.transfer(transfer_vol_1, source_wells_1, destination_wells_1, new_tip="once") # transfer function without any loop +``` + +The following is not correct since it calls transfer function twice rather than once: + +```python +p300_single.transfer(50, source_labware_1.wells_by_name()['A1'], source_labware_2.wells_by_name()['B6'], new_tip='once') +p300_single.transfer(50, source_labware_1.wells_by_name()['A2'], source_labware_2.wells_by_name()['B7'], new_tip='never') +``` + +#### + +Use the left-mounted P1000 Single-Channel GEN2 pipette to transfer 200 uL of reagent from wells A7, A6, A5, A2, A3 +of the source labware to the corresponding wells A5, A9, A1, A10, A2 of the destination labware. Use a new tip for each transfer. + +```python +TRANSFER_VOL_1 = 200 +SRC_WELL_1 = [source.wells_by_name()[well] for well in ['A7', 'A6', 'A5', 'A2', 'A3']] +DEST_WELL_1 = [destination.wells_by_name()[well] for well in ['A5', 'A9', 'A1', 'A10', 'A2']] + +# command 1 +p1000s_1.transfer(TRANSFER_VOL_1, SRC_WELL_1, DEST_WELL_1, new_tip="always") +``` + +#### + +Use the right-mounted P1000 Single-Channel GEN2 pipette to transfer 18 uL of liquid from wells A9, A12, A6, A10, A3 +of the source labware to the corresponding wells A7, A11, A6, A3, A9 of the destination labware. Use the same tip for all transfers. + +```python +TRANSFER_VOL_2 = 18 +SRC_WELL_2 = [source.wells_by_name()[well] for well in ['A9', 'A12', 'A6', 'A10', 'A3']] +DEST_WELL_2 = [source.wells_by_name()[well] for well in ['A7', 'A11', 'A6', 'A3', 'A9']] + +# command 2 +p1000s_2.transfer(TRANSFER_VOL_2, SRC_WELL_2, DEST_WELL_2, new_tip="once") +``` + +#### + +Using P300 Single-Channel GEN2 pipette on the left mount, transfer 119 uL of reagent +from first well in source labware to E12, G12, B9, A6, D7 wells in the destination labware. +Use a new tip for each transfer. + +```python +vol = 119 +src_well = source.wells_by_name()['A1'] +dest_wells = [destination.wells_by_name()[well] for well in ['E12', 'G12', 'B9', 'A6', 'D7']] + +# commands +p300s.transfer(vol, src_well, dest_wells, new_tip="always") +``` + +#### + +Using P20 Single Channel, transfer 13ul of reagent from the first tube of the source rack to each well in the destination plate. +Use the same tip for each transfer. + +```python +# parameters +vol = 13 +src_well = source.wells_by_name()['A1'] +dest_wells = destination.wells() + +# commands +p20s.transfer(vol, src_well, dest_wells, new_tip='once') +``` + +#### + +Using P20 Single Channel GEN2 pipette on right mount, transfer 16 uL from the first well of source labware 1 to each well +in destination labware 1 and destination labware 2. Reuse the same tip + +```python +# volumes setup +transfer_vol_1 = 16 + +# wells setup +source_wells_1 = source_1.wells_by_name()['A1'] +destination_wells_1 = destination_1.wells() +destination_wells_2 = destination_2.wells() +all_destinations = destination_wells_1 + destination_wells_2 + +# commands +p20s.transfer(transfer_vol_1, source_wells_1, all_destinations, new_tip="once") +``` + +#### + +Using P20 Single Channel GEN2 pipette on right mount, transfer 23 uL from each well in source labware 2 to +each well in the destination labware 1. Reuse the same tip. + +```python +# volumes setup +transfer_vol_2 = 23 + +# wells setup +source_wells_2 = source_2.wells() +destination_wells_1 = destination_1.wells() + +# commands +p20s.transfer(transfer_vol_2, source_wells_2, destination_wells_1, new_tip="once") +``` + +#### + +Using P20 Multi-Channel GEN2 pipette on the right mount, transfer 5 uL of reagent +from first column in source labware to columns 5, 9, 1, 10, and 2 in the destination labware. +Use the same tip everytime. + +```python +# parameters +vol = 5 +src_col = source.columns_by_name()['1'] +dest_cols = [destination.columns_by_name()[idx] for idx in ['5', '9', '1', '10', '2']] + +# commands +p20m.transfer(vol, src_col, dest_cols, new_tip="once") +``` + +#### + +Using P20 Multi-Channel GEN2 pipette on the left mount, transfer 24 uL of reagent +from columns 4, 3, 6, 1, 11 in source labware to columns 5, 9, 1, 10, 2 in the same source labware. +Use a new tip everytime. + +```python +# parameters +vol = 24 +src = [source.columns_by_name()[idx] for idx in ['4', '3', '6', '1', '11']] +dest = [source.columns_by_name()[idx] for idx in ['5', '9', '1', '10', '2']] + +# commands +p20m.transfer(vol, src, dest, new_tip="always") +``` + +#### + +Using P300 Multi Channel, transfer 55 uL of sample from each column of the source plate +into the corresponding columns of the destination deep well plate. +Change tips for each transfer. + +```python +# parameters +vol = 55 +src_cols = source.columns() +dest_cols = destination.columns() + +# commands +p300m.transfer(vol, src_cols, dest_cols, new_tip='always') +``` + +#### + +Using P300 Single Channel GEN2, transfer 70ul of reagent from the first tube of the source rack to each well in the destination plate. +Keep the same tip for each transfer. + +```python +# parameters +vol = 70 +src_well = source.wells_by_name()['A1'] +dest_wells = destination.wells() + +# commands +p300s.transfer(vol, src_well, dest_wells, new_tip='once') +``` + +#### + +Using P300 Single Channel GEN2, transfer 75ul of samples from each tube in the source tube rack to each well of the destination plate. +Use a new tip for each transfer. + +```python +# parameters +vol = 75 +src_wells = source.wells() +dest_wells = destination.wells() + +# commands +p300s.transfer(vol, src_wells, dest_wells, new_tip='always') +``` + +#### + +Using P300 Multi-channel pipette on the left mount, transfer 65 uL of reagent from first column in the source labware 1 +to all the columns in destination labware 1. Keep the same set of tips for this entire set of transfers within this step. + +```python +transfer_vol_1 = 65 + +# wells setup +source_wells_1 = source_1.columns_by_name()['1'] +destination_wells_1 = destination_1.columns() + +p300m.transfer(transfer_vol_1, source_wells_1, destination_wells_1, new_tip="once") +``` + +#### + +Using P1000 Single-Channel GEN2 pipette on left mount, transfer 175.0 uL of reagent +from H10, F12, D7, B1, C8 wells in source labware +to first well in the destination labware. Use a new tip for each transfer. + +```python +# parameters +transfer_vol = 175.0 +src_wells = [source.wells_by_name()[well] for well in ['H10', 'F12', 'D7', 'B1', 'C8']] +dest_well = destination.wells_by_name()['A1'] + +# commands +p1000s.transfer(transfer_vol, src_wells, dest_well, new_tip="always") +``` + +#### + +Using P300 Single-channel GEN2 pipette on left mount, transfer 51 uL from wells A1, A2 in source labware 1 +to B6, B7 in source labware 2. Reuse the same tip. + +```python +# volume setup +transfer_vol_1 = 51 + +# well setup +source_wells_1 = [source_1.wells_by_name()[wells] for wells in ['A1', 'A2']] +destination_wells_1 = [source_2.wells_by_name()[wells] for wells in ['B6', 'B7']] + +# commands +p300s.transfer(transfer_vol_1, source_wells_1, destination_wells_1, new_tip="once") +``` + +#### + +Using P20 Single-channel GEN2 pipetet on right mount, transfer 14 uL from wells C4, C6 in source labware 2 +to A3, A4 in source labware 1. Reuse the same tip. + +```python +# volume setup +transfer_vol_2 = 14 + +# well setup +source_wells_2 = [source_2.wells_by_name()[wells] for wells in ['C4', 'C6']] +destination_wells_2 = [source_1.wells_by_name()[wells] for wells in ['A3', 'A4']] + +# commands +p20s.transfer(transfer_vol_2, source_wells_2, destination_wells_2, new_tip="once") +``` + +#### + +Using P20 Single-channel GEN2 pipette on right mount, transfer 17 uL from wells B6, B7 in source labware 2 +to A1, B1 in destination labware 1. Use a new tip each time. + +```python +# volume setup +transfer_vol = 17 +# well setup +source_wells_2 = [source_2.wells_by_name()[wells] for wells in ['B6', 'B7']] +destination_wells_1 = [destination_1.wells_by_name()[wells] for wells in ['A1', 'B1']] +# commands +p20s.transfer(transfer_vol, source_wells_2, destination_wells_1, new_tip="always") +``` + +#### + +Using P20 Single-channel GEN2 pipette on right mount, transfer 15 uL from wells C4, C6 in source labware 2 +to A1, B1 in destination labware 2. Use a new tip each time. + +```python +# volume setup +transfer_vol = 15 + +# well setup +source_wells_2 = [source_2.wells_by_name()[wells] for wells in ['C4', 'C6']] +destination_wells_2 = [destination_2.wells_by_name()[wells] for wells in ['A1', 'B1']] + +# commands +p20s.transfer(transfer_vol, source_wells_2, destination_wells_2, new_tip="always") +``` + +#### + +Using the P300 Single-Channel GEN2, pool [transfer_vol]ul from all tubes in source labware into A1 of the destination labware. +Change tips between each tube. + +```python +# well setup +source_wells = source.wells() +destination_wells = [destination.wells_by_name()[wells] for wells in ['A1']] +# Transfer samples +p300_single.transfer(transfer_vol, source_wells, destination_wells, new_tip='always') +``` + +#### + +Using P300 single-channel GEN2 pipette, pool 95 uL of liquid from all the wells in source labware 1 to +the first well in destination labware 1. Use the same tip throughout. + +```python +# volume setup +transfer_vol_1 = 95 +# wells setup +source_wells_1 = source_1.wells() +destination_wells_1 = destination_1.wells_by_name()['A1'] + +# commands +p300s.transfer(transfer_vol_1, source_wells_1, destination_wells_1, new_tip="once") +``` + +#### + +Using the P20 Multi-Channel GEN2 pipette on the right mount, transfer 3 uL of reagent from the first column in the source labware +to columns 5, 9, 1, 10, 2 in the destination labware. Use a new set of tips for each transfer. + +```python +# parameters +transfer_vol = 3 +src_col = source.columns_by_name()['1'] +dest_cols = [destination.columns_by_name()[idx] for idx in ['5', '9', '1', '10', '2']] + +# commands +p20m.transfer(transfer_vol, src_col, dest_cols, new_tip='always') +``` + +#### + +Using the P20 Multi-Channel GEN2 pipette on the right mount, transfer 8 uL of reagent from source columns 4, 3, 6, 1, +and 11 to columns 5, 9, 1, 10, and 2 in the destination labware. Use the same set of tips for all transfers. + +```python +# parameters +transfer_vol = 8 +src_cols = [source.columns_by_name()[idx] for idx in ['4', '3', '6', '1', '11']] +dest_cols = [destination.columns_by_name()[idx] for idx in ['5', '9', '1', '10', '2']] + +# commands +p20m.transfer(transfer_vol, src_cols, dest_cols, new_tip="once") +``` + +#### + +Using P300 Multi-Channel GEN2 pipette on the left mount, transfer 38 uL of reagent from 4, 3, 6, 1, 11 +columns in the source labware to 5, 9, 1, 10, 2 columns in the destination labware. Use a new tip for each transfer. + +```python +# parameters +transfer_vol = 38 +src_cols = [source.columns_by_name()[idx] for idx in ['4', '3', '6', '1', '11']] +dest_cols = [destination.columns_by_name()[idx] for idx in ['5', '9', '1', '10', '2']] + +# commands +p300m.transfer(transfer_vol, src_cols, dest_cols, new_tip="always") +``` + +#### + +Using P20 Single GEN2 pipette on the right mount, transfer 10 uL of reagent +from the first well of source labware 2 to all the wells in the destination labware. Reuse the same tip. + +```python +# volumes setup +transfer_vol_1 = 10 +# wells setup +source_wells_2 = source_labware_2.wells_by_name()['A1'] +destination_wells_1 = [dest.wells() for dest in destination_list] # a list of destinations +# commands +p20s.transfer(transfer_vol_1, source_wells_2, destination_wells_1, new_tip="once") +``` + +#### + +Using P300 Single GEN2 on the left mount, perform a well to well transfer of 90 uL from source +labware to the destination labware. Use a new tip each time. + +```python +# volumes setup +transfer_vol +# wells setup +source_wells = [src.wells() for src in source_labware] +destination_wells = [dest.wells() for dest in destination_list] # a list of destinations +# commands +p300s.transfer([transfer_vol], source_wells, destination_wells, new_tip="always") +``` + +#### + +Using Flex 1-Channel 1000 uL Pipette on left mount, +transfer 186.0 uL of reagent from A7, A6, A5, A2, A3 of the source labware to A5, A9, A1, A10, A2 the destination labware. +Use a new tip for all transfers. + +```python +# parameters +TRANSFER_VOL = 186.0 +SRC_WELLS = [source.wells_by_name()[well] for well in ['A7', 'A6', 'A5', 'A2', 'A3']] +DEST_WELLS = [destination.wells_by_name()[well] for well in ['A5', 'A9', 'A1', 'A10', 'A2']] + +# command 1 +p1000s_1.transfer(TRANSFER_VOL, SRC_WELLS, DEST_WELLS, new_tip="always") +``` + +#### + +Use Flex 1-Channel 1000 uL Pipette on right mount, +transfer 10 uL of liquid from A9, A12, A6, A10, A3 of source labware to A7, A11, A6, A3, A9 of the destination labware. +Use the same tip for all transfers. + +```python +# parameters +TRANSFER_VOL = 10 +# well setup +SRC_WELLS = [source.wells_by_name()[well] for well in ['A9', 'A12', 'A6', 'A10', 'A3']] + = [destination.wells_by_name()[well] for well in ['A7', 'A11', 'A6', 'A3', 'A9']] + +# command 1 +[pipette object].transfer(TRANSFER_VOL, SRC_WELLS, DEST_WELLS, new_tip="once") +``` + +#### + +Using Flex 1-Channel 1000 uL Pipette on left mount, transfer 127.0 uL of reagent from the first well in source labware +to E12, G12, B9, A6, D7 wells in the destination labware. Use a new tip for each transfer. + +```python +# parameters +transfer_vol = 127.0 +src_well = source.wells_by_name()['A1'] +dest_wells = [destination[well] for well in ['E12', 'G12', 'B9', 'A6', 'D7']] + +# commands +[pipette object].transfer(transfer_vol, src_well, dest_wells, new_tip="always") +``` + +#### + +Using Flex 1-Channel 50 uL Pipette, transfer 2ul of reagent from the first tube of the source rack to each well in the destination plate. +Use the same tip for each transfer. + +```python +# parameters +transfer_vol = 2 +src_well = source.wells_by_name()['A1'] +dest_wells = destination.wells() + +# commands +p50s.transfer(transfer_vol, src_well, dest_wells, new_tip='once') +``` + +#### + +Using the Flex 1-Channel 50 uL Pipette, transfer 25 uL from the first well of source labware 1 to each well +in destination labware 1 and destination labware 2. Use the same tip for each transfer. + +```python +# volumes setup +transfer_vol_1 = 25 + +# wells setup +source_wells_1 = source_1.wells_by_name()['A1'] +destination_wells_1 = destination_1.wells() +destination_wells_2 = destination_2.wells() +all_dest = destination_wells_1+destination_wells_2 + +# commands +p50s.transfer(transfer_vol_1, source_wells_1, all_dest, new_tip="once") +``` + +#### + +Using Flex 8-Channel 50 uL Pipette on right mount, transfer 5 uL of reagent from the first column in source labware +to columns 4, 8, 1, 9, and 2 in the destination labware. Use the same tip for all transfers. + +```python +# parameters +transfer_vol = 5 +src_col = source.columns_by_name()['1'] +dest_cols = [destination.columns_by_name()[idx] for idx in ['4', '8', '1', '9', '2']] + +# commands +p50m.transfer(transfer_vol, src_col, dest_cols, new_tip="once") +``` + +#### + +Using Flex 8-Channel 50 uL Pipette on left mount, transfer 24.0 uL of reagent from columns 3, 2, 5, 1, 10 +to columns 4, 8, 1, 9, 2 in the same source labware. Use a new tip for each transfer. + +```python +#parameters +transfer_vol = 24.0 +src_cols = [source.columns_by_name()[idx] for idx in ['3', '2', '5', '1', '10']] +dest_cols = [source.columns_by_name()[idx] for idx in ['4', '8', '1', '9', '2']] + +# commands +p50m.transfer(transfer_vol, src_cols, dest_cols, new_tip="always") +``` + +#### + +Using Flex 8-Channel 1000 uL Pipette , transfer 70ul of sample from each well of the first column of the source plate into the first column of +the destination plate. Use a new tip for each transfer. + +```python +# parameters +transfer_vol = 70 +src_col = source.columns_by_name()['1'] +dest_col = destination.columns_by_name()['1'] + +# commands +p1000m.transfer(transfer_vol, src_col, dest_col, new_tip='always') +``` + +#### + +Transfer 80ul of reagent from the first tube of the source rack to each well in the destination plate. +Use the same tip for each transfer. + +```python +# parameters +transfer_vol = 80 +src_well = source.wells_by_name()['A1'] +dest_wells = destination.wells() + +# commands +p1000s.transfer(transfer_vol, src_well, dest_wells, new_tip='once') +``` + +#### + +Using Flex 1-Channel 1000 uL Pipette, aliquot 190 ul of samples from each tube in the source tube rack to +all wells of the destination plate evenly. Use a new tip for each transfer. + +```python +# parameters +transfer_vol = 190 +src_wells = source.wells() +dest_wells = destination.wells() + +# commands +p1000s.transfer(transfer_vol, src_wells, dest_wells, new_tip='always') +``` + +#### + +Using Flex 8-Channel 1000 uL Pipette on left mount, transfer 40 uL from the first column in the source labware 1 +to the first column in destination labware 1. Keep the same tip for this entire set of transfers within this step. + +```python +# volumes setup +transfer_vol_1 = 40 +# wells setup +source_wells_1 = source_1.columns_by_name()['1'] +destination_wells_1 = destination_1.columns_by_name()['1'] +p1000m.transfer(transfer_vol_1, source_wells_1, destination_wells_1, new_tip="once") +``` + +#### + +Using Flex 1-Channel 1000 uL Pipette on left mount, transfer 197 uL of reagent +from H10, F12, D7, B1, C8 wells in source labware to the first well in the destination labware. +Use a new tip for each transfer. + +```python +# parameters +transfer_vol = 197 +src_wells = [source.wells_by_name()[well] for well in ['H10', 'F12', 'D7', 'B1', 'C8']] +dest_well = destination.wells_by_name()['A1'] + +# commands +p1000s.transfer(transfer_vol, src_wells, dest_well, new_tip="always") +``` + +#### + +Using Flex 1-Channel 1000 uL Pipette on left mount, transfer 52 uL from wells A1, A2 in source labware 1 +to B6, B7 in source labware 2. Reuse the same tip for each transfer. + +```python +# volume setup +transfer_vol_1 = 52 + +# well setup +source_wells_1 = [source_1.wells_by_name()[wells] for wells in ['A1', 'A2']] +destination_wells_1 = [source_2.wells_by_name()[wells] for wells in ['B6', 'B7']] + +# commands +p1000s.transfer(transfer_vol_1, source_wells_1, destination_wells_1, new_tip="once") +``` + +#### + +Using Flex 1-Channel 50 uL Pipette on right mount, transfer 20 uL from wells B6, B7 in source labware 2 +to A1, B1 in destination labware 1. Use a new tip for each transfer. + +```python +# volume setup +transfer_vol_3 = 20 + +# well setup +source_wells_3 = [source_2.wells_by_name()[wells] for wells in ['B6', 'B7']] +destination_wells_3 = [destination_1.wells_by_name()[wells] for wells in ['A1', 'B1']] + +# commands +p50s.transfer(transfer_vol_3, source_wells_3, destination_wells_3, new_tip="always") +``` + +#### + +Using Flex 1-Channel 1000 uL Pipette , pool 25ul from all tubes in source labware1 into A1 of the destination labware. +Change tips between each tube. + +```python +vol = 25 +source_wells = source_labware1.wells() +dest_well = destination_labware.wells_by_name()['A1'] + +p1000s.transfer(vol, source_wells, dest_well, new_tip='always') +``` + +#### + +Using Flex 1-Channel 1000 uL Pipette, pool 90 uL of liquid from all the wells in source labware 1 to +the first well in destination labware 1. Reuse the same tip. + +```python +# volume setup +transfer_vol_1 = 90 +# wells setup +source_wells_1 = source_1.wells() +destination_wells_1 = destination_1.wells_by_name()['A1'] +# commands +p1000s.transfer(transfer_vol_1, source_wells_1, destination_wells_1, new_tip="once") +``` + +#### + +Using Flex 8-Channel 50 uL Pipette on right mount, transfer 7 uL of reagent from the first column in source labware to +4, 8, 1, 9, and 2 columns in the destination labware. Use a new tip for each transfer. + +```python +#parameters +transfer_vol = 7 +src_col = source.columns_by_name()['1'] +dest_cols = [destination.columns_by_name()[idx] for idx in ['4', '8', '1', '9', '2']] + +# commands +p50m.transfer(transfer_vol, src_col, dest_cols, new_tip="always") +``` + +#### + +Using Flex 8-Channel 50 uL Pipette on right mount, transfer 6 uL of reagent from 4, 3, 6, 1, 11 columns in source labware +to 5, 9, 1, 10, 2 columns in the destination labware. Using the same tip for all transfers. + +```python +# parameters +transfer_vol = 6 +src_cols = [source.columns_by_name()[idx] for idx in ['4', '3', '6', '1', '11']] +dest_cols = [destination.columns_by_name()[idx] for idx in ['5', '9', '1', '10', '2']] + +# commands +p50m.transfer(transfer_vol, src_cols, dest_cols, new_tip="once") +``` + +#### + +Using Flex 8-Channel 1000 uL Pipette on left mount, transfer 78 uL of reagent from 4, 3, 6, 1, 11 columns in the source labware +to 5, 9, 1, 10, 2 columns in the destination labware. Use a new tip for each transfer. + +```python +# parameters +transfer_vol = 78 +src_cols = [source.columns_by_name()[idx] for idx in ['4', '3', '6', '1', '11']] +dest_cols = [destination.columns_by_name()[idx] for idx in ['5', '9', '1', '10', '2']] + +# commands +p1000m.transfer(transfer_vol, src_cols, dest_cols, new_tip="always") +``` + +#### + +Using Flex 1-Channel 50 uL Pipette on right mount, transfer 25 uL of reagent +from the first well of source labware 2 to all wells in destination labware. Reuse the same tip. + +```python +# volumes setup +transfer_vol_1 = 25 + +# wells setup +source_wells_2 = source_labware_2.wells_by_name()['A1'] +destination_wells_1 = [dest.wells() for dest in destination] + +# commands +p50s.transfer(transfer_vol_1, source_wells_2, destination_wells_1, new_tip="once") +``` + +#### + +- when command says 'Use a new tip for each transfer', or something similar, + set the `new_tip` parameter to "always": `new_tip='always'`. +- when command says 'Use the same tip for all transfers.', 'reuse the same tip' or something similar. + set the `new_tip` parameter to "once": `new_tip='once'`. + +#### + +Note that when command says `Use the same tip for all transfers` or similar. +Do not use new_tip='once' inside loop as shown below + +```python +for src, dest in LIST: + p50_multi_right.transfer(transfer_vol, src, dest, new_tip='once') +``` + +Instead, remove `for` and use like so: + +```python +p50_multi_right.transfer(transfer_vol, src, dest, new_tip='once') +``` + +Note that no `for` loop is used. + +#### + +Source labware is ['labware name'], placed on [temperature module] on slot 3 + +```python +# modules +temperature_module = protocol.load_module(['temperature module gen2'], 3) + +# labware +source = temperature_module.load_labware(['labware name']) +``` + +#### + +Thermocycler module GEN 2 is present on slot A1+B1. `A1+B1` referes to 7, please use the slot number 7. + +Correct thermocycler load: + +```python +thermocycler = protocol.load_module('thermocyclerModuleV2') # by default slot number is 7 +``` + +Incorrect thermocycler load: + +```python +thermocycler = protocol.load_module('thermocyclerModuleV2', 'A1+B1') +``` + +#### + +- Sample temperature module GEN 2 is placed on slot D1 +- Opentrons 96 Well Aluminum Block adapter is placed on sample temperature module GEN 2 + +Corresponding protocol + +```python +temp_mod_sample = protocol.load_module('temperature module gen2', 'D1') +temp_sample_adapter = temp_mod_sample.load_adapter('opentrons_96_well_aluminum_block') +``` + +#### + +Open thermocycler lid + +```python +[thermocycler_object].open_lid() +``` + +#### + +Set the thermocycler block temperature to 1 C. + +```python +plate_temperature_c = 1 +[thermocycler_object].set_block_temperature(plate_temperature_c) +``` + +#### + +Set the thermocycler lid temperature to 50 C. + +```python +lid_temperature_c = 50 +[thermocycler_object].set_lid_temperature(lid_temperature_c) +``` + +#### + +Set the sample temperature module to 3 C. + +```python +sample_temperature_c = 3 +[temperature_module].set_temperature(sample_temperature_c) +``` + +#### + +Transfer 17 uL of mastermix from the mastermix source wells to the destination wells. +Use the same pipette tip for all transfers. + +```python +[pippette_object].transfer( + 17, + master_mix_source_wells, + master_mix_destination_wells, + new_tip='once' +) +``` + +#### + +Transfer 4 uL of the sample from the source to the destination. +Mix the sample and mastermix for a total volume of 15 uL 10 times. +Blow out to 'destination well' after each transfer. Use a new tip for each transfer. + +```python +[pippette_object].transfer( + 4, + [sample_source_wells],` + [sample_destination_wells], + new_tip='always', + mix_after=(10, 15), + blow_out=True, + blowout_location='destination well' +) +``` + +#### + +Close the thermocycler lid. + +```python +[thermocycler_module].close_lid() +``` + +#### + +Execute the thermocycler with the following profile: + +- 75 C for 66 seconds for 1 cycle (repetition). + +```python +[thermocycler_module].execute_profile( + steps=[{'temperature': 75, 'hold_time_seconds': 66}], + repetitions=1, + block_max_volume=[total_mix_volume_ul] +) +``` + +Note that you must calculate `block_max_volume` based on the whole prompt context. + +#### + +Execute the thermocycler with the following profile: + +- 61C for 8 seconds, 85°C for 20 seconds, 58°C for 45 seconds for 14 cycles. + +```python +[thermocycler_module].execute_profile( + steps=[ + {'temperature': temp, 'hold_time_seconds': duration} + for temp, duration in zip([61, 85, 58], [8, 20, 45]) + ], + repetitions=14, + block_max_volume=[total_mix_volume_ul] +) +``` + +Note that you must calculate `block_max_volume` based on the whole prompt context. + +#### + +Hold the thermocycler block at 4°C. + +```python +hold_temperature_c = 10 +[thermocycler_module].set_block_temperature(hold_temperature_c) +``` + +#### + +Deactivate the mastermix temperature module. + +```python +[master_mix_temperature_module].deactivate() +``` + +#### + +Sample source wells: the first 48 wells column-wise in the sample source plate. +Note that the pipette is a single channel. + +Use `[source_labware].wells()`. For example, + +```python +number_of_samples = 48 +source_wells = sample_plate.wells()[:number_of_samples] +``` + +#### + +Sample source wells: the first 48 wells column-wise in the sample source plate. +Note that the pipette is a multi-channel. + +- Estimate the columns using the number samples + +```python +number_of_samples = 48 +number_of_columns = math.ceil(number_of_samples / 8) +``` + +- Then, use `[source_labware].columns()` method to access the columns. + For example, + +```python +source_wells = sample_plate.columns()[:number_of_columns] +``` + +#### + +When a command says `move destination labware` or something, use `move_labware`. +We need to specify two arguments: + +- labware: The labware object you want to move. +- new_location: The destination where you want to move the labware. + +This can be any empty deck slot or a module that is ready to accept labware. +Example for the slot, + +```python +protocol.move_labware([labware]], ['C4'], use_gripper=True) +``` + +Example for the module, + +```python +protocol.move_labware([labware]], [thermocycler], use_gripper=True) +``` + +#### + +Pause the protocol + +```python +protocol.pause("Pause please") +``` + +#### + +Transfer 21 uL of liquid from 6 mm below the top surface of mastermix well to 3 mm above the bottom of destination well. +Use the same tip for each transfer. + +```python +[pipette_object].transfer(21, mastermix_well.top(-6), dest.bottom(3), new_tip='once') +``` + +#### + +5 mm above the top of the well + +```python +plate['A1'].top(z=5) +``` + +5 mm below the top of the well + +```python +plate['A1'].top(z=-5) +``` + +5 mm above the bottom of the well + +```python +plate['A1'].bottom(z=1) +``` + +5 mm below the bottom of the well + +```python +plate['A1'].bottom(z=-5) +``` + +Transfer 20 uL of liquid from 5 mm below the top surface of the mastermix well to 2 mm above the bottom of the destination well. +Use the same tip for each transfer. + +```python +pipette_96channel.transfer(20, mastermix_source_well.top(-5), destination_wells.bottom(2), new_tip='once') +``` + +#### + +Remove the tip slowly out of the well at 5 mm/s speed + +```python +pipette.move_to([well].top(), speed=5) +``` + +Move to the top of the well at 5 mm/s speed + +```python +pipette.move_to([well].top(), speed=5) +``` + +Move to 2 mm below the top of well A1 + +```python +pipette.move_to(plate['A1'].top(z=-2)) +``` + +Move to 2 mm above the bottom of well A1 + +```python +pipette.move_to(plate['A1'].bottom(z=2)) +``` + +#### + +Transfer 20 ul of liquid from 3 mm above the source well bottom to destination well 7 mm beneath the top surface. Flow rate is at half the default. +Mix the sample and mastermix of 40 ul total volume 5 times. Remove the tip slowly out of the well at 5 mm/s speed. Use the same tip for each transfer. + +```python +pipette_96channel.pick_up_tip() +pipette_96channel.aspirate(20, sample_source_wells.bottom(3), rate=0.5) +pipette_96channel.dispense(20, destination_wells.top(-7), rate=0.5) +pipette_96channel.mix(5, 40) +pipette_96channel.move_to(destination_wells.top(), speed=5) +pipette_96channel.drop_tip() +``` + +#### + +Load three opentrons_flex_96_filtertiprack_50ul tip racks in slots A2, B2, and C2 + +```python +tips_50ul = [ + protocol.load_labware( + 'opentrons_flex_96_filtertiprack_50ul', + slot + ) + for slot in ['A2', 'B2', 'C2'] +] +``` + +or + +```python +tips_50ul_a = protocol.load_labware('opentrons_flex_96_filtertiprack_50ul', 'A2') +tips_50ul_b = protocol.load_labware('opentrons_flex_96_filtertiprack_50ul', 'B2') +tips_50ul_c = protocol.load_labware('opentrons_flex_96_filtertiprack_50ul', 'C2') +tips_50ul = [tips_50ul_a, tips_50ul_b, tips_50ul_c] +``` + +#### + +Move the destination labware to the thermocycler using a gripper. + +```python +protocol.move_labware(destination_plate, thermocycler_module, use_gripper=True) +``` + +#### + +I am going to be running a protocol on my Opentrons Flex. +I have a 96-channel pipette on the system. My destination plates will be +4 'nest_96_wellplate_2ml_deep' plates. My source labware will be +a 'nest_1_reservoir_195ml'. + +```python +pipette_96_channel = protocol.load_instrument( + 'flex_96channel_1000', mount='left' + ) +source_reservoir = protocol.load_labware('nest_1_reservoir_195ml', '1') +destination_plates = [ + protocol.load_labware('nest_96_wellplate_2ml_deep', slot) + for slot in ['2', '3', '4', '5'] +] +``` + +#### Example 5 + +Transfer 25 uL from multiple source wells to a single destination well, use a new tip every time, and touch the tip after dispense. + +```python +pipette.transfer(25, source_wells, dest_well, new_tip='always', touch_tip=True) +``` + +#### + +Transfer 10 uL from source to destination, with an air gap of 5 uL after aspiration. + +```python +pipette.transfer(10, source_well, dest_well, air_gap=5) +``` + +#### + +Transfer 200 uL from source to destination, blowing out in the source well after dispensing. Use the same tip for each transfer. + +```python +pipette.transfer(200, source_well, dest_well, trash=False, blow_out=True, blowout_location='source well') +``` + +#### + +Transfer 12 uL from source to destination, mix the destination well 5 times with 10 uL after dispensing, and do not touch the tip. + +```python +pipette.transfer(12, source_well, dest_well, mix_after=(5, 10)) +``` + +#### + +Transfer 30 uL from one source to multiple destinations, after each aspirate and touch tip after dispensing. + +```python +pipette.transfer(30, source_well, dest_wells, air_gap=10, touch_tip=True) +``` + +#### + +Flex 1-Channel 1000 uL Pipette is mounted on the left side. +mastermix source wells: first N wells column-wise in mastermix plate. +Note that the pipette is a single channel. + +```python +pipette = protocol.load_instrument('flex_1channel_1000', 'left', tip_racks=[tips_1000ul]) +sample_source_wells = sample_plate.wells()[:N] +``` + +#### + +Source Labware: `Opentrons 96 Flat Bottom Heater-Shaker Adapter with NEST 96 Well Plate 200 uL Flat` in slot D1 + +```python +source = protocol.load_labware('opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat', 'D1') +``` + +#### + +Using Flex 1-Channel 1000 uL Pipette on left mount, transfer 150 uL from wells `A1, A2` in source labware 1 +to `B6, B7` in source labware 2. Use the same tip for each transfer. + +First collect all wells for source and destination. + +```python +source_wells_1 = [source_1.wells_by_name()[wells] for wells in ['A1', 'A2']] +destination_wells_1 = [source_2.wells_by_name()[wells] for wells in ['B6', 'B7']] +``` + +Then use a transfer method like so: + +```python +p1000s.transfer(150, source_wells_1, destination_wells_1, new_tip="once") +``` + +Note that we are using a single transfer function for multiple wells. + +The following is totally wrong: + +```python +pipette_1000ul.transfer(50, source_labware_1.wells_by_name()['A1'], source_labware_2.wells_by_name()['B6'], new_tip='once') +pipette_1000ul.transfer(50, source_labware_1.wells_by_name()['A2'], source_labware_2.wells_by_name()['B7'], new_tip='never') +``` + +#### + +Using the multi-channel pipette, transfer 3ul of sample from each column in the source plate to +the destination plate in duplicate. Changing tips between each column. Duplicate means that +aspirate the sample from the sample plate column 1 to the destination plate column 1, change tip, +then aspirate from sample plate column 1 to destination plate column 2. Then, transfer the sample +from the sample plate column 2 to the destination plate column 3, change tip, then transfer +the sample from sample plate column 2 to destination plate column 4. Repeat this pattern for +the remainder of the source columns. + +```python +source_columns = source_plate.columns()[:number_of_columns] +destination_columns = destination_plate.columns()[:number_of_columns * 2] # Twice the number for duplicates + +for col_ctr, s in enumerate(source_columns, start=0): + dest_index = 2 * col_ctr + pipette_multi.transfer(3, s, destination_columns[dest_index], new_tip='always') + pipette_multi.transfer(3, s, destination_columns[dest_index + 1], new_tip='always') +``` + +Note that two transfer methods is used to account for duplication. 'for' loop is used since description +says change tip for each column. + +#### + +Using the multi-channel pipette, transfer 3ul of sample from each column in the source plate to +the destination plate in triplicate. Changing tips between each column. +The triplicate means that for first source columns, +aspirate the sample from the source column 1 to the destination plate column 1, change tip, +then aspirate from source column 1 to destination plate column 2, change tip, +then aspirate from source column 1 to destination plate column 3, change tip. +For second source column, +aspirate the sample from the source column 2 to the destination column 4, change tip, +then aspirate the sample from source column 2 to destination column 5, change tip, +then aspirate the sample from source column 2 to destination column 6, change tip. + +Repeat this pattern for the remainder of the source columns. + +```python +source_columns = source_plate.columns()[:number_of_columns] +destination_columns = destination_plate.columns()[:number_of_columns * 2] # Twice the number for duplicates + +for col_ctr, s in enumerate(source_columns, start=0): + dest_index = 2 * col_ctr + pipette_multi.transfer(3, s, destination_columns[dest_index], new_tip='always') + pipette_multi.transfer(3, s, destination_columns[dest_index + 1], new_tip='always') + pipette_multi.transfer(3, s, destination_columns[dest_index + 2], new_tip='always') +``` + +Note that two transfer methods is used to account for duplication. 'for' loop is used since description +says change tip for each column. diff --git a/opentrons-ai-server/api/storage/docs/deck_layout.md b/opentrons-ai-server/api/storage/docs/deck_layout.md new file mode 100644 index 00000000000..e2532de3712 --- /dev/null +++ b/opentrons-ai-server/api/storage/docs/deck_layout.md @@ -0,0 +1,167 @@ +# Deck Layout Rules + +## Overview + +This document collects all of the guidelines around recommended deck slot locations in one place. Previously, this information was scattered in multiple documents, or the logic was built into Opentrons products like Protocol Designer or the OT App. + +## Deck Slot Guidelines - OT-2 + +OT-2 deck slots: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, trash bin. + +### Modules + +- Heater-Shaker + + - Recommended: Slot 1 + - Allowed: Slots 3, 4, 6, 7, 1 + - Not allowed: Slots 2, 5, 8, 9, 11 + +- Magnetic Module + + - Recommended: Slot 1 + - Allowed: Slots 3, 4, 6, 7, 9, 10 + - Not allowed: Slots 2, 5, 8, 11 + +- Magnetic Block: Not compatible with OT-2 + +- Plate Reader Module (Absorbance): Not compatible with OT-2 + +- Temperature Module + + - Recommended: Slot 3 + - Allowed: Slot 1, 4, 6, 7, 9, 10 + - Not allowed: Slots 2, 5, 8, 11 + +- Thermocycler Module + + - Recommended/Allowed: Slots 7, 8, 10, and 11 (All four slots) + - Not allowed: Any other location + - Note: Only one Thermocycler module can be added to the deck. + +- Fixtures - N/A + - The OT-2 waste bin is fixed on the deck map, taking up what would have been Slot 12. + +### Labware + +Note: We should default to placing the shortest labware near the front and left of the OT-2 (Slot 1 then 2 then 3 then 4 then 5, etc.), followed by progressively taller labware towards the back and right. From shortest to tallest the order should be: Well plates, then Reservoirs, then Tube racks, then Tip racks. + +- Well plates + + - Recommended: Slots 1, 2, or 3 + - If needed: Slots 4, 5, or 6 + - Allowed: Any slot + +- Reservoirs + + - Recommended: Slots 4, 5, or 6 + - If available: Slots 1, 2, or 3 + - If needed: Slots 7, 8, or 9 + - Allowed: Any slot + +- Tube racks + + - Recommended: Slots 7, 8, or 9 + - If available: Slots 1, 4, 2, 5, 3, or 6 (Slots on the far left side are preferable to ones in the middle or left since they’re easier to access.) + - Allowed: Any slot + +- Tip racks + - Recommended: Slots 11, 10, 9, 8, 7 (Start towards the back right and move left then to the front) + - If available: Slots 6, 5, 4, 3, 2, 1 + - Allowed: Any slot + +## Deck Slot Guidelines - Flex + +Flex deck layout: D1, D2, D3, C1, C2, C3, B1, B2, B3, A1, A2, A3 (Trash bin) + +### Modules + +- Heater-Shaker + + - Recommended: Slot D1 + - Allowed: Slots A1, B1, C1, D1, A3, B3, C3, or D3 + - Not allowed: Slots A2, B2, C2, or D2 + +- Magnetic Module: Not compatible with Flex + +- Magnetic Block + + - Recommended: Slot D2 + - Allowed: Slots A1, B1, C1, D1, A2, B2, C2, D2, A3, B3, C3, or D3 + - Not allowed: On staging area slots + +- Plate Reader Module (Absorbance) + + - Recommended: D3 + - Allowed: Slots A3, B3, C3, or D + - Not allowed: Slots A1, B1, C1, D1, A2, B2, C2, or D2 + +- Temperature Module + + - Recommended: D1 + - Allowed: A1, B1, C1, D1, A3, B3, C3, or D3 + - Not allowed: A2, B2, C2, or D2 + +- Thermocycler Module + - Recommended/Allowed: A1 + B1 (Both slots) + - Not allowed: Any other location + +### Fixtures + +- Staging area slots + + - Allowed: A3, B3, C3, or D3 + - Not allowed: A1, B1, C1, D1, A2, B2, C2, or D2 + - Notes: When a staging area slot is added, a new deck slot is created in the far right column in slots A4, B4, C4, or D4. The gripper can access these deck slots, but pipetting in column 4 is not possible. + - Because the staging area slots can only be accessed by the gripper, tube racks should not be placed in these locations since the gripper cannot safely move this type of labware. All other labware types are compatible with staging area slots. + - The trash bin cannot occupy the same deck slot as a staging area slot. + +- Trash bin + + - Recommended: A3 + - Allowed: A1, B1, C1, D1, A3, B3, C3, or D3 + - Not allowed: A2, B2, C2, or D2 + - Note: The trash bin cannot occupy the same deck slot as a staging area slot. + +- Waste chute + - Recommended/Allowed: D3 (The waste chute fixture diverts waste to an off-deck receptacle and is designed to be placed exclusively in slot D3). + - Not allowed: Any other location + +### Labware + +Note: We should default to placing the shortest labware near the front and left of the Flex (Slot D1 then D2 then D3 then C1 then C2, etc.), followed by progressively taller labware towards the back and right. From shortest to tallest the order should be: Well plates, then Reservoirs, then Tube racks, then Tip racks. + +- Well plates + + - Recommended: Slots D1, D2, or D3 + - If needed: Slots C1, C2, C3, B1, B2, B3, A1, A2, or A3 + - Allowed: Any slot + +- Reservoirs + + - Recommended: Slots C1, C2, or C3 + - If available: Slots D1, D2, or D3 + - If needed: Slots B1, B2, B3, A1, A2, or A3 + - Allowed: Any slot + +- Tube racks + + - Recommended: Slots B1, B2, B3 + - If available: Slots D1, C1, D2, C2, D3, or C3 (Slots on the far left side are preferable to ones in the middle or left since they’re easier to access.) + - Allowed: Any slot + +- Tip racks + + - Recommended: Slots A3 (if trash bin is not present), A2, A1, B3, B2, B1 (Start towards the back right and move left then to the front) + - If available: Slots C3, C2, C1, D3, D2, or D1 + - Allowed: Any slot + +- Adapters + - Opentrons Flex 96 Tip Rack Adapter + - Recommended: A2, B2, C2, D2 (to avoid modules in columns 1 and 3) + - Allowed: Any slot + +## Reference documents + +1. Confluence: https://opentrons.atlassian.net/wiki/spaces/RPDO/pages/3859939364 +2. Flex product manual (See the section on Deck Fixtures in Chapter 3): https://insights.opentrons.com/hubfs/Products/Flex/Opentrons Flex manual REV2.pdf +3. OT-2 product manual: https://insights.opentrons.com/hubfs/Products/OT-2/OT-2R User Manual.pdf diff --git a/opentrons-ai-server/api/storage/docs/full-examples.md b/opentrons-ai-server/api/storage/docs/full-examples.md new file mode 100644 index 00000000000..959dd4cf4d9 --- /dev/null +++ b/opentrons-ai-server/api/storage/docs/full-examples.md @@ -0,0 +1,1263 @@ +## Below seven examples are shown in pairs: a description () and a corresponding protocol (). + +[1] Example + +Application: Reagent Filling - One source to Multiple destinations +Robot: OT-2 +API: 2.15 + +Modules: + +- No modules + +Labware: + +- Source labware: Opentrons Tough 96 Well Plate 200 uL PCR Full Skirt in slot 4 +- Destination Labware: NEST 2 mL 96-Well Deep Well Plate, V Bottom in slot 9 +- Tiprack: Opentrons OT-2 96 Tip Rack 300 uL in slot 1 + +Pipette mount: + +- P300 Multi Channel is mounted on the right + +Commands: + +1. Transfer 50 uL of sample from each column of the source plate into the corresponding columns of the destination deep well plate. + Change tips for each transfer. + + + +# metadata +metadata = { + 'protocolName': 'Reagent Transfer', + 'author': 'chatGPT', + 'description': 'Transfer reagent', + 'apiLevel': '2.15' +} + +def run(protocol): # labware +tiprack = protocol.load_labware('opentrons_96_tiprack_300ul', 1) +source = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 4) +destination = protocol.load_labware('nest_96_wellplate_2ml_deep', 9) + + # pipettes + p300m = protocol.load_instrument('p300_multi_gen2', mount="right", tip_racks=[tiprack]) + + # parameters + TRANSFER_VOL = 50 + SRC_COLS = source.columns() + DEST_COLS = destination.columns() + + # commands + p300m.transfer(TRANSFER_VOL, SRC_COLS, DEST_COLS, new_tip='always') + + + +[2] Example + +Metadata: + +- Application: Reagent transfer +- Robot: OT-2 +- API: 2.15 + +Labware: + +- Source Labware 1: NEST 1 Well Reservoir 195 mL is positioned in slot 7 +- Destination Labware 1: Corning 96 Well Plate 360 uL Flat is positioned in slot 1 +- Destination Labware 2: Corning 96 Well Plate 360 uL Flat is positioned in slot 2 +- Tiprack 1: Opentrons 96 Tip Rack 300 uL is positioned in slot 10 + +Pipette Mount: + +- Left Mount: P300 Multi-Channel GEN2 + +Commands: + +1. Using P300 Multi-channel pipette on the left mount, transfer 50 uL of reagent from first column in the source labware 1 + to all the columns in destination labware 1. Keep the same set of tips for this entire set of transfers within this step. +2. Using P300 Multi-channel pipette on the left mount, transfer 100 uL from first column in the source labware 1 + to each column in destination labware 2. Keep the same set of tips for this entire set of transfers within this step. + + + +from opentrons import protocol_api + +# metadata + +metadata = { +"protocolName": "Reagent Transfer protocol", +"author": "Opentrons Generative AI", +"description": "Transfer reagents from multile source labware to multiple destination labware", +"apiLevel": "2.15" +} + +def run(protocol: protocol_api.ProtocolContext): # labware +source_1 = protocol.load_labware("nest_1_reservoir_195ml", location=7) +destination_1 = protocol.load_labware("corning_96_wellplate_360ul_flat", location=1) +destination_2 = protocol.load_labware("corning_96_wellplate_360ul_flat", location=2) + + tiprack300 = protocol.load_labware("opentrons_96_tiprack_300ul", location=10) + + # pipettes + p300m = protocol.load_instrument("p300_multi_gen2", mount="left", tip_racks=[tiprack300]) + + # wells setup + source_wells_1 = source_1.columns()[0] + destination_wells_1 = destination_1.columns() + destination_wells_2 = destination_2.columns() + + # volumes setup + transfer_vol_1 = 50 + transfer_vol_2 = 100 + + p300m.transfer(transfer_vol_1, source_wells_1, destination_wells_1, new_tip="once") + p300m.transfer(transfer_vol_2, source_wells_1, destination_wells_2, new_tip="once") + + + +[3] Example + + +- Application: Reagent transfer +- Robot: OT-2 +- API: 2.15 + +Labware: + +- Source Labware: Thermo Scientific Nunc 96 Well Plate 2000 uL in slot 7 +- Destination Labware: Opentrons 24 Well Aluminum Block with NEST 0.5 mL Screwcap in slot 3 +- Tiprack: Opentrons 96 Filter Tip Rack 1000 uL in slot 4 + +Pipette mount: + +- P1000 Single-Channel GEN2 is mounted on the left + +Commands: + +1. Using P1000 Single-Channel GEN2 pipette on left mount, transfer 195.0 uL of reagent + from H10, F12, D7, B1, C8 wells in source labware + to first well in the destination labware. Use a new tip for each transfer. + + + +metadata = { + 'protocolName': 'Reagent Transfer', + 'author': 'chatGPT', + 'description': 'P1000 Single-Channel GEN2 transfer 195.0 ul', + 'apiLevel': '2.15' +} + +def run(protocol): + + # labware + tiprack = protocol.load_labware('opentrons_96_filtertiprack_1000ul', 4) + source = protocol.load_labware('thermoscientificnunc_96_wellplate_2000ul', 7) + destination = protocol.load_labware('opentrons_24_aluminumblock_nest_0.5ml_screwcap', 3) + + # pipettes + p1000s = protocol.load_instrument('p1000_single_gen2', mount="left", tip_racks=[tiprack]) + + # parameters + TRANSFER_VOL = 195.0 + SRC_WELLS = ['H10', 'F12', 'D7', 'B1', 'C8'] + DEST_WELL = destination.wells()[0] + + # commands + for src in SRC_WELLS: + p1000s.transfer(TRANSFER_VOL, source.wells_by_name()[src], DEST_WELL, new_tip="always") + + + +[4] Example + +Metadata and requirements: + +- Application: Reagent transfer +- Robot: Flex +- API: 2.15 + +Labware: + +- Source Labware 1: Corning 96 Well Plate 360 uL Flat is positioned in slot C1 +- Source Labware 1: Corning 96 Well Plate 360 uL Flat is positioned in slot C2 +- Destination Labware 1: Corning 96 Well Plate 360 uL Flat is positioned in slot D1 +- Destination Labware 2: Corning 96 Well Plate 360 uL Flat is positioned in slot D2 +- Tiprack 1: Opentrons Flex 96 Filter Tip Rack 200 uL is positioned in slot B2 +- Tiprack 2: Opentrons Flex 96 Filter Tip Rack 50 uL is positioned in slot A2 + +Pipette Mount: + +- Flex 1-Channel 1000 uL Pipette is mounted on the left side +- Flex 1-Channel 50 uL Pipette is mounted on the right side + +Commands: + +1. Using Flex 1-Channel 1000 uL Pipette on left mount, transfer 50 uL from wells A1, A2 in source labware 1 + to B6, B7 in source labware 2. Reuse the same tip for each transfer. +2. Using Flex 1-Channel 50 uL Pipette on right mount, transfer 15 uL from wells C4, C6 in source labware 2 + to A3, A4 in source labware 1. Reuse the same tip for each transfer. +3. Using Flex 1-Channel 50 uL Pipette on right mount, transfer 10 uL from wells B6, B7 in source labware 2 + to A1, B1 in destination labware 1. Use a new tip each time for each transfer. +4. Using Flex 1-Channel 50 uL Pipette on right mount, transfer 10 uL from wells C4, C6 in source labware 2 + to A1, B1 in destination labware 2. Use a new tip each time for each transfer. + + + +from opentrons import protocol_api + +# metadata + +metadata = { +'protocolName': 'Reagent Transfer', +'author': 'Opentrons Generative AI', +} +requirements = {"robotType": "Flex", "apiLevel": "2.15"} + +# protocol run function + +def run(protocol: protocol_api.ProtocolContext): + + # labware + source_1 = protocol.load_labware("corning_96_wellplate_360ul_flat", location='C1') + source_2 = protocol.load_labware("corning_96_wellplate_360ul_flat", location='C2') + destination_1 = protocol.load_labware("corning_96_wellplate_360ul_flat", location='D1') + destination_2 = protocol.load_labware("corning_96_wellplate_360ul_flat", location='D2') + tiprack200 = protocol.load_labware("opentrons_flex_96_filtertiprack_200ul", location='B2') + tiprack50 = protocol.load_labware("opentrons_flex_96_filtertiprack_50ul", location='A2') + + # pipettes + p1000s = protocol.load_instrument("flex_1channel_1000", mount="left", tip_racks=[tiprack200]) + p50s = protocol.load_instrument("flex_1channel_50", mount="right", tip_racks=[tiprack50]) + + # well setup + source_wells_1 = [source_1[wells] for wells in ['A1', 'A2']] + source_wells_2 = [source_2[wells] for wells in ['C4', 'C6']] + source_wells_3 = [source_2[wells] for wells in ['B6', 'B7']] + source_wells_4 = [source_2[wells] for wells in ['C4', 'C6']] + destination_wells_1 = [source_2[wells] for wells in ['B6', 'B7']] + destination_wells_2 = [source_1[wells] for wells in ['A3', 'A4']] + destination_wells_3 = [destination_1[wells] for wells in ['A1', 'B1']] + destination_wells_4 = [destination_2[wells] for wells in ['A1', 'B1']] + + # volume setup + transfer_vol_1 = 50 + transfer_vol_2 = 15 + transfer_vol_3 = 10 + transfer_vol_4 = 10 + + # commands + p1000s.transfer(transfer_vol_1, source_wells_1, destination_wells_1, new_tip="once") + p50s.transfer(transfer_vol_2, source_wells_2, destination_wells_2, new_tip="once") + p50s.transfer(transfer_vol_3, source_wells_3, destination_wells_3, new_tip="always") + p50s.transfer(transfer_vol_4, source_wells_4, destination_wells_4, new_tip="always") + + + +[5] Example + +Metadata and requirements: + +- Application: Reagent transfer +- Robot: Flex +- API: 2.15 + +Labware: + +- Source Labware: Opentrons 96 Flat Bottom Adapter with NEST 96 Well Plate 200 uL Flat on slot D1 +- Destination Labware: Opentrons 96 Flat Bottom Adapter with NEST 96 Well Plate 200 uL Flat on slot C2 +- Tiprack: Opentrons Flex 96 Filter Tip Rack 50 uL on slot C1 + +Pipette Mount: + +- Flex 8-Channel 50 uL Pipette is mounted on the right side + +Commands: + +1. Using Flex 8-Channel 50 uL Pipette on right mount, transfer 8 uL of reagent from 4, 3, 6, 1, 11 columns in source labware + to 5, 9, 1, 10, 2 columns in the destination labware. Using the same tip for all transfers. + + + +from opentrons import protocol_api + +metadata = { +'protocolName': 'Reagent Transfer', +'author': 'Opentrons Generative AI', +} + +requirements = {"robotType": "Flex", "apiLevel": "2.15"} + +def run(protocol: protocol_api.ProtocolContext): + + # labware + tiprack = protocol.load_labware('opentrons_flex_96_filtertiprack_50ul', 'C1') + source = protocol.load_labware('opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat', 'D1') + destination = protocol.load_labware('opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat', 'C2') + + # pipettes + p50m = protocol.load_instrument('flex_8channel_50', mount="right", tip_racks=[tiprack]) + + # parameters + transfer_vol = 8 + src_cols = [3, 2, 5, 0, 10] + dest_cols = [4, 8, 0, 9, 1] + + # commands + p50m.pick_up_tip() + for src_col, dest_col in zip(src_cols, dest_cols): + p50m.transfer(transfer_vol, source.columns()[src_col], destination.columns()[dest_col], new_tip="never") + p50m.drop_tip() + + + +[6] Example + +Metadata: + +- Author: Bob +- Protocol Name: PCR + +Requirements: + +- `requirements = {"robotType": "OT-2", "apiLevel": "2.15"}` + +Modules: + +- The thermocycler module is located in slot 7. +- The sample temperature module is positioned in slot 1. +- The mastermix temperature module is positioned in slot 3. + +Labware: + +- The source sample labware, an Opentrons Tough 96 Well Plate 200 uL PCR Full Skirt, is placed on the temperature module in slot 1. +- The source mastermix labware, an Opentrons Tough 96 Well Plate 200 uL PCR Full Skirt, is placed on the temperature module in slot 3. +- The destination labware, an Opentrons Tough 96 Well Plate 200 uL PCR Full Skirt, is placed on the thermocycler module in slot 7. +- A 20 uL filter tip rack is used in slot 4. + +Pipette Mount: + +- A P20 Multi-Channel Gen2 pipette is mounted on the left side. + +Well Allocation: + +- Sample source wells: the first 64 wells column-wise in the sample source plate. +- Mastermix source wells: the first 64 wells column-wise in the mastermix plate. +- Destination wells: the first 64 wells column-wise in the thermocycler. + +Commands: + +1. Set the total number of samples to 64. +2. Open the thermocycler lid. +3. Set the thermocycler block temperature to 6C. +4. Set the thermocycler lid temperature to 55C. +5. Set the sample temperature module to 4C. +6. Set the mastermix temperature module to 10C. +7. Transfer 7 uL of mastermix from the mastermix source wells to the destination wells. Use the same pipette tip for all transfers. +8. Transfer 5 uL of the sample from the source to the destination. Mix the sample and mastermix for a total volume of 12 uL 9 times. + Blow out to `destination well` after each transfer. Use a new tip for each transfer. +9. Close the thermocycler lid. +10. Execute the thermocycler with the following profile: + - 74C for 65 seconds for 1 cycle, block max volume is sample and mastermix volume +11. Execute the thermocycler with the following profile: + - 60C for 7 seconds, 84C for 19 seconds, 57C for 44 seconds for 13 cycles, block max volume is sample and mastermix volume +12. Execute the thermocycler with the following profile: + - 75C for 480 seconds for 1 cycle, block max volume is sample and mastermix volume +13. Hold the thermocycler block at 4C. +14. Open the thermocycler lid. +15. Deactivate the mastermix temperature module. +16. Deactivate the sample temperature module. + + + +import math +from opentrons import protocol_api + +metadata = { +'protocol_name': 'QIAGEN OneStep RT-PCR Kit PCR Amplification', +'author': 'chatGPT', +'description': 'Amplification using QIAGEN OneStep RT-PCR Kit with 13 cycles', +'apiLevel': '2.15' +} + +def run(protocol: protocol_api.ProtocolContext): # Sample preparation parameters +number_of_samples = 64 +sample_volume_ul = 5 # Volume in microliters +master_mix_volume_ul = 7 # Volume in microliters +mixing_cycles = 9 +total_mix_volume_ul = sample_volume_ul + master_mix_volume_ul +master_mix_temperature_c = 10 # Temperature in Celsius +sample_temperature_c = 4 # Temperature in Celsius + + # Thermocycler parameters + lid_temperature_c = 55 # Lid temperature in Celsius + initial_plate_temperature_c = 6 # Initial plate temperature in Celsius + hold_temperature_c = 4 # Hold temperature in Celsius for infinite hold + + # Modules loading + thermocycler_module = protocol.load_module('thermocyclerModuleV2') + sample_temperature_module = protocol.load_module('temperature module gen2', 1) + master_mix_temperature_module = protocol.load_module('temperature module gen2', 3) + + # Labware loading + tips_20ul = protocol.load_labware('opentrons_96_filtertiprack_20ul', 4) + pcr_plate = thermocycler_module.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + sample_plate = sample_temperature_module.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + master_mix_plate = master_mix_temperature_module.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + + # Pipette loading + multi_channel_pipette = protocol.load_instrument('p20_multi_gen2', 'left', tip_racks=[tips_20ul]) + + # Well allocation + number_of_columns = math.ceil(number_of_samples / 8) + sample_source_wells = sample_plate.columns()[:number_of_columns] + sample_destination_wells = pcr_plate.columns()[:number_of_columns] + master_mix_source_wells = master_mix_plate.columns()[:number_of_columns] + master_mix_destination_wells = pcr_plate.columns()[:number_of_columns] + + # Command 2: Open lid + thermocycler_module.open_lid() + + # Command 3: Set initial plate temperature + thermocycler_module.set_block_temperature(initial_plate_temperature_c) + + # Command 4: Set lid temperature + thermocycler_module.set_lid_temperature(lid_temperature_c) + + # Command 5: Set sample temperature + sample_temperature_module.set_temperature(sample_temperature_c) + + # Command 6: Set master mix temperature + master_mix_temperature_module.set_temperature(master_mix_temperature_c) + + # Command 7: Transfer master mix to destination wells + multi_channel_pipette.transfer( + master_mix_volume_ul, + master_mix_source_wells, + master_mix_destination_wells, + new_tip='once' + ) + + # Command 8: Transfer samples to destination wells and mix + multi_channel_pipette.transfer( + sample_volume_ul, + sample_source_wells, + sample_destination_wells, + new_tip='always', + mix_after=(mixing_cycles, total_mix_volume_ul), + blow_out=True, + blowout_location='destination well' + ) + + # Command 9: Close lid + thermocycler_module.close_lid() + + # Commands 10-12: PCR cycling + thermocycler_module.execute_profile( + steps=[{'temperature': 74, 'hold_time_seconds': 65}], + repetitions=1, + block_max_volume=total_mix_volume_ul + ) + thermocycler_module.execute_profile( + steps=[ + {'temperature': temp, 'hold_time_seconds': duration} + for temp, duration in zip([60, 84, 57], [7, 19, 44]) + ], + repetitions=13, + block_max_volume=total_mix_volume_ul + ) + thermocycler_module.execute_profile( + steps=[{'temperature': 75, 'hold_time_seconds': 480}], + repetitions=1, + block_max_volume=total_mix_volume_ul + ) + + # Command 13: Set final hold temperature + thermocycler_module.set_block_temperature(hold_temperature_c) + + # Command 14: Open lid post-PCR + thermocycler_module.open_lid() + + # Commands 15 & 16: Deactivate temperature modules + master_mix_temperature_module.deactivate() + sample_temperature_module.deactivate() + + + +[7] Example + +Metadata: + +- Author: Bob +- ProtocolName: PCR + +Requirements: + +- requirements = {"robotType": "Flex", "apiLevel": "2.15"} + +Modules: + +- Thermocycler module GEN 2 is present on slot A1+B1 +- Temperature module GEN 2 is placed on slot D1 +- Mastermix temperature module GEN 2 is placed on slot D3 + +Adapter: + +- Opentrons 96 Well Aluminum Block adapter is placed on the temperature module GEN 2 +- Opentrons 96 Well Aluminum Block adapter is placed on the mastermix temperature module GEN 2 + +Labware: + +- Source labware: `Opentrons Tough 96 Well Plate 200 uL PCR Full Skirt` placed on the temperature module +- Source mastermix labware: `Opentrons Tough 96 Well Plate 200 uL PCR Full Skirt` placed on temperature module +- Destination labware: `Opentrons Tough 96 Well Plate 200 uL PCR Full Skirt`, placed on C3 +- Load three `opentrons_flex_96_filtertiprack_50ul` tip racks on `opentrons_flex_96_tiprack_adapter` adapters in slots A2, B2, and C2 + +Pipette mount: + +- `Flex 96-Channel 1000 uL Pipette` is mounted on the left side + +Well Allocation: + +- source well: A1 well of source labware +- destination well: A1 well of destination labware +- mastermix well: A1 well of mastermix labware + +Commands: + +1. Set the thermocycler block temperature to 22 C. +2. Open the thermocycler lid. +3. Set the thermocycler lid temperature to 95 C. +4. Set the temperature module to 37 C. +5. Set master mix temperature module to 10 C. +6. Transfer 20 uL of liquid from 5 mm below the top surface of mastermix well to 2 mm above the bottom of destination well. Use the same tip for each transfer. +7. Transfer 20 ul of liquid from 3 mm above the source well bottom to destination well 7 mm beneath the top surface. Flow rate is at half the default. + Mix the sample and mastermix of 40 ul total volume 5 times. Remove the tip slowly out of the well at 5 mm/s speed. Use the same tip for each transfer. +8. Move the destination labware to the thermocycler using gripper. +9. Close the thermocycler lid. +10. Execute the thermocycle using the following profile: + +- 74 degree C for 65 seconds for 1 cycle, block max volume is sample and mastermix volume + +11. Execute the thermocycle using the following profile: + +- 60 degree C for 7 seconds, 84 degree C for 19 seconds, 57 degree C for 44 seconds for 25 cycles, block max volume is sample and mastermix volume + +12. Execute the thermocycle using the following profile: + +- 75 degree C for 480 seconds for 1 cycle, block max volume is sample and mastermix volume + +13. Hold thermocycler block at 4 C. +14. Open thermocycler lid. +15. Move the destination labware from thermocycler back to its original slot C3 using gripper. +16. Pause the protocol and tell the user to pick up the destination plate, seal it and refrigerate it at 4 C. +17. Deactivate the temperature modules. +18. Deactivate the mastermix temperature modules. + + + +from opentrons import protocol_api +metadata = { + 'protocol_name': 'PCR Amplification protocol', + 'author': 'Opentrons Generative AI', + 'description': 'PCR Amplification protocol with 25 cycles', +} + +requirements = {"robotType": "Flex", "apiLevel": "2.15"} + +def run(protocol: protocol_api.ProtocolContext): # Sample parameters +sample_volume_ul = 20 +master_mix_volume_ul = 20 +mix_cycles = 5 +total_mix_volume_ul = sample_volume_ul + master_mix_volume_ul +return_slot = 'C3' + + master_mix_temperature_c = 10 + sample_temperature_c = 37 + step1_cycles = 1 + step2_cycles = 25 + step3_cycles = 1 + + # Thermocycler parameters + lid_temperature_c = 95 + initial_block_temperature_c = 22 + final_hold_temperature_c = 4 + + # Modules + thermocycler_module = protocol.load_module('thermocyclerModuleV2') + sample_temperature_module = protocol.load_module('temperature module gen2', 'D1') + master_mix_temperature_module = protocol.load_module('temperature module gen2', 'D3') + + # Adapters + sample_adapter = sample_temperature_module.load_adapter('opentrons_96_well_aluminum_block') + master_mix_adapter = master_mix_temperature_module.load_adapter('opentrons_96_well_aluminum_block') + + # Labware + sample_plate = sample_adapter.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + master_mix_plate = master_mix_adapter.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + destination_plate = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 'C3') + tips_50ul = [ + protocol.load_labware( + 'opentrons_flex_96_filtertiprack_50ul', + slot, + adapter="opentrons_flex_96_tiprack_adapter" + ) + for slot in ['A2', 'B2', 'C2'] + ] + + # Pipette + pipette_96channel = protocol.load_instrument('flex_96channel_1000', 'left', tip_racks=tips_50ul) + + # Well allocation + sample_source_wells = sample_plate['A1'] + destination_wells = destination_plate['A1'] + master_mix_source_well = master_mix_plate['A1'] + + # Set thermocycler block and lid temperature + thermocycler_module.set_block_temperature(initial_block_temperature_c) + thermocycler_module.open_lid() + thermocycler_module.set_lid_temperature(lid_temperature_c) + + # Temperature module setup + sample_temperature_module.set_temperature(sample_temperature_c) + master_mix_temperature_module.set_temperature(master_mix_temperature_c) + + # Master mix transfer + pipette_96channel.transfer( + master_mix_volume_ul, + master_mix_source_well.top(-5), + destination_wells.bottom(2), + new_tip='once' + ) + + # Sample transfer + pipette_96channel.pick_up_tip() + pipette_96channel.aspirate(sample_volume_ul, sample_source_wells.bottom(3), rate=0.5) + pipette_96channel.dispense(sample_volume_ul, destination_wells.top(-7), rate=0.5) + pipette_96channel.mix(mix_cycles, total_mix_volume_ul) + pipette_96channel.move_to(destination_wells.top(), speed=5) + pipette_96channel.drop_tip() + + # Moving the plate to the thermocycler + protocol.move_labware(destination_plate, thermocycler_module, use_gripper=True) + + # PCR cycling + thermocycler_module.close_lid() + thermocycler_module.execute_profile( + steps=[ + {'temperature': 74, 'hold_time_seconds': 65} + ], + repetitions=step1_cycles, + block_max_volume=total_mix_volume_ul + ) + + thermocycler_module.execute_profile( + steps=[ + {'temperature': 60, 'hold_time_seconds': 7}, + {'temperature': 84, 'hold_time_seconds': 19}, + {'temperature': 57, 'hold_time_seconds': 44} + ], + repetitions=step2_cycles, + block_max_volume=total_mix_volume_ul + ) + + thermocycler_module.execute_profile( + steps=[{'temperature': 75, 'hold_time_seconds': 480}], + repetitions=step3_cycles, + block_max_volume=total_mix_volume_ul + ) + + thermocycler_module.set_block_temperature(final_hold_temperature_c) + thermocycler_module.open_lid() + + # Moving the plate back to its original location + protocol.move_labware(destination_plate, return_slot, use_gripper=True) + + # Optional: pause for manual intervention + protocol.pause("Pick up the destination plate, seal it, and refrigerate at 4C.") + + # Deactivate temperature modules at the end of the protocol + master_mix_temperature_module.deactivate() + sample_temperature_module.deactivate() + + + +## Common rules for transfer + +================= COMMON RULES for TRANSFER ================= + +- when we allocate wells for source and destination, we need to pay attention to pipette type. + For example, see the command below + +``` +Sample source wells: the first 64 well column-wise in the sample source plate. +``` + +- pipette (eg., Flex 8-Channel 1000 uL Pipette), given the number of wells + we need to estimate the columns and use method `labware.columns()` to access the columns. + For example, + +```python +number_of_columns = math.ceil([number_of_samples] / 8) +source_wells = labware.columns()[:number_of_columns] +``` + +- pipette (eg., Flex 1-Channel 1000 uL Pipette), + we use `labware.wells()`. For example, + +```python +source_wells = labware.wells()[:[number_of_samples]] +``` + +- If prompt says row-wise, we need to use `rows()` +- If prompt does not mention column-wise, we use `wells()` since it is default. +- If the number of samples are not specified, then use all wells. + +```python +source_wells = sample_plate.wells() +``` + +- If `blowout_location` location is mentioned explicitly, then incorporate to transfer method. +- Avoid using `for` with transfer + the following is incorrect: + +```python +source_columns = [source_labware.columns_by_name()[str(index)] for index in [3, 2, 5, 1, 10]] +destination_columns = [source_labware.columns_by_name()[str(index)] for index in [4, 8, 1, 9, 2]] + +# Transfer reagents +for src, dest in zip(source_columns, destination_columns): + pipette.transfer(14.0, src, dest, new_tip='always') +``` + +The correct: + +```python +source_columns = [source_labware.columns_by_name()[str(index)] for index in [3, 2, 5, 1, 10]] +destination_columns = [source_labware.columns_by_name()[str(index)] for index in [4, 8, 1, 9, 2]] + +# Transfer reagents +pipette.transfer(14.0, source_columns, destination_columns, new_tip='always') +``` + +## Other Examples with More Explanation + +### First Example + +**Description** + +Write a protocol using the Opentrons Python Protocol API v2 for the OT-2 robot based on the following details: + +**Labware:** + +- **Source Labware:** `Opentrons 24 Tube Rack with NEST 1.5 mL Snapcap` placed in slot 3. +- **Destination Labware:** `Opentrons Tough 96 Well Plate 200 µL PCR Full Skirt` placed in slot 9. +- **Tip Rack:** `Opentrons OT-2 96 Tip Rack 20 µL` placed in slot 2. + +**Pipette Mount:** + +- A **P20 Single Channel** pipette is mounted on the **right** side. + +**Commands:** + +1. Using the P20 Single Channel pipette, **transfer 1 µL** of reagent from the **first tube (A1)** of the source rack to **each well** in the destination plate. +2. **Use the same tip** for each transfer (do not change tips between transfers). + +**Protocol** + +```python +from opentrons import protocol_api + +metadata = { + 'protocolName': 'Reagent Transfer', + 'author': 'Your Name', + 'description': 'Transfer 1 µL of reagent from source tube A1 to all wells in the destination plate using the same tip.', +} +requirements = {"robotType": "OT-2", "apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + # Load labware + tiprack = protocol.load_labware('opentrons_96_tiprack_20ul', 2) + source_rack = protocol.load_labware('opentrons_24_tuberack_nest_1.5ml_snapcap', 3) + dest_plate = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 9) + + # Load pipette + p20 = protocol.load_instrument('p20_single_gen2', mount='right', tip_racks=[tiprack]) + + # Define source and destination wells + source_well = source_rack.wells_by_name()['A1'] + destination_wells = dest_plate.wells() + + # Perform transfer + p20.transfer( + 1, # Volume in µL + source_well, # Source well + destination_wells, # List of destination wells + new_tip='once' # Use the same tip for all transfers + ) +``` + +**Explanation:** + +- **Labware Setup:** + - **Tip Rack:** Loaded into slot 2 for the P20 pipette to pick up tips. + - **Source Rack:** Contains the reagent in tube A1, placed in slot 3. + - **Destination Plate:** The 96-well plate where the reagent will be dispensed, placed in slot 9. +- **Pipette Initialization:** + - The P20 Single Channel pipette is initialized on the right mount with the specified tip rack. +- **Transfer Details:** + - **Volume:** 1 µL is transferred from the source to each destination well. + - **Source Well:** Tube A1 in the source rack. + - **Destination Wells:** All wells in the 96-well plate. + - **Tip Usage:** `new_tip='once'` ensures the same tip is used throughout the entire transfer process. +- **Method Used:** + - The `transfer` method is used without any explicit loops because it inherently handles the iteration over the list of destination wells. + +--- + +### Second Example + +**Description** + +Using a **Flex 1-Channel 1000 µL Pipette** mounted on the **left** side, transfer **150 µL** from wells **A1** and **A2** in **source labware 1** to wells **B6** and **B7** in **source labware 2**. **Use the same tip** for each transfer. + +**Protocol** + +```python +from opentrons import protocol_api + +metadata = { + 'protocolName': 'Multiple Well Transfer', + 'author': 'Your Name', + 'description': 'Transfer 150 µL from specific source wells to specific destination wells using the same tip.', +} +requirements = {"robotType": "OT-2", "apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + # Load labware + source_labware_1 = protocol.load_labware('source_labware_1_definition', slot=1) + source_labware_2 = protocol.load_labware('source_labware_2_definition', slot=2) + tiprack = protocol.load_labware('opentrons_96_tiprack_1000ul', slot=3) + + # Load pipette + p1000 = protocol.load_instrument('flex_1channel_1000', mount='left', tip_racks=[tiprack]) + + # Define source and destination wells + source_wells = [source_labware_1.wells_by_name()[well] for well in ['A1', 'A2']] + destination_wells = [source_labware_2.wells_by_name()[well] for well in ['B6', 'B7']] + + # Perform transfer + p1000.transfer( + 150, # Volume in µL + source_wells, # List of source wells + destination_wells, # List of destination wells + new_tip='once' # Use the same tip for all transfers + ) +``` + +**Explanation:** + +- **Labware Setup:** + - **Source Labware 1:** Contains the initial samples in wells A1 and A2, loaded into slot 1. + - **Source Labware 2:** Will receive the transferred samples in wells B6 and B7, loaded into slot 2. + - **Tip Rack:** Loaded into slot 3 for the pipette to pick up tips. +- **Pipette Initialization:** + - The Flex 1-Channel 1000 µL pipette is initialized on the left mount with the specified tip rack. +- **Defining Wells:** + - **Source Wells:** A list containing wells A1 and A2 from source labware 1. + - **Destination Wells:** A list containing wells B6 and B7 from source labware 2. +- **Transfer Details:** + - **Volume:** 150 µL is transferred from each source well to the corresponding destination well. + - **Tip Usage:** `new_tip='once'` ensures the same tip is used for all transfers. +- **Method Used:** + - The `transfer` method is used with lists of source and destination wells. This method pairs each source well with its corresponding destination well, eliminating the need for explicit loops. + +**Note:** The use of a single `transfer` function with lists allows for multiple transfers in a streamlined manner. + +--- + +By using the `transfer` method effectively, we can simplify the protocol code and make it more readable. The method automatically handles the pairing and iteration over wells, so explicit loops are unnecessary. Additionally, specifying `new_tip='once'` optimizes the protocol by reducing tip usage when appropriate. + +## Best Practices for Optimizing the transfer Method in Pipetting Automation + +1. **Optimizing `transfer` Usage Without Loops** + + - **Issue**: Using the `transfer` method inside a `for` loop is unnecessary because `transfer` can handle lists implicitly. + - **Solution**: Remove the `for` loop and use the `transfer` method directly with lists for efficient code. + + **Example:** + + - _Inefficient Code (Excerpt-1):_ + + ```python + for source_well, destination_well in zip(source_wells, destination_wells): + pipette.pick_up_tip() + pipette.transfer(TRANSFER_VOL, source_well, destination_well, new_tip='never') + pipette.drop_tip() + ``` + + - _Optimized Code (Excerpt-2):_ + ```python + pipette.transfer(TRANSFER_VOL, source_wells, destination_wells, new_tip='always') + ``` + +2. **Correct Use of `new_tip='once'`** + + - **Note**: When instructed to "Use the same tip for all transfers" or similar, avoid using `new_tip='once'` inside a `for` loop, as this is incorrect. + - **Solution**: Use the `transfer` method without a `for` loop to ensure the same tip is used throughout. + + **Incorrect Usage:** + + ```python + for src, dest in zip(source_columns, destination_columns): + pipette.transfer(transfer_vol, src, dest, new_tip='once') + ``` + + **Correct Usage:** + + ```python + pipette.transfer(transfer_vol, source_columns, destination_columns, new_tip='once') + ``` + +3. **Importing Necessary Libraries** + + - **Reminder**: Always import necessary libraries, such as `math`, when using functions like `ceil` or other mathematical methods. + + ```python + import math + ``` + +4. **Using `columns` Method with Multi-Channel Pipettes** + + - **Guideline**: For multi-channel pipettes (e.g., P20 Multi-Channel Gen2), utilize the `columns` method to access labware columns effectively. + + **Example:** + + ```python + source_columns = source_plate.columns() + destination_columns = destination_plate.columns() + ``` + +--- + +### Another Example + +```python +"from opentrons import protocol_api + +# metadata +metadata = { + 'protocolName': 'Reagent Transfer', + 'author': 'Opentrons Generative AI', +} + +requirements = {"robotType": "Flex", "apiLevel": "2.19"} + + +def run(protocol: protocol_api.ProtocolContext): + # labware + source_labware_1 = [protocol.load_labware("corning_96_wellplate_360ul_flat", location=slot) for slot in ['D1', 'D2', 'D3']] + source_labware_2 = protocol.load_labware("nest_1_reservoir_195ml", location='A1') + destination = [protocol.load_labware("corning_96_wellplate_360ul_flat", location=slot) for slot in ['C1', 'C2', 'C3']] + tiprack200 = [protocol.load_labware("opentrons_flex_96_filtertiprack_200ul", location=slot) for slot in ['B1', 'B2', 'B3']] + tiprack50 = protocol.load_labware("opentrons_flex_96_filtertiprack_50ul", location='A2') + + # pipettes + p50s = protocol.load_instrument("flex_1channel_50", mount="right", tip_racks=[tiprack50]) + p1000s = protocol.load_instrument("flex_1channel_1000", mount="left", tip_racks=[*tiprack200]) + # load trash bin + trash = protocol.load_trash_bin('A3') + + # volumes setup + transfer_vol_1 = 20 + transfer_vol_2 = 100 + + # wells setup + source_wells_1 = [src.wells() for src in source_labware_1] + source_wells_2 = source_labware_2.wells_by_name()['A1'] + destination_wells_1 = [dest.wells() for dest in destination] + + # commands + p50s.transfer(transfer_vol_1, source_wells_2, destination_wells_1, new_tip="once") + p1000s.transfer(transfer_vol_2, source_wells_1, destination_wells_1, new_tip="always") +``` + +#### another example + +```python +from opentrons import protocol_api + +# metadata +metadata = { + 'protocolName': 'Reagent Transfer', + 'author': 'Opentrons Generative AI', +} + +requirements = {"robotType": "Flex", "apiLevel": "2.19"} + + +def run(protocol: protocol_api.ProtocolContext): + + # labware + source_1 = protocol.load_labware("corning_96_wellplate_360ul_flat", location='C1') + source_2 = protocol.load_labware("corning_96_wellplate_360ul_flat", location='C2') + destination_1 = protocol.load_labware("corning_96_wellplate_360ul_flat", location='D1') + destination_2 = protocol.load_labware("corning_96_wellplate_360ul_flat", location='D2') + tiprack200 = protocol.load_labware("opentrons_flex_96_filtertiprack_200ul", location='B2') + tiprack50 = protocol.load_labware("opentrons_flex_96_filtertiprack_50ul", location='A2') + + # pipettes + p1000s = protocol.load_instrument("flex_1channel_1000", mount="left", tip_racks=[tiprack200]) + p50s = protocol.load_instrument("flex_1channel_50", mount="right", tip_racks=[tiprack50]) + # load trash bin + trash = protocol.load_trash_bin('A3') + # volume setup + transfer_vol_1 = 50 + transfer_vol_2 = 15 + transfer_vol_3 = 10 + transfer_vol_4 = 10 + + # well setup + source_wells_1 = [source_1.wells_by_name()[wells] for wells in ['A1', 'A2']] + source_wells_2 = [source_2.wells_by_name()[wells] for wells in ['C4', 'C6']] + source_wells_3 = [source_2.wells_by_name()[wells] for wells in ['B6', 'B7']] + source_wells_4 = [source_2.wells_by_name()[wells] for wells in ['C4', 'C6']] + destination_wells_1 = [source_2.wells_by_name()[wells] for wells in ['B6', 'B7']] + destination_wells_2 = [source_1.wells_by_name()[wells] for wells in ['A3', 'A4']] + destination_wells_3 = [destination_1.wells_by_name()[wells] for wells in ['A1', 'B1']] + destination_wells_4 = [destination_2.wells_by_name()[wells] for wells in ['A1', 'B1']] + + # commands + p1000s.transfer(transfer_vol_1, source_wells_1, destination_wells_1, new_tip="once") + p50s.transfer(transfer_vol_2, source_wells_2, destination_wells_2, new_tip="once") + p50s.transfer(transfer_vol_3, source_wells_3, destination_wells_3, new_tip="always") + p50s.transfer(transfer_vol_4, source_wells_4, destination_wells_4, new_tip="always") +``` + +### pcr example + +```python +import math +from opentrons import protocol_api + +metadata = { + 'protocol_name': 'QIAGEN OneStep RT-PCR Kit PCR Amplification', + 'author': 'chatGPT', + 'description': 'Amplification using QIAGEN OneStep RT-PCR Kit with 13 cycles', + 'apiLevel': '2.16' +} + + +def run(protocol: protocol_api.ProtocolContext): + # Sample preparation parameters + number_of_samples = 64 + sample_volume_ul = 5 # Volume in microliters + master_mix_volume_ul = 7 # Volume in microliters + mixing_cycles = 9 + total_mix_volume_ul = sample_volume_ul + master_mix_volume_ul + master_mix_temperature_c = 10 # Temperature in Celsius + sample_temperature_c = 4 # Temperature in Celsius + + # Thermocycler parameters + lid_temperature_c = 55 # Lid temperature in Celsius + initial_plate_temperature_c = 6 # Initial plate temperature in Celsius + hold_temperature_c = 4 # Hold temperature in Celsius for infinite hold + + # Modules loading + thermocycler_module = protocol.load_module('thermocyclerModuleV2') + sample_temperature_module = protocol.load_module('temperature module gen2', 1) + master_mix_temperature_module = protocol.load_module('temperature module gen2', 3) + + # Labware loading + tips_20ul = protocol.load_labware('opentrons_96_filtertiprack_20ul', 4) + pcr_plate = thermocycler_module.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + sample_plate = sample_temperature_module.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + master_mix_plate = master_mix_temperature_module.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + + # Pipette loading + multi_channel_pipette = protocol.load_instrument('p20_multi_gen2', 'left', tip_racks=[tips_20ul]) + + # Well allocation + number_of_columns = math.ceil(number_of_samples / 8) + sample_source_wells = sample_plate.columns()[:number_of_columns] + sample_destination_wells = pcr_plate.columns()[:number_of_columns] + master_mix_source_wells = master_mix_plate.columns()[:number_of_columns] + master_mix_destination_wells = pcr_plate.columns()[:number_of_columns] + + # Command 2: Open lid + thermocycler_module.open_lid() + + # Command 3: Set initial plate temperature + thermocycler_module.set_block_temperature(initial_plate_temperature_c) + + # Command 4: Set lid temperature + thermocycler_module.set_lid_temperature(lid_temperature_c) + + # Command 5: Set sample temperature + sample_temperature_module.set_temperature(sample_temperature_c) + + # Command 6: Set master mix temperature + master_mix_temperature_module.set_temperature(master_mix_temperature_c) + + # Command 7: Transfer master mix to destination wells + multi_channel_pipette.transfer( + master_mix_volume_ul, + master_mix_source_wells, + master_mix_destination_wells, + new_tip='once' + ) + + # Command 8: Transfer samples to destination wells and mix + multi_channel_pipette.transfer( + sample_volume_ul, + sample_source_wells, + sample_destination_wells, + new_tip='always', + mix_after=(mixing_cycles, total_mix_volume_ul), + blow_out=True, + blowout_location='destination well' + ) + + # Command 9: Close lid + thermocycler_module.close_lid() + + # Commands 10-12: PCR cycling + thermocycler_module.execute_profile( + steps=[{'temperature': 74, 'hold_time_seconds': 65}], + repetitions=1, + block_max_volume=total_mix_volume_ul + ) + thermocycler_module.execute_profile( + steps=[ + {'temperature': temp, 'hold_time_seconds': duration} + for temp, duration in zip([60, 84, 57], [7, 19, 44]) + ], + repetitions=13, + block_max_volume=total_mix_volume_ul + ) + thermocycler_module.execute_profile( + steps=[{'temperature': 75, 'hold_time_seconds': 480}], + repetitions=1, + block_max_volume=total_mix_volume_ul + ) + + # Command 13: Set final hold temperature + thermocycler_module.set_block_temperature(hold_temperature_c) + + # Command 14: Open lid post-PCR + thermocycler_module.open_lid() + + # Commands 15 & 16: Deactivate temperature modules + master_mix_temperature_module.deactivate() + sample_temperature_module.deactivate() + +``` + +### Liquid transfer with Heater Shaker module + + +Write a protocol using the Opentrons Python Protocol API v2.19 for Opentrons Flex robot for the following description: + +Metadata: + +- Author: User +- ProtocolName: Liquid Transfer with Heater Shaker +- Description: Transfer liquids between reservoir, PCR plate, and heater shaker module's plate. + +Requirements: + +- requirements = {"robotType": "Flex", "apiLevel": "2.19"} + +Labware: + +- Trash: Load a trash bin in slot A3 +- Heater Shaker Module: Load in slot D1 with a Corning 96 Well Plate 360 µL Flat +- Reservoir: NEST 1 Well Reservoir 195 mL in slot C1 +- PCR Plate: NEST 96 Well Plate 200 µL Flat in slot D2 +- Tipracks: + - Opentrons Flex 96 Tiprack 200 µL in slot A2 + - Opentrons Flex 96 Tiprack 1000 µL in slot B2 + - Opentrons Flex 96 Tiprack 50 µL in slot C2 + All tipracks should use the Opentrons Flex 96 Tiprack Adapter + +Pipette: + +- Flex 96-Channel 1000 µL pipette mounted on the left + +Steps: + +1. Open the Heater Shaker Module's labware latch +2. Pause the protocol and prompt the user to load the Corning 96 well plate +3. Close the Heater Shaker Module's labware latch +4. Using the 96-channel pipette with 200 µL tips, transfer 70 µL from the reservoir (A1) to the Heater Shaker plate (A1) +5. Using the 96-channel pipette with 50 µL tips, transfer 10 µL from the PCR plate (A1) to the Heater Shaker plate (A1) +6. Shake the plate on the Heater Shaker Module at 2000 rpm for 1 minute +7. Deactivate the shaker + +Notes: + +- Use new tips for each transfer +- The protocol includes comments and pauses for user interaction + + + + +```python +from opentrons import protocol_api + +metadata = { + 'protocolName': 'Liquid Transfer with Heater Shaker', + 'author': 'User', + 'description': 'Transfer liquids between reservoir, PCR plate, and heater shaker module\'s plate.' +} + +requirements = { + 'robotType': 'Flex', + 'apiLevel': '2.19' +} + +def run(protocol: protocol_api.ProtocolContext): + # Load trash bin + trash = protocol.load_trash_bin('A3') + + # Load modules + heater_shaker = protocol.load_module('heaterShakerModuleV1', 'D1') + + # Load labware + heater_shaker_plate = heater_shaker.load_labware('corning_96_wellplate_360ul_flat') + reservoir = protocol.load_labware('nest_1_reservoir_195ml', 'C1') + pcr_plate = protocol.load_labware('nest_96_wellplate_200ul_flat', 'D2') + + # Load tipracks with adapters + tiprack_200 = protocol.load_labware('opentrons_flex_96_tiprack_200ul', 'A2', adapter='opentrons_flex_96_tiprack_adapter') + tiprack_1000 = protocol.load_labware('opentrons_flex_96_tiprack_1000ul', 'B2', adapter='opentrons_flex_96_tiprack_adapter') + tiprack_50 = protocol.load_labware('opentrons_flex_96_tiprack_50ul', 'C2', adapter='opentrons_flex_96_tiprack_adapter') + + # Load pipette + pipette = protocol.load_instrument('flex_96channel_1000', 'left', tip_racks=[tiprack_200, tiprack_1000, tiprack_50]) + + # Protocol steps + # 1. Open the Heater Shaker Module's labware latch + heater_shaker.open_labware_latch() + + # 2. Pause for user to load the plate + protocol.pause('Please load the Corning 96 well plate onto the Heater Shaker Module and resume the protocol.') + + # 3. Close the Heater Shaker Module's labware latch + heater_shaker.close_labware_latch() + + # 4. Transfer 70 µL from reservoir to Heater Shaker plate + pipette.transfer(70, reservoir['A1'], heater_shaker_plate['A1'], new_tip='always') + + # 5. Transfer 10 µL from PCR plate to Heater Shaker plate + pipette.transfer(10, pcr_plate['A1'], heater_shaker_plate['A1'], new_tip='always') + + # 6. Shake the plate + heater_shaker.set_and_wait_for_shake_speed(rpm=2000) + protocol.delay(minutes=1) + + # 7. Deactivate the shaker + heater_shaker.deactivate_shaker() +``` + + diff --git a/opentrons-ai-server/api/storage/docs/out_of_tips_error_219.md b/opentrons-ai-server/api/storage/docs/out_of_tips_error_219.md new file mode 100644 index 00000000000..0617543b916 --- /dev/null +++ b/opentrons-ai-server/api/storage/docs/out_of_tips_error_219.md @@ -0,0 +1,382 @@ +# Preventing "Out of Tips" and Index Errors in Opentrons Protocols + +"Out of tips" and index errors are common issues that can halt the execution of protocols on Opentrons robots. These errors occur when the protocol attempts to use more pipette tips than are available or when it tries to access wells beyond the labware's dimensions. Proper planning and understanding of tip consumption and labware indexing are essential to prevent such errors and ensure smooth laboratory operations. + +## Common Scenarios Leading to Errors + +### 1. Single Pipette Exceeds Tip Rack Capacity + +**Scenario:** +A single-channel pipette performs repeated operations using tips from a single tip rack without accounting for tip depletion. + +**Protocol Example:** + +```python +from opentrons import protocol_api + +metadata = { + 'protocolName': 'Single Tip Rack Exhaustion Example', + 'author': 'Opentrons', + 'description': 'A protocol that runs out of tips after exceeding tip rack capacity', + 'apiLevel': '2.13' +} + +def run(protocol: protocol_api.ProtocolContext): + # Load labware + plate = protocol.load_labware('corning_96_wellplate_360ul_flat', '1') + tip_rack = protocol.load_labware('opentrons_96_tiprack_300ul', '2') + + # Load pipette + pipette = protocol.load_instrument('p300_single', 'left', tip_racks=[tip_rack]) + + # Perform operations + for _ in range(100): + pipette.pick_up_tip() + pipette.aspirate(100, plate['A1']) + pipette.dispense(100, plate['B1']) + pipette.drop_tip() +``` + +**Issue Explanation:** +The protocol attempts 100 tip pickups using a single tip rack containing only 96 tips. After 96 successful pickups, the pipette runs out of tips, resulting in an error on the 97th attempt. + +--- + +### 2. Multi-Channel Pipette with Insufficient Tip Racks + +**Scenario:** +A multi-channel pipette uses tips from a single tip rack but requires more tips than are available due to the number of channels used per operation. + +**Protocol Example:** + +```python +from opentrons import protocol_api + +metadata = { + 'protocolName': 'Multi-Channel Tip Rack Exhaustion Example', + 'author': 'Opentrons', + 'description': 'A protocol where a multi-channel pipette runs out of tips', + 'apiLevel': '2.13' +} + +def run(protocol: protocol_api.ProtocolContext): + # Load labware + plate = protocol.load_labware('corning_96_wellplate_360ul_flat', '1') + tip_rack = protocol.load_labware('opentrons_96_tiprack_300ul', '2') + + # Load pipette + pipette = protocol.load_instrument('p300_multi', 'right', tip_racks=[tip_rack]) + + # Perform operations + for i in range(20): + pipette.pick_up_tip() + pipette.aspirate(100, plate.rows()[0][i]) + pipette.dispense(100, plate.rows()[1][i]) + pipette.drop_tip() +``` + +**Issue Explanation:** +A multi-channel pipette uses 8 tips per pick-up. Over 20 iterations, it requires 160 tips (20 iterations × 8 tips). A single 96-tip rack is exhausted after 12 iterations (96 tips / 8 tips per iteration), causing an error during the 13th iteration. Additionally, attempting to access `plate.rows()[0][i]` where `i` exceeds 11 (the maximum index for 12 columns) results in an index error. + +**Solution:** + +- **Load Additional Tip Racks:** Introduce more tip racks to provide enough tips for all operations. +- **Validate Index Ranges:** Ensure that the loop indices do not exceed the labware dimensions. + +**Corrected Protocol Example:** + +```python +from opentrons import protocol_api + +metadata = { + 'protocolName': 'Multi-Channel Tip Rack Exhaustion Example - Solved', + 'author': 'Opentrons', + 'description': 'Multi-channel pipette avoids running out of tips and index errors', + 'apiLevel': '2.13' +} + +def run(protocol: protocol_api.ProtocolContext): + # Load labware + plate = protocol.load_labware('corning_96_wellplate_360ul_flat', '1') + tip_rack1 = protocol.load_labware('opentrons_96_tiprack_300ul', '2') + tip_rack2 = protocol.load_labware('opentrons_96_tiprack_300ul', '3') + + # Load pipette + pipette = protocol.load_instrument('p300_multi', 'right', tip_racks=[tip_rack1, tip_rack2]) + + # Perform operations within available columns range + for i in range(12): # Restrict to 12 columns + pipette.pick_up_tip() + pipette.aspirate(100, plate.columns()[i][0]) + pipette.dispense(100, plate.columns()[i][1]) + pipette.drop_tip() +``` + +--- + +### 3. Nested Loops Causing Excessive Tip Usage + +**Scenario:** +Nested loops in the protocol lead to a higher number of tip pickups than anticipated, exhausting the available tips. + +**Protocol Example:** + +```python +from opentrons import protocol_api + +metadata = { + 'protocolName': 'Nested Loops Tip Exhaustion Example', + 'author': 'Opentrons', + 'description': 'A protocol demonstrating tip exhaustion due to nested loops', + 'apiLevel': '2.13' +} + +def run(protocol: protocol_api.ProtocolContext): + # Load labware + plate = protocol.load_labware('corning_96_wellplate_360ul_flat', '1') + tip_rack = protocol.load_labware('opentrons_96_tiprack_300ul', '2') + + # Load pipette + pipette = protocol.load_instrument('p300_single', 'left', tip_racks=[tip_rack]) + + # Perform operations + for row in range(8): + for col in range(12): + for _ in range(2): + pipette.pick_up_tip() + pipette.aspirate(100, plate.rows()[row][col]) + pipette.dispense(100, plate.rows()[row][(col + 1) % 12]) + pipette.drop_tip() +``` + +**Issue Explanation:** +The nested loops result in 192 tip pickups (8 rows × 12 columns × 2 repetitions). With only 96 tips available, the protocol runs out of tips halfway through, causing an error. + +**Solution:** +Introduce additional tip racks to provide enough tips for all operations. + +**Corrected Protocol Example:** + +```python +def run(protocol: protocol_api.ProtocolContext): + # Load labware + plate = protocol.load_labware('corning_96_wellplate_360ul_flat', '1') + tip_rack1 = protocol.load_labware('opentrons_96_tiprack_300ul', '2') + tip_rack2 = protocol.load_labware('opentrons_96_tiprack_300ul', '3') + + # Load pipette + pipette = protocol.load_instrument('p300_single', 'left', tip_racks=[tip_rack1, tip_rack2]) + + # Perform operations (same as above) +``` + +--- + +## Calculating Tip Usage in Many-to-Many Transfers + +In protocols involving many-to-many transfers, it's crucial to calculate the number of tips required accurately to avoid "out of tips" errors. + +### Guidelines for Many-to-Many Transfers + +- **Even Divisibility:** Ensure the number of wells in the larger group (source or destination) is evenly divisible by the number of wells in the smaller group. +- **Stretching the Smaller Group:** Conceptually "stretch" the smaller group of wells to match the length of the larger group. Each well in the smaller group may be used multiple times. +- **Tip Requirement:** The number of tips required is always equal to the number of wells in the larger group. +- **Multi-Channel Pipettes:** For multi-channel pipettes, remember that each operation uses multiple tips (e.g., 8 tips for an 8-channel pipette). If using a 96-channel pipette, each operation consumes 96 tips. + +### Example Calculation + +- **Scenario:** Transfer from 24 source wells to 96 destination wells. +- **Process:** + - The 24 source wells are stretched to match the 96 destination wells. + - Each source well is used multiple times to cover all destination wells. + - **Total Transfers:** 96. + - **Tips Required:** + - **Single-Channel Pipette:** 96 tips (one per transfer). + - **Multi-Channel Pipette (8-channel):** 12 transfers (96 wells / 8 channels), using 8 tips per transfer, totaling 96 tips. + +--- + +## Key Points to Avoid Index Errors + +- **Validate Access Ranges:** Always ensure that your loops and operations do not exceed the dimensions of the labware being used. For example, a 96-well plate has 12 columns and 8 rows; accessing an index beyond these ranges will cause an error. +- **Sufficient Resources:** Make sure the number of loaded tip racks can handle the total number of operations required by the protocol. + +**Example Problem:** + +A multi-channel pipette runs out of tips after 12 operations due to using 8 tips per operation, and the code attempts to access non-existent column indices beyond the 12 columns available in a 96-well plate. + +**Incorrect Protocol Example:** + +```python +from opentrons import protocol_api + +metadata = { + 'protocolName': 'Multi-Channel Index Error Example', + 'author': 'Opentrons', + 'description': 'A protocol that causes index errors due to invalid column access', + 'apiLevel': '2.13' +} + +def run(protocol: protocol_api.ProtocolContext): + # Load labware + plate = protocol.load_labware('corning_96_wellplate_360ul_flat', '1') + tip_rack = protocol.load_labware('opentrons_96_tiprack_300ul', '2') + + # Load pipette + pipette = protocol.load_instrument('p300_multi', 'right', tip_racks=[tip_rack]) + + # Perform operations + for i in range(20): # Exceeds available columns + pipette.pick_up_tip() + pipette.aspirate(100, plate.columns()[i][0]) + pipette.dispense(100, plate.columns()[i][1]) + pipette.drop_tip() +``` + +**Solution:** + +- **Restrict Loop Indices:** Adjust the loop to stay within the valid column indices (0 to 11 for a 96-well plate). +- **Load Additional Tip Racks:** Ensure enough tips are available for all operations. + +**Corrected Protocol Example:** + +```python +from opentrons import protocol_api + +metadata = { + 'protocolName': 'Multi-Channel Index Error Example - Solved', + 'author': 'Opentrons', + 'description': 'A protocol that avoids index errors by validating column indices', + 'apiLevel': '2.13' +} + +def run(protocol: protocol_api.ProtocolContext): + # Load labware + plate = protocol.load_labware('corning_96_wellplate_360ul_flat', '1') + tip_rack1 = protocol.load_labware('opentrons_96_tiprack_300ul', '2') + tip_rack2 = protocol.load_labware('opentrons_96_tiprack_300ul', '3') + + # Load pipette + pipette = protocol.load_instrument('p300_multi', 'right', tip_racks=[tip_rack1, tip_rack2]) + + # Perform operations within available columns range + for i in range(12): # Valid column indices for a 96-well plate + pipette.pick_up_tip() + pipette.aspirate(100, plate.columns()[i][0]) + pipette.dispense(100, plate.columns()[i][1]) + pipette.drop_tip() +``` + +--- + +## Best Practices to Avoid "Out of Tips" and Index Errors + +### 1. Calculate Required Tips in Advance + +- **Estimate Operations:** Calculate the total number of pipetting actions that require new tips, including loops and many-to-many transfers. +- **Consider Multiple Pipettes:** Calculate tip requirements separately for each pipette, accounting for their specific usage patterns. + +### 2. Load Sufficient Tip Racks + +- **Tip Rack Capacity:** Standard 96-tip racks hold 96 tips. Ensure the total number of tips available meets or exceeds your calculated requirement. +- **Add Buffers:** Include extra tip racks to handle unexpected needs or minor calculation errors. + +### 3. Validate Labware Indexing + +- **Check Labware Dimensions:** Before accessing wells or columns in loops, confirm the dimensions of your labware to avoid index errors. +- **Adjust Loop Ranges:** Ensure that loop indices do not exceed the maximum indices of the labware being used. + +### 4. Associate Tip Racks with Pipettes + +- **Specify Tip Racks:** Explicitly associate each pipette with its corresponding tip racks for efficient tip tracking. +- **Multiple Tip Racks:** Use multiple tip racks for pipettes with high tip consumption. + +### 5. Implement Tip Replenishment Strategies + +- **Dynamic Replenishment:** Use commands like `move_labware()` to swap in fresh tip racks during long protocols. +- **Manual Replenishment:** Plan steps within the protocol to allow for manual replacement of tip racks if automatic replenishment isn't feasible. + +### 6. Optimize Tip Usage + +- **Reuse Tips When Appropriate:** If protocol requirements allow, reuse the same tip for multiple transfers to reduce tip consumption. +- **Minimize Tip Pickups:** Combine transfers when possible to limit the number of tip pickups. + +### 7. Handle Special Cases Carefully + +- **Multi-Channel Pipettes:** Remember that multi-channel pipettes consume multiple tips per pickup. Adjust tip rack quantities accordingly. +- **Nested Loops:** Be cautious with nested loops, as they can exponentially increase tip usage. Validate tip requirements before execution. +- **Many-to-Many Transfers:** Apply the specific calculations for many-to-many transfers to determine accurate tip usage. + +### 8. Implement Error Handling and Testing + +- **Catch Errors Early:** Incorporate checks to detect potential "out of tips" or index errors before they cause runtime issues. +- **Conduct Dry Runs:** Perform simulations or test runs to ensure all logical paths are covered and tip requirements are met. + +--- + +## Example when using serial dilution protocol + +Below protocol produces `OutofTips` error, since it excauts all tips by using `plate.rows()`: +(One needs to be careful) + +```python +from opentrons import protocol_api + +requirements = { + 'robotType': 'OT-2', + 'apiLevel': '2.16' +} + +def run(protocol: protocol_api.ProtocolContext): + # Load labware + tiprack = protocol.load_labware('opentrons_96_tiprack_300ul', 1) + reservoir = protocol.load_labware('nest_12_reservoir_15ml', 2) + plate = protocol.load_labware('nest_96_wellplate_200ul_flat', 3) + + # Load pipette + p300 = protocol.load_instrument('p300_single_gen2', 'left', tip_racks=[tiprack]) + + # Distribute diluent + p300.transfer(100, reservoir['A1'], plate.wells()) + + # Perform serial dilution + for row in plate.rows(): + # Transfer and mix solution from reservoir to first well + p300.transfer(100, reservoir['A2'], row[0], mix_after=(3, 50), new_tip='always') + + # Serial dilution within the row + p300.transfer(100, row[:11], row[1:], mix_after=(3, 50), new_tip='always') + +``` + +Correct way is follows: + +```python +from opentrons import protocol_api + +requirements = { + 'robotType': 'OT-2', + 'apiLevel': '2.16' +} + +def run(protocol: protocol_api.ProtocolContext): + tips = protocol.load_labware("opentrons_96_tiprack_300ul", 1) + reservoir = protocol.load_labware("nest_12_reservoir_15ml", 2) + plate = protocol.load_labware("nest_96_wellplate_200ul_flat", 3) + left_pipette = protocol.load_instrument("p300_single_gen2", "left", tip_racks=[tips]) + + # distribute diluent + left_pipette.transfer(100, reservoir["A1"], plate.wells()) + + # loop through each row + for i in range(8): + + # save the destination row to a variable + row = plate.rows()[i] + + # transfer solution to first well in column + left_pipette.transfer(100, reservoir["A2"], row[0], mix_after=(3, 50)) + + # dilute the sample down the row + left_pipette.transfer(100, row[:11], row[1:], mix_after=(3, 50)) +``` diff --git a/opentrons-ai-server/api/storage/docs/pcr_protocols_with_csv.md b/opentrons-ai-server/api/storage/docs/pcr_protocols_with_csv.md new file mode 100644 index 00000000000..40b1ce10739 --- /dev/null +++ b/opentrons-ai-server/api/storage/docs/pcr_protocols_with_csv.md @@ -0,0 +1,1065 @@ +# PCR protocol examples + +## 1. PCR protocol + + +Write a protocol using the Opentrons Python Protocol API v2 for the OT-2 robot according to the following description: + +Requirements: + +- requirements = {"robotType": "OT-2", "apiLevel": "2.19"} + +Modules: + +- Temperature module GEN2 for samples in slot 1 +- Temperature module GEN2 for mastermix in slot 3 + +Labware: + +- Sample source: Opentrons 96 Tough Well Plate 200 uL PCR Full Skirt on sample temperature module +- Mastermix source: Opentrons 96 Tough Well Plate 200 uL PCR Full Skirt on mastermix temperature module +- Destination: Opentrons 96 Tough Well Plate 200 uL PCR Full Skirt in slot 7 +- Tips: Opentrons 96 Filter Tip Rack 20 uL in slot 4 + +Pipette: + +- Left mount: P20 Multi-Channel Gen2 + +Sample Setup: + +- Number of samples: 64 (8 columns) +- Well allocation: First 64 wells (column-wise) in all plates + +Temperature Settings: + +- Sample temperature: 4C +- Mastermix temperature: 10C + +Protocol Steps: + +1. Set temperature modules to specified temperatures +2. Transfer 7 uL mastermix to destination wells (reuse tip) +3. Transfer 5 uL sample to destination wells, mix 9 times with 12 uL total volume + (use new tip for each transfer, blow out to destination well) +4. Deactivate both temperature modules + + + + +```python +from opentrons import protocol_api + +requirements = {"robotType": "OT-2", "apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + # Module loading + temp_mod_sample = protocol.load_module('temperature module gen2', 1) + temp_mod_mastermix = protocol.load_module('temperature module gen2', 3) + + # Load thermal adapters + sample_adapter = temp_mod_sample.load_adapter("opentrons_96_well_aluminum_block") + mastermix_adapter = temp_mod_mastermix.load_adapter("opentrons_96_well_aluminum_block") + + # Load labware onto the adapters + sample_plate = sample_adapter.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + mastermix_plate = mastermix_adapter.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + + dest_plate = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 7) + tiprack = protocol.load_labware('opentrons_96_filtertiprack_20ul', 4) + + # Pipette loading + p20_single = protocol.load_instrument('p20_multi_gen2', 'left', tip_racks=[tiprack]) + + # Well allocation + num_samples = 64 + num_cols = num_samples // 8 + sample_wells = sample_plate.columns()[:num_cols] + mastermix_wells = mastermix_plate.columns()[:num_cols] + dest_wells = dest_plate.columns()[:num_cols] + + # Volume and temperature parameters + sample_temp = 4 # Celsius + mastermix_temp = 10 # Celsius + mastermix_vol = 7 # uL + sample_vol = 5 # uL + mix_cycles = 9 + total_mix_vol = sample_vol + mastermix_vol # 12 uL total + + # Execute commands in order specified in description + # Command 1: Set sample temperature + temp_mod_sample.set_temperature(sample_temp) + + # Command 2: Set mastermix temperature + temp_mod_mastermix.set_temperature(mastermix_temp) + + # Command 3: Transfer mastermix + p20_single.transfer( + mastermix_vol, + mastermix_wells, + dest_wells, + new_tip='once' + ) + + # Command 4: Transfer samples and mix + p20_single.transfer( + sample_vol, + sample_wells, + dest_wells, + new_tip='always', + mix_after=(mix_cycles, total_mix_vol), + blow_out=True, + blowout_location='destination well' + ) + + # Command 5 and 6: Deactivate temperature modules + temp_mod_mastermix.deactivate() + temp_mod_sample.deactivate() +``` + + + +## 2. PCR protocol + + +Write a protocol using the Opentrons Python Protocol API v2 for OT-2 robot according to the following description: + +Requirements: + +- requirements = {"robotType": "OT-2", "apiLevel": "2.19"} + +Module: + +- Temperature module GEN2 on slot 1 + +Labware: + +- Sample Source: Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap on temperature module +- Mastermix Source: Opentrons 24 Tube Rack with NEST 1.5 mL Snapcap on slot 3 +- Destination: Opentrons 96 Well Plate 200 uL PCR Full Skirt on slot 7 +- Tips: Opentrons 96 Filter Tip Rack 20 uL on slot 4 + +Pipette: + +- Right mount: P20 Single Channel GEN2 + +Sample Setup: + +- Number of samples: 24 +- Well allocation: First 24 wells (column-wise) in all plates + +Protocol Steps: + +1. Set temperature module to 4°C +2. Transfer 8 uL mastermix to destination wells (reuse same tip) +3. Transfer 7 uL sample to destination wells, mix 4 times with 15 uL total volume + (use new tip for each transfer, blow out to destination well) +4. Deactivate temperature module + + + + +```python +from opentrons import protocol_api + +requirements = { + 'robotType': 'OT-2', + 'apiLevel': '2.19' +} + +def run(protocol: protocol_api.ProtocolContext): + + # Protocol parameters + num_samples = 24 + sample_vol = 7 # uL + mastermix_vol = 8 # uL + mix_cycles = 4 + total_vol = sample_vol + mastermix_vol # 15 uL total + temp_celsius = 4 + + # Load temperature module + temp_module = protocol.load_module('temperature module gen2', 1) + + # Load labware + source_samples = temp_module.load_labware('opentrons_24_aluminumblock_nest_1.5ml_snapcap') + source_mastermix = protocol.load_labware('opentrons_24_tuberack_nest_1.5ml_snapcap', 3) + dest_plate = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 7) + tiprack = protocol.load_labware('opentrons_96_filtertiprack_20ul', 4) + + # Load pipette + p20_single = protocol.load_instrument('p20_single_gen2', 'right', tip_racks=[tiprack]) + + # Define wells + sample_wells = source_samples.wells()[:num_samples] + mastermix_wells = source_mastermix.wells()[:num_samples] + dest_wells = dest_plate.wells()[:num_samples] + + # Set temperature + temp_module.set_temperature(temp_celsius) + + # Transfer mastermix + p20_single.transfer( + mastermix_vol, + mastermix_wells, + dest_wells, + new_tip='once' + ) + + # Transfer samples and mix + p20_single.transfer( + sample_vol, + sample_wells, + dest_wells, + mix_after=(mix_cycles, total_vol), + blow_out=True, + blowout_location='destination well', + new_tip='always' + ) + + # Deactivate temperature module + temp_module.deactivate() +``` + + + +## 3. PCR protocol + + +Write a protocol using the Opentrons Python Protocol API v2 for Flex robot according to the following description: + +Requirements: + +- requirements = {"robotType": "Flex", "apiLevel": "2.19"} + +Temperature Modules: + +- Sample module: Temperature Module GEN2 on slot D1 +- Mastermix module: Temperature Module GEN2 on slot D3 + +Module Adapters: + +- Opentrons 96 Well Aluminum Block on both temperature modules + +Labware: + +1. Source Plates: + - Sample plate: Opentrons 96 Tough Well Plate 200 uL PCR Full Skirt on sample module + - Mastermix plate: Opentrons 96 Tough Well Plate 200 uL PCR Full Skirt on mastermix module +2. Destination: + - Opentrons 96 Tough Well Plate 200 uL PCR Full Skirt on slot A1 +3. Tips: + - Opentrons Flex 96 Filter Tip Rack 1000 uL on slots C1 and C2 + +Pipettes: + +- Left mount: Flex 8-Channel 1000 uL +- Right mount: Flex 8-Channel 50 uL + +Sample Setup: + +- Total samples: 72 +- Well usage: First 72 wells (column-wise) in all plates + +Protocol Steps: + +1. Temperature Setup: + a. Set sample module to 37°C + b. Set mastermix module to 4°C + +2. Mastermix Transfer: + + - Transfer 15 uL mastermix from source to destination wells + - Use the same tip for all transfers + +3. Sample Transfer: + + - Transfer 10 uL sample from source to destination wells + - Mix 9 times with 25 uL total volume after each transfer + - Use a new tip for each transfer + - Blow out to destination well after each transfer + +4. Module Shutdown: + a. Deactivate mastermix temperature module + b. Deactivate sample temperature module + + + + +```python +from opentrons import protocol_api + +requirements = {"robotType": "Flex", "apiLevel": "2.19"} + +def run(protocol: protocol_api.ProtocolContext): + # Trash + trash = protocol.load_trash_bin("A3") + + # Protocol parameters + num_samples = 72 + mastermix_vol = 15 # uL + sample_vol = 10 # uL + mix_cycles = 9 + total_vol = mastermix_vol + sample_vol + + # Temperature settings + temp_mastermix = 4 # C + temp_sample = 37 # C + + # Load temperature modules + temp_mod_sample = protocol.load_module('temperature module gen2', 'D1') + temp_mod_mastermix = protocol.load_module('temperature module gen2', 'D3') + + # Load module adapters + block_sample = temp_mod_sample.load_adapter('opentrons_96_well_aluminum_block') + block_mastermix = temp_mod_mastermix.load_adapter('opentrons_96_well_aluminum_block') + + # Load labware + plate_sample = block_sample.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + plate_mastermix = block_mastermix.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + plate_dest = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 'A1') + + # Load tip racks + tips_1000 = protocol.load_labware('opentrons_flex_96_filtertiprack_1000ul', 'C1') + tips_50 = protocol.load_labware('opentrons_flex_96_filtertiprack_1000ul', 'C2') + + # Load pipettes + p50_multi = protocol.load_instrument('flex_8channel_50', 'right', tip_racks=[tips_50]) + p1000_multi = protocol.load_instrument('flex_8channel_1000', 'left', tip_racks=[tips_1000]) + + # Set up well arrays + source_mastermix = plate_mastermix.columns()[:num_samples//8] + source_sample = plate_sample.columns()[:num_samples//8] + wells_dest = plate_dest.columns()[:num_samples//8] + + # Step 1: Set temperatures + temp_mod_sample.set_temperature(temp_sample) + temp_mod_mastermix.set_temperature(temp_mastermix) + + # Step 2: Transfer mastermix + p50_multi.transfer( + mastermix_vol, + source_mastermix, + wells_dest, + new_tip='once' + ) + + # Step 3: Transfer samples + p1000_multi.transfer( + sample_vol, + source_sample, + wells_dest, + new_tip='always', + mix_after=(mix_cycles, total_vol), + blow_out=True, + blowout_location='destination well' + ) + + # Step 4: Deactivate modules + temp_mod_mastermix.deactivate() + temp_mod_sample.deactivate() +``` + + + +## 4. PCR protocol + + +Write a protocol using the Opentrons Python Protocol API v2 for the Flex robot according to the following description: + +Requirements: + +- `requirements = {"robotType": "Flex", "apiLevel": "2.19"}` + +Modules: + +- No modules + +Labware: + +- The source sample labware, a Biorad 96 well plate 200ul full skirt, is placed in slot D1. +- The source mastermix labware, an opentrons 24 tuberack nest 2ml snap cap, is placed on slot C1. +- The destination labware, an Opentrons Tough 96 Well Plate 200 uL PCR Full Skirt, is placed in slot B1. +- A 50 uL filter tip rack is used in slot A1. +- A 50 uL filter tip rack is used in slot A2. + +Pipette Mount: + +- Flex 1-Channel 50 uL Pipette is mounted on the right side +- Flex 8-Channel 50 uL Pipette is mounted on the left side + +Well Allocation: + +- For mastermix, the csv provided has source tube in the first column, and destination well in the second column. +- The number of sample columns is 3. + +Commands: + +- Using the single-chaneel pipette, for each row in the csv, aspirate 7ul of mastermix from the source tube in the tube rack (left column of csv) to the destination well (right column of csv) in the destination plate. Use one tip per mastermix tube. +- Using the multi-channel pipette, transfer 5ul of sample from the sample plate to the destination plate, column for column, up to the number of samples specified. Grab new tips for each column. + + + + + +```python +from opentrons import protocol_api + +requirements = { + 'robotType': 'Flex', + 'apiLevel': '2.19' +} + +def run(protocol: protocol_api.ProtocolContext): + csv_samp = """ + Primer Tube,Destination well + A1,A1 + B1,B1 + C1,C1 + D1,D1 + A2,E1 + B2,F1 + C2,G1 + D2,H1 + A3,A2 + B3,B2 + C3,C2 + D3,D2 + A4,E2 + B4,F2 + C4,G2 + D4,H2 + A5,A3 + B5,B3 + C5,C3 + D5,D3 + A6,E3 + B6,F3 + C6,G3 + D6,H3 + """ + + # Convert to list + csv_lines = [[val.strip() for val in line.split(',')] + for line in csv_samp.splitlines() + if line.split(',')[0].strip()][1:] + num_col = 3 + + # Load labware + mmx_tuberack = protocol.load_labware('opentrons_24_tuberack_nest_2ml_snapcap', 'C1') + dna_plate = protocol.load_labware('biorad_96_wellplate_200ul_pcr', 'D1') + dest_plate = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 'B1') + + tiprack_single = protocol.load_labware('opentrons_flex_96_tiprack_50ul', 'A1') + tiprack_multi = protocol.load_labware('opentrons_flex_96_tiprack_50ul', 'A2') + + # Load pipettes + single_pip = protocol.load_instrument("flex_1channel_50", 'right', tip_racks=[tiprack_single]) + multi_pip = protocol.load_instrument("flex_8channel_50", 'left', tip_racks=[tiprack_multi]) + + # Trash + trash = protocol.load_trash_bin("A3") + + # 1. Transfer mastermix + for source_tube, dest_well in csv_lines: + single_pip.pick_up_tip() + single_pip.transfer(7, source=mmx_tuberack[source_tube], dest=dest_plate[dest_well], new_tip='never') + single_pip.drop_tip() + + # 2. Transfer sample + for s, d in zip(dna_plate.rows()[0][:num_col], dest_plate.rows()[0][:num_col]): + multi_pip.pick_up_tip() + multi_pip.transfer(5, source=s, dest=d, new_tip='never') + multi_pip.drop_tip() + +``` + + + +## 5. PCR protocol + + +Write a protocol using the Opentrons Python Protocol API v2 for the Flex robot according to the following description: + +Requirements: + +- `requirements = {"robotType": "Flex", "apiLevel": "2.19"}` + +Modules: + +- Thermocycler module + +Labware: + +- The source sample labware, an biorad_96_wellplate_200ul_pcr, is placed in slot D1. +- The source mastermix labware, an opentrons 24 tuberack nest 2ml snap cap, is placed on slot C1. +- The destination labware, an opentrons_96_aluminumblock_nest_wellplate_100ul, is placed in thermocycler. +- A 50uL tip rack for the single channel pipette is in A2 +- A 50uL tip rack for the single channel pipette is in B2 + +Pipette Mount: + +- Flex 1-Channel 50 uL Pipette is mounted on the right side +- Flex 8-Channel 50 uL Pipette is mounted on the left side + +Well Allocation: + +- For mastermix, the csv provided has source tube in the first column, and destination well in the second column. +- The number of columns is 3. + +Commands: + +- Open the thermocycler lid. +- Set the thermocycler block temperature to 6C. +- Set the thermocycler lid temperature to 55C. +- For each row in the csv, aspirate 7ul of mastermix from the source tube in the tube rack (left column of csv) to the destination well (right column of csv) in the destination plate. Use one tip per mastermix tube. +- Using the multi-channel pipette, transfer 5ul of sample from the sample plate to the destination plate, column for column, up to the number of samples specified. +- Close the thermocycler lid. +- Execute the thermocycler with the following profile: + - 74C for 65 seconds for 1 cycle, block max volume is sample and mastermix volume +- Execute the thermocycler with the following profile for 13 cycles: + - 60C for 7 seconds, + - 84C for 19 seconds, + - 57C for 44 seconds, + block max volume is sample and mastermix volume. +- Execute the thermocycler with the following profile: + - 75C for 480 seconds for 1 cycle, block max volume is sample and mastermix volume +- Hold the thermocycler block at 4C. +- Open the thermocycler lid. + + + + +```python +from opentrons import protocol_api + +requirements = { + 'robotType': 'Flex', + 'apiLevel': '2.19' +} + + +def run(protocol: protocol_api.ProtocolContext): + csv_samp = """ + Primer Tube,Destination well + A1,A1 + B1,B1 + C1,C1 + D1,D1 + A2,E1 + B2,F1 + C2,G1 + D2,H1 + A3,A2 + B3,B2 + C3,C2 + D3,D2 + A4,E2 + B4,F2 + C4,G2 + D4,H2 + A5,A3 + B5,B3 + C5,C3 + D5,D3 + A6,E3 + B6,F3 + C6,G3 + D6,H3 + """ + # Convert to list + csv_lines = [[val.strip() for val in line.split(',')] + for line in csv_samp.splitlines() + if line.split(',')[0].strip()][1:] + + num_col = 3 + sample_volume_ul = 5 # in microliters + master_mix_volume_ul = 7 # in microliters + total_mix_volume_ul = sample_volume_ul + sample_volume_ul + + # Thermocycler parameters + lid_temperature_c = 55 # in Celsius + initial_plate_temperature_c = 6 # in Celsius + hold_temperature_c = 4 # in Celsius + + # Module + thermocycler_module = protocol.load_module('thermocyclerModuleV2') + + # Labware setup + mmx_tuberack = protocol.load_labware('opentrons_24_tuberack_nest_2ml_snapcap', 'C1') + dna_plate = protocol.load_labware('biorad_96_wellplate_200ul_pcr', 'D1') + dest_plate = thermocycler_module.load_labware('opentrons_96_aluminumblock_nest_wellplate_100ul') + + # Tip racks + tiprack_for_single = protocol.load_labware('opentrons_flex_96_tiprack_50ul', 'A2') + tiprack_for_multi = protocol.load_labware('opentrons_flex_96_tiprack_50ul', 'B2') + + # Load pipettes + single_pip = protocol.load_instrument("flex_1channel_50", 'right', tip_racks=[tiprack_for_single]) + multi_pip = protocol.load_instrument("flex_8channel_50", 'left', tip_racks=[tiprack_for_multi]) + + # Trash + trash = protocol.load_trash_bin("A3") + + # Open lid, set temperatures + thermocycler_module.open_lid() + thermocycler_module.set_block_temperature(initial_plate_temperature_c) + thermocycler_module.set_lid_temperature(lid_temperature_c) + + # Transfer mastermix + for source_tube, dest_well in csv_lines: + single_pip.pick_up_tip() + single_pip.transfer(master_mix_volume_ul, mmx_tuberack[source_tube], dest_plate[dest_well], new_tip='never') + single_pip.drop_tip() + + # Transfer samples + for s, d in zip(dna_plate.rows()[0][:num_col], dest_plate.rows()[0][:num_col]): + multi_pip.pick_up_tip() + multi_pip.transfer(sample_volume_ul, s, d, new_tip='never') + multi_pip.drop_tip() + + # PCR cycling + thermocycler_module.close_lid() + thermocycler_module.execute_profile( + steps=[ + {'temperature': 74, 'hold_time_seconds': 65} + ], + repetitions=1, + block_max_volume=total_mix_volume_ul + ) + + thermocycler_module.execute_profile( + steps=[ + {'temperature': 60, 'hold_time_seconds': 7}, + {'temperature': 84, 'hold_time_seconds': 19}, + {'temperature': 57, 'hold_time_seconds': 44} + ], + repetitions=13, + block_max_volume=total_mix_volume_ul + ) + + thermocycler_module.execute_profile( + steps=[{'temperature': 75, 'hold_time_seconds': 480}], + repetitions=1, + block_max_volume=total_mix_volume_ul + ) + # Thermo set temperature, open lid + thermocycler_module.set_block_temperature(hold_temperature_c) + thermocycler_module.open_lid() + +``` + + + +## 6. PCR protocol + + +Write a protocol using the Opentrons Python Protocol API v2 for the Flex robot according to the following description: + +Requirements: + +- `requirements = {"robotType": "Flex", "apiLevel": "2.15"}` + +Modules: + +- The thermocycler module + +Labware: + +- The source sample labware, an biorad_96_wellplate_200ul_pcr, is placed in slot D3. +- The source mastermix labware, an opentrons 24 tuberack nest 2ml snap cap, is placed on slot C1. +- The destination labware, an opentrons_96_aluminumblock_nest_wellplate_100ul, is placed in thermocycler. +- A 50uL tip rack for the single channel pipette is in A2 +- A 50uL tip rack for the single channel pipette is in B2 + +Pipette Mount: + +- Flex 1-Channel 50 uL Pipette is mounted on the right side +- Flex 8-Channel 50 uL Pipette is mounted on the left side + +Well Allocation: + +- The number of sample columns is 3. +- For mastermix, the csv provided has source tube in the first column, and destination well in the second column. + The mastermix volume for each destination well is also provided in each row of the csv, and is in the 3rd column. + +Commands: + +- Open the thermocycler lid. +- Set the thermocycler block temperature to 6C. +- Set the thermocycler lid temperature to 55C. +- For each row in the csv, there is the source tube, destination well, and transfer volume. Transfer the volume specified in the csv (3rd column of csv) of mastermix from the source tube in the tube rack (first column of csv) + to the destination well (second column of csv) in the destination plate. Use one tip per mastermix tube. +- Using the multi-channel pipette, aspirate 5ul of sample from the sample plate to the destination plate, column for column, up to the number of columns specified. +- Close the thermocycler lid. +- Execute the thermocycler with the following profile: + - 74C for 65 seconds for 1 cycle, block max volume is sample and mastermix volume +- Execute the thermocycler with the following profile: + - 60C for 7 seconds, + - 84C for 19 seconds, + - 57C for 44 seconds for 13 cycles, + block max volume is sample and mastermix volume +- Execute the thermocycler with the following profile: + - 75C for 480 seconds for 1 cycle, block max volume is sample and mastermix volume +- Hold the thermocycler block at 4C. +- Open the thermocycler lid. + + + + + +```python +from opentrons import protocol_api + +requirements = { + 'robotType': 'Flex', + 'apiLevel': '2.15' +} + + +def run(protocol: protocol_api.ProtocolContext): + + csv_samp = """ + Primer Tube,Destination well,Transfer volume + A1,A1,4 + B1,B1,4 + C1,C1,4 + D1,D1,4 + A2,E1,4 + B2,F1,4 + C2,G1,5 + D2,H1,5 + A3,A2,5 + B3,B2,5 + C3,C2,5 + D3,D2,5 + A4,E2,7 + B4,F2,7 + C4,G2,7 + D4,H2,7 + A5,A3,7 + B5,B3,3 + C5,C3,3 + D5,D3,4 + A6,E3,2 + B6,F3,8 + C6,G3,5 + D6,H3,20 + """ + # Convert to list + csv_lines = [[val.strip() for val in line.split(',')] + for line in csv_samp.splitlines() + if line.split(',')[0].strip()][1:] + num_col = 3 + sample_temperature_c = 4 # Temperature in Celsius + sample_volume_ul = 5 # Volume in microliters + total_mix_volume_ul = 10 + + # Thermocycler parameters + lid_temperature_c = 55 # Celsius + initial_plate_temperature_c = 6 # in Celsius + hold_temperature_c = 4 # in Celsius + + # Module + thermocycler_module = protocol.load_module('thermocyclerModuleV2') + + # Labware setup + tuberack = protocol.load_labware('opentrons_24_tuberack_nest_2ml_snapcap', 'C1') + dna_plate = protocol.load_labware('biorad_96_wellplate_200ul_pcr', 'D3') + dest_plate = thermocycler_module.load_labware('opentrons_96_aluminumblock_nest_wellplate_100ul') + + # Tip racks + tiprack_for_single = protocol.load_labware('opentrons_flex_96_tiprack_50ul', 'A2') + tiprack_for_multi = protocol.load_labware('opentrons_flex_96_tiprack_50ul', "B2") + + # Load pipette + single_pip = protocol.load_instrument("flex_1channel_50", 'right', tip_racks=[tiprack_for_single]) + multi_pip = protocol.load_instrument("flex_8channel_50", 'left', tip_racks=[tiprack_for_multi]) + + thermocycler_module.open_lid() + thermocycler_module.set_block_temperature(initial_plate_temperature_c) + thermocycler_module.set_lid_temperature(lid_temperature_c) + for source_tube, dest_well, transfer_vol in csv_lines: + single_pip.pick_up_tip() + single_pip.transfer(int(transfer_vol), tuberack[source_tube], dest_plate[dest_well], new_tip='never') + single_pip.drop_tip() + + for s, d in zip(dna_plate.rows()[0][:num_col], dest_plate.rows()[0][:num_col]): + multi_pip.pick_up_tip() + multi_pip.transfer(sample_volume_ul, s, d, new_tip='never') + multi_pip.drop_tip() + + # PCR cycling + thermocycler_module.close_lid() + thermocycler_module.execute_profile( + steps=[ + {'temperature': 74, 'hold_time_seconds': 65} + ], + repetitions=1, + block_max_volume=total_mix_volume_ul + ) + + thermocycler_module.execute_profile( + steps=[ + {'temperature': 60, 'hold_time_seconds': 7}, + {'temperature': 84, 'hold_time_seconds': 19}, + {'temperature': 57, 'hold_time_seconds': 44} + ], + repetitions=13, + block_max_volume=total_mix_volume_ul + ) + + thermocycler_module.execute_profile( + steps=[{'temperature': 75, 'hold_time_seconds': 480}], + repetitions=1, + block_max_volume=total_mix_volume_ul + ) + + thermocycler_module.set_block_temperature(hold_temperature_c) + thermocycler_module.open_lid() + +``` + + + +## 7. PCR protocol + + +Write a protocol using the Opentrons Python Protocol API v2 for the Flex robot according to the following description: + +Requirements: + +- `requirements = {"robotType": "Flex", "apiLevel": "2.15"}` + +Modules: + +- No modules + +Labware: + +- The source sample labware, an Opentrons Tough 96 Well Plate 200 uL PCR Full Skirt, is placed in slot D3. +- The source mastermix labware, an opentrons 24 tuberack nest 2ml snap cap, is placed on slot C1. +- The destination labware, an Opentrons Tough 96 Well Plate 200 uL PCR Full Skirt, is placed in slot B1. +- Tiprack 1: opentrons_flex_96_tiprack_50ul is in A2 +- Tiprack 2: opentrons_flex_96_tiprack_50ul is in B2 + +Pipette Mount: + +- Flex 1-Channel 50 uL Pipette is mounted on the right side +- Flex 8-Channel 50 uL Pipette is mounted on the left side + +Well Allocation: + +- For mastermix, the csv provided has source tube in the first column, and destination well in the second column. +- 3 columns of samples. + +Commands: + +- For each row in the csv, transfer 7ul of mastermix from the source tube in the tube rack (left column of csv) to the destination well (right column of csv) in the destination plate. Use one tip per mastermix tube. +- For each column in the source plate, we are going to the destination plate in duplicate, changing tips between each column. For example, using the multi-channel pipette, transfer 3ul of sample from the sample plate column 1 to the destination plate plate column 1, change tip, then aspirate from sample plate column 1 to destination plate column 2. Then, transfer 3ul of sample from the sample plate column 2 to the destination plate plate column 3, change tip, then transfer from sample plate column 2 to destination plate column 4. Repeat this pattern for the remainder of the source columns + + + + + +```python +from opentrons import protocol_api + +requirements = { + 'robotType': 'Flex', + 'apiLevel': '2.15' +} + +def run(protocol: protocol_api.ProtocolContext): + csv_samp = """ + Primer Tube,Destination well + A1,A1 + B1,B1 + C1,C1 + D1,D1 + A2,E1 + B2,F1 + C2,G1 + D2,H1 + A3,A2 + B3,B2 + C3,C2 + D3,D2 + A4,E2 + B4,F2 + C4,G2 + D4,H2 + A5,A3 + B5,B3 + C5,C3 + D5,D3 + A6,E3 + B6,F3 + C6,G3 + D6,H3 + """ + # Convert to nested list + csv_lines = [[val.strip() for val in line.split(',')] + for line in csv_samp.splitlines() + if line.split(',')[0].strip()][1:] + + NUM_COL = 3 + STRIDE = 2 + + # Load labware + tuberack = protocol.load_labware('opentrons_24_tuberack_nest_2ml_snapcap', 'C1') + dna_plate = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 'D3') + dest_plate = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 'B1') + + tiprack_single = protocol.load_labware('opentrons_flex_96_tiprack_50ul', 'A2') + tiprack_multi = protocol.load_labware('opentrons_flex_96_tiprack_50ul', 'B2') + + # Load pipette + single_pip = protocol.load_instrument("flex_1channel_50", 'right', tip_racks=[tiprack_single]) + multi_pip = protocol.load_instrument("flex_8channel_50", 'left', tip_racks=[tiprack_multi]) + + # Transfer mastermix + for source_tube, dest_well in csv_lines: + single_pip.pick_up_tip() + single_pip.transfer(7, tuberack[source_tube], dest_plate[dest_well], new_tip='never') + single_pip.drop_tip() + + # transfer in duplicate + col_ctr = 0 + for s in dna_plate.rows()[0][:NUM_COL]: + multi_pip.pick_up_tip() + multi_pip.transfer(3, s, dest_plate.rows()[0][col_ctr], new_tip='never') + multi_pip.drop_tip() + + multi_pip.pick_up_tip() + multi_pip.transfer(3, s, dest_plate.rows()[0][col_ctr+1], new_tip='never') + multi_pip.drop_tip() + + col_ctr += STRIDE + +``` + + + +## 8. PCR protocol + + +Write a protocol using the Opentrons Python Protocol API v2 for the Flex robot according to the following description: + +Requirements: + +- `requirements = {"robotType": "Flex", "apiLevel": "2.15"}` + +Modules: + +- No modules + +Labware: + +- The source sample labware, an Opentrons Tough 96 Well Plate 200 uL PCR Full Skirt, is placed in slot D3. +- The source mastermix labware, an opentrons 24 tuberack nest 2ml snap cap, is placed on slot C1. +- The destination labware, an Opentrons Tough 96 Well Plate 200 uL PCR Full Skirt, is placed in slot D1. +- Tiprack in A2: opentrons_flex_96_tiprack_50ul +- Tiprack in B2: opentrons_flex_96_tiprack_50ul + +Pipette Mount: + +- Flex 1-Channel 50 uL Pipette is mounted on the right side +- Flex 8-Channel 50 uL Pipette is mounted on the left side + +Well Allocation: + +- For mastermix, the csv provided has source tube in the first column, and destination well in the second column. +- 3 columns of samples. + +Commands: + +- For each row in the csv, aspirate 7 ul of mastermix from the source tube in the tube rack (left column of csv) to the destination well (right column of csv) in the destination plate. Use one tip per mastermix tube. +- For each column in the source plate, we are going to the destination plate in triplicate, changing tips between each column. For example, using the multi-channel pipette, + transfer 3 ul of sample from the sample plate column 1 to the destination plate plate column 1, change tip, then aspirate from sample plate column 1 to destination plate column 2, change tip, then aspirate form sample plate column 1 to destination plate column 3. Repeat this pattern for the remainder of the source columns + + + + + +```python +from opentrons import protocol_api + +requirements = { + 'robotType': 'Flex', + 'apiLevel': '2.15' +} + + +def run(protocol: protocol_api.ProtocolContext): + + csv_samp = """ + Primer Tube,Destination well + A1,A1 + B1,B1 + C1,C1 + D1,D1 + A2,E1 + B2,F1 + C2,G1 + D2,H1 + A3,A2 + B3,B2 + C3,C2 + D3,D2 + A4,E2 + B4,F2 + C4,G2 + D4,H2 + A5,A3 + B5,B3 + C5,C3 + D5,D3 + A6,E3 + B6,F3 + C6,G3 + D6,H3 + """ + # Convert to list + csv_lines = [[val.strip() for val in line.split(',')] + for line in csv_samp.splitlines() + if line.split(',')[0].strip()][1:] + + NUM_COL = 3 + STRIDE = 3 + + # Load labware + tuberack = protocol.load_labware('opentrons_24_tuberack_nest_2ml_snapcap', 'C1') + dna_plate = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 'D3') + dest_plate = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 'D1') + + tiprack_single = protocol.load_labware('opentrons_flex_96_tiprack_50ul', 'A2') + tiprack_multi = protocol.load_labware('opentrons_flex_96_tiprack_50ul', 'B2') + + # Load pipette + single_pip = protocol.load_instrument("flex_1channel_50", 'right', tip_racks=[tiprack_single]) + multi_pip = protocol.load_instrument("flex_8channel_50", 'left', tip_racks=[tiprack_multi]) + + # transfer mastermix + for source_tube, dest_well in csv_lines: + single_pip.pick_up_tip() + single_pip.transfer(7, tuberack[source_tube], dest_plate[dest_well], new_tip='never') + single_pip.drop_tip() + + # Transfer in triplicate + col_ctr = 0 + for s in dna_plate.rows()[0][:NUM_COL]: + multi_pip.pick_up_tip() + multi_pip.transfer(3, s, dest_plate.rows()[0][col_ctr], new_tip='never') + multi_pip.drop_tip() + + multi_pip.pick_up_tip() + multi_pip.transfer(3, s, dest_plate.rows()[0][col_ctr+1], new_tip='never') + multi_pip.drop_tip() + + multi_pip.pick_up_tip() + multi_pip.transfer(3, s, dest_plate.rows()[0][col_ctr+2], new_tip='never') + multi_pip.drop_tip() + + col_ctr += STRIDE +``` + + diff --git a/opentrons-ai-server/api/storage/docs/serial_dilution_samples.md b/opentrons-ai-server/api/storage/docs/serial_dilution_samples.md new file mode 100644 index 00000000000..ad9a5ee24be --- /dev/null +++ b/opentrons-ai-server/api/storage/docs/serial_dilution_samples.md @@ -0,0 +1,842 @@ +# Serial dilution examples + +## 1. Serial dilution example + + +Write a protocol using the Opentrons Python Protocol API v2 for Flex robot for the following description: + +Metadata: + +- Author: New API User +- ProtocolName: Serial Dilution Tutorial – Flex 1-channel +- Description: serial dilution + +Requirements: + +- requirements = {"robotType": "Flex", "apiLevel": "2.16"} + +Labware: + +- Tiprack: `Opentrons Flex 96 Tip Rack 200 µL` in slot D1 +- Reservoir: `NEST 12 Well Reservoir 15 mL` in slot D2 +- Plate: `NEST 96 Well Plate 200 µL Flat` in slot D3 +- Trash bin in slot A3 + +Pipette mount: + +- Flex 1-channel 1000 µL pipette is mounted on the left + +Commands: + +1. Use the left-mounted Flex 1-channel 1000 µL pipette to distribute 100 µL of diluent from well A1 of the reservoir to all wells of the plate. +2. For each of the 8 rows in the plate: + a. Transfer 100 µL of solution from well A2 of the reservoir to the first well of the row, mixing 3 times with 50 µL after transfer. + b. Perform a serial dilution by transferring 100 µL from each well to the next well in the row (from left to right), for a total of 11 transfers. Mix 3 times with 50 µL after each transfer. + + + + +```python +from opentrons import protocol_api + +metadata = { + "protocolName": "Serial Dilution Tutorial – Flex 1-channel", + "description": """serial dilution""", + "author": "New API User" + } + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16" + } + +def run(protocol: protocol_api.ProtocolContext): + tips = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "D1") + reservoir = protocol.load_labware("nest_12_reservoir_15ml", "D2") + plate = protocol.load_labware("nest_96_wellplate_200ul_flat", "D3") + trash = protocol.load_trash_bin("A3") + left_pipette = protocol.load_instrument("flex_1channel_1000", "left", tip_racks=[tips]) + + # distribute diluent + left_pipette.transfer(100, reservoir["A1"], plate.wells()) + + # loop through each row + for i in range(8): + + # save the destination row to a variable + row = plate.rows()[i] + + # transfer solution to first well in column + left_pipette.transfer(100, reservoir["A2"], row[0], mix_after=(3, 50)) + + # dilute the sample down the row + left_pipette.transfer(100, row[:11], row[1:], mix_after=(3, 50)) +``` + + + +## 2. Serial dilution example + + + +serial&heater-shaker +serial&heater-shaker +100% +10 +A3 + +Write a protocol using the Opentrons Python Protocol API v2 for Flex robot for the following description: + +Metadata: + +- Author: New API User +- ProtocolName: Serial Dilution Tutorial – Flex 8-channel +- Description: This protocol is the outcome of following the Python Protocol API Tutorial located at https://docs.opentrons.com/v2/tutorial.html. It takes a solution and progressively dilutes it by transferring it stepwise across a plate. + +Requirements: + +- requirements = {"robotType": "Flex", "apiLevel": "2.16"} + +Labware: + +- Tiprack: `Opentrons 96 Tip Rack 300 µL` in slot D1 +- Reservoir: `NEST 12 Well Reservoir 15 mL` in slot D2 +- Plate: `NEST 96 Well Plate 200 µL Flat` in slot D3 +- Trash bin in slot A3 + +Pipette mount: + +- Flex 8-channel 1000 µL pipette is mounted on the right + +Commands: + +1. Use the right-mounted Flex 8-channel 1000 µL pipette to distribute 100 µL of diluent from well A1 of the reservoir to the first row of the plate. +2. Transfer 100 µL of solution from well A2 of the reservoir to the first column of the first row in the plate, mixing 3 times with 50 µL after transfer. +3. Perform a serial dilution by transferring 100 µL from each column to the next column in the row (from left to right), for a total of 11 transfers. Mix 3 times with 50 µL after each transfer. + +Write a protocol using the Opentrons Python Protocol API v2 for Flex robot for the following description: + +Metadata: + +- Author: New API User +- ProtocolName: Serial Dilution Tutorial – Flex 8-channel +- Description: This protocol is the outcome of following the Python Protocol API Tutorial located at https://docs.opentrons.com/v2/tutorial.html. It takes a solution and progressively dilutes it by transferring it stepwise across a plate. + +Requirements: + +- requirements = {"robotType": "Flex", "apiLevel": "2.16"} + +Labware: + +- Tiprack: `Opentrons 96 Tip Rack 300 µL` in slot D1 +- Reservoir: `NEST 12 Well Reservoir 15 mL` in slot D2 +- Plate: `NEST 96 Well Plate 200 µL Flat` in slot D3 +- Trash bin in slot A3 + +Pipette mount: + +- Flex 8-channel 1000 µL pipette is mounted on the right + +Commands: + +1. Use the right-mounted Flex 8-channel 1000 µL pipette to distribute 100 µL of diluent from well A1 of the reservoir to the first row of the plate. +2. Transfer 100 µL of solution from well A2 of the reservoir to the first column of the first row in the plate, mixing 3 times with 50 µL after transfer. +3. Perform a serial dilution by transferring 100 µL from each column to the next column in the row (from left to right), for a total of 11 transfers. Mix 3 times with 50 µL after each transfer. + Turn on screen reader support + To enable screen reader support, press ⌘+Option+Z To learn about keyboard shortcuts, press ⌘slash + + + + +```python +from opentrons import protocol_api + +metadata = { + "protocolName": "Serial Dilution Tutorial – Flex 8-channel", + "description": """This protocol is the outcome of following the + Python Protocol API Tutorial located at + https://docs.opentrons.com/v2/tutorial.html. It takes a + solution and progressively dilutes it by transferring it + stepwise across a plate.""", + "author": "New API User" + } + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16" + } + +def run(protocol: protocol_api.ProtocolContext): + tips = protocol.load_labware("opentrons_96_tiprack_300ul", "D1") + reservoir = protocol.load_labware("nest_12_reservoir_15ml", "D2") + plate = protocol.load_labware("nest_96_wellplate_200ul_flat", "D3") + trash = protocol.load_trash_bin("A3") + left_pipette = protocol.load_instrument("flex_8channel_1000", "right", tip_racks=[tips]) + + # distribute diluent + left_pipette.transfer(100, reservoir["A1"], plate.rows()[0]) + + # no loop, 8-channel pipette + + # save the destination row to a variable + row = plate.rows()[0] + + # transfer solution to first well in column + left_pipette.transfer(100, reservoir["A2"], row[0], mix_after=(3, 50)) + + # dilute the sample down the row + left_pipette.transfer(100, row[:11], row[1:], mix_after=(3, 50)) +``` + + + +## 3. Serial dilution example + + + +Write a protocol using the Opentrons Python Protocol API v2 for OT-2 robot for the following description: + +Metadata: + +- Author: New API User +- ProtocolName: Serial Dilution Tutorial – OT-2 single-channel +- Description: This protocol is the outcome of following the Python Protocol API Tutorial located at https://docs.opentrons.com/v2/tutorial.html. It takes a solution and progressively dilutes it by transferring it stepwise across a plate. +- apiLevel: 2.16 + +Requirements: + +- robotType: OT-2 +- apiLevel: 2.16 + +Labware: + +- Tiprack: `Opentrons 96 Tip Rack 300 µL` in slot 1 +- Reservoir: `NEST 12 Well Reservoir 15 mL` in slot 2 +- Plate: `NEST 96 Well Plate 200 µL Flat` in slot 3 + +Pipette mount: + +- P300 Single-Channel GEN2 pipette is mounted on the left + +Commands: + +1. Use the left-mounted P300 Single-Channel GEN2 pipette to distribute 100 µL of diluent from well A1 of the reservoir to all wells of the plate. +2. For each of the 8 rows in the plate: + a. Transfer 100 µL of solution from well A2 of the reservoir to the first well of the row, mixing 3 times with 50 µL after transfer. + b. Perform a serial dilution by transferring 100 µL from each well to the next well in the row (from left to right), for a total of 11 transfers. Mix 3 times with 50 µL after each transfer. + + + + + +```python +from opentrons import protocol_api + +metadata = { + "apiLevel": "2.16", + "protocolName": "Serial Dilution Tutorial – OT-2 single-channel", + "description": """This protocol is the outcome of following the + Python Protocol API Tutorial located at + https://docs.opentrons.com/v2/tutorial.html. It takes a + solution and progressively dilutes it by transferring it + stepwise across a plate.""", + "author": "New API User" + } + +def run(protocol: protocol_api.ProtocolContext): + tips = protocol.load_labware("opentrons_96_tiprack_300ul", 1) + reservoir = protocol.load_labware("nest_12_reservoir_15ml", 2) + plate = protocol.load_labware("nest_96_wellplate_200ul_flat", 3) + left_pipette = protocol.load_instrument("p300_single_gen2", "left", tip_racks=[tips]) + + # distribute diluent + left_pipette.transfer(100, reservoir["A1"], plate.wells()) + + # loop through each row + for i in range(8): + + # save the destination row to a variable + row = plate.rows()[i] + + # transfer solution to first well in column + left_pipette.transfer(100, reservoir["A2"], row[0], mix_after=(3, 50)) + + # dilute the sample down the row + left_pipette.transfer(100, row[:11], row[1:], mix_after=(3, 50)) +``` + + + +## 4. Serial dilution example + + +Write a protocol using the Opentrons Python Protocol API v2 for OT-2 robot for the following description: + +Metadata: + +- Author: New API User +- ProtocolName: Serial Dilution Tutorial – OT-2 8-channel +- Description: This protocol is the outcome of following the Python Protocol API Tutorial located at https://docs.opentrons.com/v2/tutorial.html. It takes a solution and progressively dilutes it by transferring it stepwise across a plate. +- apiLevel: 2.16 + +Requirements: + +- robotType: OT-2 +- apiLevel: 2.16 + +Labware: + +- Tiprack: `Opentrons 96 Tip Rack 300 µL` in slot 1 +- Reservoir: `NEST 12 Well Reservoir 15 mL` in slot 2 +- Plate: `NEST 96 Well Plate 200 µL Flat` in slot 3 + +Pipette mount: + +- P300 8-Channel GEN2 pipette is mounted on the right + +Commands: + +1. Use the right-mounted P300 8-Channel GEN2 pipette to distribute 100 µL of diluent from well A1 of the reservoir to the first row of the plate. +2. Transfer 100 µL of solution from well A2 of the reservoir to the first column of the plate (row A), mixing 3 times with 50 µL after transfer. +3. Perform a serial dilution by transferring 100 µL from each column to the next column in the row (from left to right), for a total of 11 transfers across the plate. Mix 3 times with 50 µL after each transfer. + + + + +```python +from opentrons import protocol_api + +metadata = { + "apiLevel": "2.16", + "protocolName": "Serial Dilution Tutorial – OT-2 8-channel", + "description": """This protocol is the outcome of following the + Python Protocol API Tutorial located at + https://docs.opentrons.com/v2/tutorial.html. It takes a + solution and progressively dilutes it by transferring it + stepwise across a plate.""", + "author": "New API User" + } + +def run(protocol: protocol_api.ProtocolContext): + tips = protocol.load_labware("opentrons_96_tiprack_300ul", 1) + reservoir = protocol.load_labware("nest_12_reservoir_15ml", 2) + plate = protocol.load_labware("nest_96_wellplate_200ul_flat", 3) + left_pipette = protocol.load_instrument("p300_multi_gen2", "right", tip_racks=[tips]) + + # distribute diluent + left_pipette.transfer(100, reservoir["A1"], plate.rows()[0]) + + # no loop, 8-channel pipette + + # save the destination row to a variable + row = plate.rows()[0] + + # transfer solution to first well in column + left_pipette.transfer(100, reservoir["A2"], row[0], mix_after=(3, 50)) + + # dilute the sample down the row + left_pipette.transfer(100, row[:11], row[1:], mix_after=(3, 50)) +``` + + + +## 5. Serial dilution example + + +Write a protocol using the Opentrons Python Protocol API v2 for OT-2 robot for the following description: + +Metadata: + +- Author: John C. Lynch +- ProtocolName: Serial Dilution for Eskil +- Description: Execute serial dilution protocol +- Source: Custom Protocol Request +- API Level: 2.19 + +Requirements: + +- requirements = {"robotType": "OT-2", "apiLevel": "2.19"} + +Constants: + +- Plate type: Opentrons 96 Aluminum Block NEST Well Plate 100 µL +- Dilution factor: 1.5 +- Number of dilutions: 10 +- Total mixing volume: 150 uL + +Modules: + +- Temperature module loaded in slot 4 + +Labware: + +- NEST 12 Well Reservoir 15 mL in slot 1 +- Dilution plate (Opentrons 96 Aluminum Block NEST Well Plate 100 uL) loaded on temperature module +- Two Opentrons 96 Tip Racks 300 uL in slots 2 and 3 + +Pipette Mount: + +- P300 Multi-Channel GEN2 pipette mounted on the left side + +Calculations: + +- Transfer volume = Total mixing volume / Dilution factor +- Diluent volume = Total mixing volume - Transfer volume + +Commands: + +1. Using the P300 Multi-Channel pipette, transfer diluent from the first well of the reservoir to wells 2-10 of the first row of the dilution plate: + + - Volume: calculated diluent volume + - Use a 10 uL air gap + - Use new tips for each transfer + +2. Perform serial dilutions across the first row of the dilution plate: + + - For each pair of adjacent wells (from well 1 to well 9, transferring to wells 2 to 10): + - Transfer the calculated transfer volume + - Use a 10 uL air gap + - After each transfer, mix 5 times with (Total mixing volume - 5 uL) + - Use new tips for each transfer + +3. Add blank to the last well: + - Transfer calculated diluent volume from the first reservoir well to the last well of the first row in the dilution plate + - Use a 10 uL air gap + - Use a new tip + + + + +```python +metadata = { + 'protocolName': 'Serial Dilution for Eskil', + 'author': 'John C. Lynch', + 'source': 'Custom Protocol Request', + 'apiLevel': '2.19' +} + +def run(protocol): + + # Constants + PLATE_TYPE = 'opentrons_96_aluminumblock_nest_wellplate_100ul' + DILUTION_FACTOR = 1.5 + NUM_DILUTIONS = 10 + TOTAL_MIXING_VOLUME = 150 + + # Calculated volumes + transfer_volume = TOTAL_MIXING_VOLUME / DILUTION_FACTOR + diluent_volume = TOTAL_MIXING_VOLUME - transfer_volume + + # Load temperature module and labware + temp_module = protocol.load_module('temperature module gen2', '4') + reservoir = protocol.load_labware('nest_12_reservoir_15ml', '1') + dilution_plate = temp_module.load_labware(PLATE_TYPE) + + # Load tipracks + tipracks = [ + protocol.load_labware('opentrons_96_tiprack_300ul', slot) + for slot in ['2', '3'] + ] + + # Load pipette + pipette = protocol.load_instrument( + 'p300_multi_gen2', + mount='left', + tip_racks=tipracks + ) + + # 1. Distribute diluent + pipette.transfer( + diluent_volume, + reservoir.wells()[0], + dilution_plate.rows()[0][1:NUM_DILUTIONS], + air_gap=10, + new_tip='always' + ) + + # 2. Perform serial dilutions + sources = dilution_plate.rows()[0][:NUM_DILUTIONS-1] + dests = dilution_plate.rows()[0][1:NUM_DILUTIONS] + + pipette.transfer( + transfer_volume, + sources, + dests, + air_gap=10, + mix_after=(5, TOTAL_MIXING_VOLUME-5), + new_tip='always' + ) + + # 3. Add blank + pipette.transfer( + diluent_volume, + reservoir.wells()[0], + dilution_plate.rows()[0][-1], + air_gap=10, + new_tip='always' + ) +``` + + + +## 6. Serial dilution example + + +Write a protocol using the Opentrons Python Protocol API v2 for Flex robot for the following description: + +Metadata: + +- Author: Opentrons +- ProtocolName: Customizable Serial Dilution +- Source: Protocol Library + +Requirements: + +- requirements = {"robotType": "Flex", "apiLevel": "2.19"} + +Inside the run function: + +Constants: + +- Dilution factor: 3 +- Number of dilutions: 10 +- Total mixing volume: 150.0 µL +- Air gap volume: 10 µL + +Calculations: + +- Transfer volume = Total mixing volume / Dilution factor +- Diluent volume = Total mixing volume - Transfer volume + +Labware: + +- NEST 12 Well Reservoir 15 mL in slot D2 +- NEST 96 Well Plate 200 µL Flat in slot D3 +- Two Opentrons Flex 96 Filter Tip Racks 1000 µL in slots C1 and D1 +- Trash bin in slot A3 + +Pipette Mount: + +- Flex 1-channel 1000 µL pipette mounted on the right side + +Liquid Definitions: + +- Diluent liquid: Green color (#33FF33), loaded in reservoir at 80% max volume +- Sample liquid: Red color (#FF0000), loaded in first column of plate at 150 µL per well + +Commands: + +1. Distribute diluent to dilution plate: + + - Pick up one tip + - Transfer calculated diluent volume from reservoir to all wells in columns 2-11 + - Use 10 µL air gap for each transfer + - Reuse the same tip for all transfers + - Drop tip after completion + +2. Perform serial dilutions: + + - Pick up one tip + - For each well in columns 1-10 (source) to columns 2-11 (destination): + - Transfer calculated transfer volume + - Use 10 µL air gap + - After each transfer, mix 5 times with half the total mixing volume + - Reuse the same tip for all transfers + - Drop tip after completion + +3. Add blank to last column: + - Pick up one tip + - Transfer calculated diluent volume from reservoir to all wells in column 12 + - Use 10 µL air gap + - Reuse the same tip for all transfers + - Drop tip after completion + + + + +```python +metadata = { + 'protocolName': 'Customizable Serial Dilution', + 'author': 'Opentrons ', + 'source': 'Protocol Library' +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.19" +} + +def run(protocol): + + # Constants + DILUTION_FACTOR = 3 + NUM_DILUTIONS = 10 + TOTAL_MIXING_VOLUME = 150.0 + AIR_GAP_VOLUME = 10 + + # Calculated volumes + transfer_volume = TOTAL_MIXING_VOLUME / DILUTION_FACTOR + diluent_volume = TOTAL_MIXING_VOLUME - transfer_volume + + # Labware setup + trough = protocol.load_labware('nest_12_reservoir_15ml', 'D2') + plate = protocol.load_labware('nest_96_wellplate_200ul_flat', 'D3') + tip_name = "opentrons_flex_96_filtertiprack_1000ul" + tipracks = [ + protocol.load_labware(tip_name, slot) + for slot in ["C1", "D1"] + ] + + # Pipette setup + pipette = protocol.load_instrument('flex_1channel_1000', 'right', tipracks) + + # Waste setup + trash = protocol.load_trash_bin("A3") + + # Reagent setup + diluent = trough.wells()[0] + source = plate.columns()[0] + + # Define and load liquids + diluent_liquid = protocol.define_liquid( + name="Dilutent", + description="Diluent liquid is filled in the reservoir", + display_color="#33FF33" + ) + sample_liquid = protocol.define_liquid( + name="Sample", + description="Non-diluted samples are loaded in the 1st column", + display_color="#FF0000" + ) + + diluent.load_liquid(liquid=diluent_liquid, volume=0.8 * diluent.max_volume) + for well in source: + well.load_liquid(liquid=sample_liquid, volume=TOTAL_MIXING_VOLUME) + + # Set up dilution destinations + dilution_destination_sets = plate.columns()[1:NUM_DILUTIONS+1] + dilution_source_sets = plate.columns()[:NUM_DILUTIONS] + blank_set = plate.columns()[NUM_DILUTIONS+1] + + # 1. Distribute diluent + all_diluent_destinations = [well for wells in dilution_destination_sets for well in wells] + pipette.pick_up_tip() + for dest in all_diluent_destinations: + pipette.transfer( + diluent_volume, + diluent, + dest, + air_gap=AIR_GAP_VOLUME, + new_tip='never' + ) + pipette.drop_tip() + + # 2. Perform serial dilutions + pipette.pick_up_tip() + for source_set, dest_set in zip(dilution_source_sets, dilution_destination_sets): + for s, d in zip(source_set, dest_set): + pipette.transfer( + transfer_volume, + s, + d, + air_gap=AIR_GAP_VOLUME, + mix_after=(5, TOTAL_MIXING_VOLUME/2), + new_tip='never' + ) + pipette.drop_tip() + + # 3. Add blank + pipette.pick_up_tip() + for blank_well in blank_set: + pipette.transfer( + diluent_volume, + diluent, + blank_well, + air_gap=AIR_GAP_VOLUME, + new_tip='never' + ) + pipette.drop_tip() +``` + + + +## 7. Serial dilution example + + +Write a protocol using the Opentrons Python Protocol API v2 for Flex robot for the following description: + +Metadata: + +- Author: Opentrons +- ProtocolName: Customizable Serial Dilution +- Source: Protocol Library + +Requirements: + +- requirements = {"robotType": "Flex", "apiLevel": "2.19"} + +Inside the run function: + +Constants: + +- Dilution factor: 3 +- Number of dilutions: 10 +- Total mixing volume: 150.0 µL +- Air gap volume: 10 µL + +Calculations: + +- Transfer volume = Total mixing volume / Dilution factor +- Diluent volume = Total mixing volume - Transfer volume + +Labware: + +- NEST 12 Well Reservoir 15 mL in slot D2 +- NEST 96 Well Plate 200 µL Flat in slot D3 +- Two Opentrons Flex 96 Filter Tip Racks 1000 µL in slots C1 and D1 +- Trash bin in slot A3 + +Pipette Mount: + +- Flex 8-channel 1000 µL pipette mounted on the right side + +Liquid Definitions: + +- Diluent liquid: Green color (#33FF33), loaded in reservoir at 80% max volume +- Sample liquid: Red color (#FF0000), loaded in first column of plate at 150 µL per well + +Commands: + +1. Distribute diluent to dilution plate: + + - Pick up one tip with 8-channel pipette + - Transfer calculated diluent volume from reservoir to wells 2-11 in row A + - Use 10 µL air gap for each transfer + - Reuse the same tip for all transfers + - Drop tip after completion + +2. Perform serial dilutions along row A: + + - Pick up one tip with 8-channel pipette + - For each well pair from A1 to A10 (source) to A2 to A11 (destination): + - Transfer calculated transfer volume + - Use 10 µL air gap + - After each transfer, mix 5 times with half the total mixing volume + - Reuse the same tip for all transfers + - Drop tip after completion + +3. Add blank to well A12: + - Pick up one tip with 8-channel pipette + - Transfer calculated diluent volume from reservoir to well A12 + - Use 10 µL air gap + - Reuse the same tip + - Drop tip after completion + + + + +```python +metadata = { + 'protocolName': 'Customizable Serial Dilution', + 'author': 'Opentrons ', + 'source': 'Protocol Library' +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.19" +} + +def run(protocol): + + # Constants + DILUTION_FACTOR = 3 + NUM_DILUTIONS = 10 + TOTAL_MIXING_VOLUME = 150.0 + AIR_GAP_VOLUME = 10 + + # Calculated volumes + transfer_volume = TOTAL_MIXING_VOLUME / DILUTION_FACTOR + diluent_volume = TOTAL_MIXING_VOLUME - transfer_volume + + # Labware setup + trough = protocol.load_labware('nest_12_reservoir_15ml', 'D2') + plate = protocol.load_labware('nest_96_wellplate_200ul_flat', 'D3') + tip_name = "opentrons_flex_96_filtertiprack_1000ul" + tipracks = [ + protocol.load_labware(tip_name, slot) + for slot in ["C1", "D1"] + ] + + # Pipette setup + pipette = protocol.load_instrument('flex_8channel_1000', 'right', tipracks) + + # Waste setup + trash = protocol.load_trash_bin("A3") + + # Reagent setup + diluent = trough.wells()[0] + source = plate.columns()[0] + + # Define and load liquids + diluent_liquid = protocol.define_liquid( + name="Dilutent", + description="Diluent liquid is filled in the reservoir", + display_color="#33FF33" + ) + sample_liquid = protocol.define_liquid( + name="Sample", + description="Non-diluted samples are loaded in the 1st column", + display_color="#FF0000" + ) + + diluent.load_liquid(liquid=diluent_liquid, volume=0.8 * diluent.max_volume) + for well in source: + well.load_liquid(liquid=sample_liquid, volume=TOTAL_MIXING_VOLUME) + + # Set up dilution destinations + dilution_destination_sets = [[row] for row in plate.rows()[0][1:NUM_DILUTIONS+1]] + dilution_source_sets = [[row] for row in plate.rows()[0][:NUM_DILUTIONS]] + blank_set = [plate.rows()[0][NUM_DILUTIONS+1]] + + # 1. Distribute diluent + all_diluent_destinations = [well for wells in dilution_destination_sets for well in wells] + pipette.pick_up_tip() + for dest in all_diluent_destinations: + pipette.transfer( + diluent_volume, + diluent, + dest, + air_gap=AIR_GAP_VOLUME, + new_tip='never' + ) + pipette.drop_tip() + + # 2. Perform serial dilutions + pipette.pick_up_tip() + for source_set, dest_set in zip(dilution_source_sets, dilution_destination_sets): + for s, d in zip(source_set, dest_set): + pipette.transfer( + transfer_volume, + s, + d, + air_gap=AIR_GAP_VOLUME, + mix_after=(5, TOTAL_MIXING_VOLUME/2), + new_tip='never' + ) + pipette.drop_tip() + + # 3. Add blank + pipette.pick_up_tip() + for blank_well in blank_set: + pipette.transfer( + diluent_volume, + diluent, + blank_well, + air_gap=AIR_GAP_VOLUME, + new_tip='never' + ) + pipette.drop_tip() +``` + + diff --git a/opentrons-ai-server/api/storage/docs/standard-api-v0.0.1.md b/opentrons-ai-server/api/storage/docs/standard-api-v0.0.1.md new file mode 100644 index 00000000000..f4b54d4308a --- /dev/null +++ b/opentrons-ai-server/api/storage/docs/standard-api-v0.0.1.md @@ -0,0 +1,157 @@ +Standard API + +### Approved Pipette Loadnames + +Note that the labware names are hard to differentiate sometimes, +since there are cases that they differ in terms of last digits only. + +#### OT-2 Approved Loadnames + +For OT-2 robots, use the following approved loadnames: + +- p20_single_gen2 +- p300_single_gen2 +- p1000_single_gen2 +- p300_multi_gen2 +- p20_multi_gen2 + +#### Flex Approved Loadnames + +For Flex robots, use these approved loadnames: + +- flex_1channel_50 +- flex_1channel_1000 +- flex_8channel_50 +- flex_8channel_1000 +- flex_96channel_1000 + +### Agilent Labware + +- Agilent 1 Well Reservoir 290 mL: agilent_1_reservoir_290ml + +### Applied Biosystems Labware + +- Applied Biosystems MicroAmp 384 Well Plate 40 uL: appliedbiosystemsmicroamp_384_wellplate_40ul + +### Axygen Labware + +- Axygen 1 Well Reservoir 90 mL: axygen_1_reservoir_90ml + +### Bio-Rad Labware + +- Bio-Rad 384 Well Plate 50 uL: biorad_384_wellplate_50ul +- Bio-Rad 96 Well Plate 200 uL PCR: biorad_96_wellplate_200ul_pcr + +### Corning Labware + +- Corning 12 Well Plate 6.9 mL Flat: corning_12_wellplate_6.9ml_flat +- Corning 24 Well Plate 3.4 mL Flat: corning_24_wellplate_3.4ml_flat +- Corning 384 Well Plate 112 uL Flat: corning_384_wellplate_112ul_flat +- Corning 48 Well Plate 1.6 mL Flat: corning_48_wellplate_1.6ml_flat +- Corning 6 Well Plate 16.8 mL Flat: corning_6_wellplate_16.8ml_flat +- Corning 96 Well Plate 360 uL Flat: corning_96_wellplate_360ul_flat + +### GEB Labware + +- GEB 96 Tip Rack 1000 uL: geb_96_tiprack_1000ul +- GEB 96 Tip Rack 10 uL: geb_96_tiprack_10ul + +### NEST Labware + +- NEST 12 Well Reservoir 15 mL: nest_12_reservoir_15ml +- NEST 1 Well Reservoir 195 mL: nest_1_reservoir_195ml +- NEST 1 Well Reservoir 290 mL: nest_1_reservoir_290ml +- NEST 96 Well Plate 100 uL PCR Full Skirt: nest_96_wellplate_100ul_pcr_full_skirt +- NEST 96 Well Plate 200 uL Flat: nest_96_wellplate_200ul_flat +- NEST 96 Deep Well Plate 2mL: nest_96_wellplate_2ml_deep + +### Opentrons Labware + +- Opentrons 10 Tube Rack with Falcon 4x50 mL, 6x15 mL Conical: opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical +- Opentrons 10 Tube Rack with NEST 4x50 mL, 6x15 mL Conical: opentrons_10_tuberack_nest_4x50ml_6x15ml_conical +- Opentrons 15 Tube Rack with Falcon 15 mL Conical: opentrons_15_tuberack_falcon_15ml_conical +- Opentrons 15 Tube Rack with NEST 15 mL Conical: opentrons_15_tuberack_nest_15ml_conical +- Opentrons 24 Well Aluminum Block with Generic 2 mL Screwcap: opentrons_24_aluminumblock_generic_2ml_screwcap +- Opentrons 24 Well Aluminum Block with NEST 0.5 mL Screwcap: opentrons_24_aluminumblock_nest_0.5ml_screwcap +- Opentrons 24 Well Aluminum Block with NEST 1.5 mL Screwcap: opentrons_24_aluminumblock_nest_1.5ml_screwcap +- Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap: opentrons_24_aluminumblock_nest_1.5ml_snapcap +- Opentrons 24 Well Aluminum Block with NEST 2 mL Screwcap: opentrons_24_aluminumblock_nest_2ml_screwcap +- Opentrons 24 Well Aluminum Block with NEST 2 mL Snapcap: opentrons_24_aluminumblock_nest_2ml_snapcap +- Opentrons 24 Tube Rack with Eppendorf 1.5 mL Safe-Lock Snapcap: opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap +- Opentrons 24 Tube Rack with Eppendorf 2 mL Safe-Lock Snapcap: opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap +- Opentrons 24 Tube Rack with Generic 2 mL Screwcap: opentrons_24_tuberack_generic_2ml_screwcap +- Opentrons 24 Tube Rack with NEST 0.5 mL Screwcap: opentrons_24_tuberack_nest_0.5ml_screwcap # not opentrons_24_tuberack_nest_0_5ml_screwcap +- Opentrons 24 Tube Rack with NEST 1.5 mL Screwcap: opentrons_24_tuberack_nest_1.5ml_screwcap # not opentrons_24_tuberack_nest_1_5ml_screwcap +- Opentrons 24 Tube Rack with NEST 1.5 mL Snapcap: opentrons_24_tuberack_nest_1.5ml_snapcap # note the use of dot. (`.`); opentrons_24_tuberack_nest_1_5ml_snapcap is incorrect +- Opentrons 24 Tube Rack with NEST 2 mL Screwcap: opentrons_24_tuberack_nest_2ml_screwcap +- Opentrons 24 Tube Rack with NEST 2 mL Snapcap: opentrons_24_tuberack_nest_2ml_snapcap +- Opentrons 6 Tube Rack with Falcon 50 mL Conical: opentrons_6_tuberack_falcon_50ml_conical +- Opentrons 6 Tube Rack with NEST 50 mL Conical: opentrons_6_tuberack_nest_50ml_conical +- Opentrons 96 Well Aluminum Block with Bio-Rad Well Plate 200 uL: opentrons_96_aluminumblock_biorad_wellplate_200ul +- Opentrons 96 Well Aluminum Block with Generic PCR Strip 200 uL: opentrons_96_aluminumblock_generic_pcr_strip_200ul +- Opentrons 96 Well Aluminum Block with NEST Well Plate 100 uL: opentrons_96_aluminumblock_nest_wellplate_100ul +- Opentrons 96 Deep Well Heater-Shaker Adapter: opentrons_96_deep_well_adapter +- Opentrons 96 Deep Well Heater-Shaker Adapter with NEST Deep Well Plate 2 mL: opentrons_96_deep_well_adapter_nest_wellplate_2ml_deep +- Opentrons OT-2 96 Filter Tip Rack 1000 uL: opentrons_96_filtertiprack_1000ul +- Opentrons OT-2 96 Filter Tip Rack 10 uL: opentrons_96_filtertiprack_10ul +- Opentrons OT-2 96 Filter Tip Rack 200 uL: opentrons_96_filtertiprack_200ul +- Opentrons OT-2 96 Filter Tip Rack 20 uL: opentrons_96_filtertiprack_20ul +- Opentrons 96 Flat Bottom Heater-Shaker Adapter: opentrons_96_flat_bottom_adapter +- Opentrons 96 Flat Bottom Heater-Shaker Adapter with NEST 96 Well Plate 200 uL Flat: opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat +- Opentrons 96 PCR Heater-Shaker Adapter: opentrons_96_pcr_adapter +- Opentrons 96 PCR Heater-Shaker Adapter with NEST Well Plate 100 ul: opentrons_96_pcr_adapter_nest_wellplate_100ul_pcr_full_skirt +- Opentrons OT-2 96 Tip Rack 1000 uL: opentrons_96_tiprack_1000ul +- Opentrons OT-2 96 Tip Rack 10 uL: opentrons_96_tiprack_10ul +- Opentrons OT-2 96 Tip Rack 20 uL: opentrons_96_tiprack_20ul +- Opentrons OT-2 96 Tip Rack 300 uL: opentrons_96_tiprack_300ul +- Opentrons 96 Well Aluminum Block: opentrons_96_well_aluminum_block +- Opentrons 96 Well Aluminum Block adapter: opentrons_96_well_aluminum_block +- Opentrons Tough 96 Well Plate 200 uL PCR Full Skirt: opentrons_96_wellplate_200ul_pcr_full_skirt +- Opentrons Aluminum Flat Bottom Plate: opentrons_aluminum_flat_bottom_plate +- Opentrons Flex 96 Filter Tip Rack 1000 uL: opentrons_flex_96_filtertiprack_1000ul # note that 1000ul not 200ul +- Opentrons Flex 96 Filter Tip Rack 200 uL: opentrons_flex_96_filtertiprack_200ul # note that 200ul not 1000ul +- Opentrons Flex 96 Filter Tip Rack 50 uL: opentrons_flex_96_filtertiprack_50ul +- Opentrons Flex 96 Tip Rack 1000 uL: opentrons_flex_96_tiprack_1000ul +- Opentrons Flex 96 Tip Rack 200 uL: opentrons_flex_96_tiprack_200ul +- Opentrons Flex 96 Tip Rack 50 uL: opentrons_flex_96_tiprack_50ul +- Opentrons Flex 96 Tip Rack Adapter: opentrons_flex_96_tiprack_adapter +- Opentrons Universal Flat Heater-Shaker Adapter: opentrons_universal_flat_adapter +- Opentrons Universal Flat Heater-Shaker Adapter with Corning 384 Well Plate 112 ul Flat: opentrons_universal_flat_adapter_corning_384_wellplate_112ul_flat + +### Other Labware Brands + +- Thermo Scientific Nunc 96 Well Plate 1300 uL: thermoscientificnunc_96_wellplate_1300ul +- Thermo Scientific Nunc 96 Well Plate 2000 uL: thermoscientificnunc_96_wellplate_2000ul +- USA Scientific 12 Well Reservoir 22 mL: usascientific_12_reservoir_22ml +- USA Scientific 96 Deep Well Plate 2.4 mL: usascientific_96_wellplate_2.4ml_deep + +### Additional Opentrons Tube Racks + +- 4-in-1 Tube Rack Set 15: opentrons_15_tuberack_nest_15ml_conical +- 4-in-1 Tube Rack Set 50: opentrons_6_tuberack_nest_50ml_conical + +### Flex Pipettes + +- Flex 1-Channel 50 uL Pipette (single channel): flex_1channel_50 +- Flex 1-Channel 1000 uL Pipette (single channel): flex_1channel_1000 +- Flex 8-Channel 50 uL Pipette (multi-channel): flex_8channel_50 +- Flex 8-Channel 1000 uL Pipette (multi-channel): flex_8channel_1000 +- Flex 96-Channel 1000 uL Pipette (multi-channel): flex_96channel_1000 + +### Modules + +- temperature module: temperature module gen2 +- thermocycler module: thermocycler module +- thermocycler module gen2: thermocyclerModuleV2 + +### Single channel pipettes: + +- Flex 1-Channel 50 uL Pipette +- Flex 1-Channel 1000 uL Pipette +- flex_1channel_1000 + +### Multi channel pipettes: + +- Flex 8-Channel 50 uL Pipette +- Flex 8-Channel 1000 uL Pipette +- Flex 96-Channel 1000 uL Pipette diff --git a/opentrons-ai-server/api/storage/docs/transfer_function_notes.md b/opentrons-ai-server/api/storage/docs/transfer_function_notes.md new file mode 100644 index 00000000000..dcf4f315545 --- /dev/null +++ b/opentrons-ai-server/api/storage/docs/transfer_function_notes.md @@ -0,0 +1,651 @@ + + +**Introduction** + +The `transfer` function in the Opentrons API v2 simplifies liquid handling operations by abstracting the complexities involved in pipetting tasks. It allows users to perform liquid transfers efficiently without the need to write explicit loops for iterating over wells or volumes. This manual provides a comprehensive guide on using the `transfer` function effectively, including best practices, common pitfalls, and practical examples. This updated version incorporates additional examples and clarifications based on real-world protocols, including the use of modules and dynamic data-driven transfers. + +--- + +**Basic Usage of `transfer`** + +The `transfer` function enables the movement of liquids from one location to another with optional parameters to control tip usage, mixing, air gaps, and more. Its basic syntax is: + +```python +pipette.transfer( + volume, # Volume to transfer (single value or list) + source, # Source well(s) + destination, # Destination well(s) + new_tip='always' # Tip usage strategy ('always', 'once', or 'never') + # Additional optional parameters... +) +``` + +- **Volume**: The amount of liquid to transfer, specified in microliters (µL). It can be a single value or a list of volumes. +- **Source**: The starting location(s) of the liquid, specified as a well or a list of wells. +- **Destination**: The target location(s) for the liquid, specified as a well or a list of wells. +- **`new_tip`**: Controls how tips are used during the transfer: + - `'always'`: Change tips between each transfer step. + - `'once'`: Use the same tip for all transfers. + - `'never'`: Do not change tips (use with caution). + +--- + +**Understanding Pipette Types** + +Choosing the correct method for accessing wells or columns depends on the type of pipette used. + +### Single-Channel Pipettes + +Single-channel pipettes interact with individual wells. When using single-channel pipettes, access wells using the `wells()` method. + +**Example:** + +```python +source_wells = source_labware.wells()[:number_of_samples] +``` + +### Multi-Channel Pipettes + +Multi-channel pipettes interact with rows or columns simultaneously. When using multi-channel pipettes, access columns using the `columns()` method. + +**Example:** + +```python +import math + +number_of_samples = 48 +number_of_columns = math.ceil(number_of_samples / 8) +source_columns = source_labware.columns()[:number_of_columns] +``` + +--- + +**Well Selection Methods** + +Accurate well selection is crucial for successful liquid transfers. + +### Accessing Wells + +- **Access all wells**: + + ```python + all_wells = labware.wells() + ``` + +- **Access specific wells by name**: + + ```python + well_a1 = labware.wells_by_name()['A1'] + ``` + +- **Access a list of wells by name**: + + ```python + specific_wells = [labware.wells_by_name()[well] for well in ['A1', 'B2', 'C3']] + ``` + +### Accessing Columns + +- **Access all columns**: + + ```python + all_columns = labware.columns() + ``` + +- **Access specific columns by index (0-based)**: + + ```python + first_three_columns = labware.columns()[:3] + ``` + +- **Access columns by name (1-based)**: + + ```python + column_one = labware.columns_by_name()['1'] + ``` + +- **Access multiple columns by name**: + + ```python + specific_columns = [labware.columns_by_name()[idx] for idx in ['1', '3', '5']] + ``` + +### Accessing Rows + +- **Access all rows**: + + ```python + all_rows = labware.rows() + ``` + +- **Access specific rows by name**: + + ```python + row_a = labware.rows_by_name()['A'] + ``` + +--- + +**Handling the `new_tip` Parameter** + +The `new_tip` parameter controls tip usage during transfers. + +- **`new_tip='always'`**: Use a new tip for each transfer. This is appropriate when avoiding cross-contamination is critical. + +- **`new_tip='once'`**: Use the same tip for all transfers in the `transfer` function call. Use this when transferring from a single source to multiple destinations and cross-contamination is not a concern. + +- **`new_tip='never'`**: Never change tips during the transfer. Use with caution, ensuring that cross-contamination will not occur. + +**Important Note:** Do not use `new_tip='once'` inside a loop; instead, pass lists of wells to the `transfer` function and let it handle the iteration. + +--- + +**Avoiding Unnecessary Loops** + +**Incorrect Usage:** + +```python +for src, dest in zip(source_wells, destination_wells): + pipette.transfer(volume, src, dest, new_tip='always') +``` + +**Issue:** This approach unnecessarily calls the `transfer` method multiple times and can lead to inefficiencies or errors. + +**Correct Usage:** + +```python +pipette.transfer(volume, source_wells, destination_wells, new_tip='always') +``` + +**Explanation:** The `transfer` function can handle lists of sources and destinations, automatically pairing them and iterating over them. + +--- + +**Proper Use of `new_tip`** + +**Incorrect Usage:** + +Using `new_tip='once'` inside a loop when intending to reuse the same tip. + +```python +for src, dest in zip(source_wells, destination_wells): + pipette.transfer(volume, src, dest, new_tip='once') +``` + +**Correct Usage:** + +```python +pipette.transfer(volume, source_wells, destination_wells, new_tip='once') +``` + +**Explanation:** When `new_tip='once'`, the pipette picks up a tip at the beginning of the transfer and uses it throughout. Using it inside a loop can cause the pipette to attempt to pick up a tip that is already in use, leading to errors. + +--- + +**Preventing "Out of Tips" Errors** + +- **Tip Rack Capacity:** Be mindful of the number of tips available in your tip racks. For example, a standard 96-tip rack cannot provide more than 96 tips. + +- **Calculating Tip Usage:** Estimate the number of tips required based on the `new_tip` parameter and the number of transfers. + +- **Loading Additional Tip Racks:** If your protocol requires more tips than are available in a single rack, load additional tip racks. + +**Example:** + +```python +tiprack1 = protocol.load_labware('opentrons_96_tiprack_300ul', 2) +tiprack2 = protocol.load_labware('opentrons_96_tiprack_300ul', 3) +pipette = protocol.load_instrument('p300_single_gen2', 'left', tip_racks=[tiprack1, tiprack2]) +``` + +--- + +**Index Errors** + +- **Labware Dimensions:** Ensure that your loops do not exceed the dimensions of the labware (e.g., a 96-well plate has 12 columns and 8 rows). + +- **Valid Indices:** Adjust loop ranges to stay within valid indices. + +**Incorrect Usage:** + +```python +for i in range(13): # Exceeds available columns (0-11) + pipette.transfer(volume, source_columns[i], dest_columns[i]) +``` + +**Correct Usage:** + +```python +for i in range(12): # Valid column indices for a 96-well plate + pipette.transfer(volume, source_columns[i], dest_columns[i]) +``` + +--- + +**Calculating Tip Usage** + +- **Estimate in Advance:** Before running the protocol, calculate the number of tips required based on the number of transfers and the `new_tip` parameter. + +- **Account for Pipette Type:** Remember that multi-channel pipettes use multiple tips per pick-up (e.g., an 8-channel pipette uses 8 tips per pick-up). + +- **Example Calculation:** + + If you are transferring samples to 96 wells using a single-channel pipette with `new_tip='always'`, you will need 96 tips. If you are using a multi-channel pipette (8-channel) to transfer to 12 columns, you will need 12 tip pickups (12 columns x 8 tips per pickup = 96 tips). + +--- + +**Optimizing Transfers** + +- **Use Lists in `transfer`:** Provide lists of source and destination wells to the `transfer` function to leverage its built-in iteration. + +- **Minimize Tip Usage:** When appropriate, reuse tips by setting `new_tip='once'` to conserve tips and reduce waste. + +- **Avoid Unnecessary Loops:** Let the `transfer` function handle iteration over wells and volumes. + +--- + +**Efficient Labware Access** + +- **Match Pipette Type to Access Method:** Use `wells()` for single-channel pipettes and `columns()` for multi-channel pipettes. + +- **Use Labware Methods Correctly:** Ensure you are accessing wells and columns using the correct methods to prevent errors. + +--- + +**Example 1: Single Source to Multiple Destinations** + +**Task:** Transfer 1 µL of reagent from tube A1 in the source rack to all wells in the destination plate using the same tip. + +**Protocol:** + +```python +def run(protocol): + # Labware + tiprack = protocol.load_labware('opentrons_96_tiprack_20ul', 2) + source_rack = protocol.load_labware('opentrons_24_tuberack_nest_1.5ml_snapcap', 3) + dest_plate = protocol.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt', 9) + + # Pipette + p20 = protocol.load_instrument('p20_single_gen2', mount='right', tip_racks=[tiprack]) + + # Wells + src_well = source_rack.wells_by_name()['A1'] + dest_wells = dest_plate.wells() + + # Transfer + p20.transfer(1, src_well, dest_wells, new_tip='once') +``` + +--- + +**Example 2: Well-to-Well Transfers with Reused Tips** + +**Task:** Transfer 50 µL from wells A1 and A2 in source labware 1 to wells B6 and B7 in source labware 2 using the same tip. + +**Protocol:** + +```python +def run(protocol): + # Labware + source_labware_1 = protocol.load_labware('source_labware_1_definition', 1) + source_labware_2 = protocol.load_labware('source_labware_2_definition', 2) + tiprack = protocol.load_labware('opentrons_96_tiprack_300ul', 3) + + # Pipette + p300 = protocol.load_instrument('p300_single_gen2', mount='left', tip_racks=[tiprack]) + + # Wells + source_wells = [source_labware_1.wells_by_name()[well] for well in ['A1', 'A2']] + destination_wells = [source_labware_2.wells_by_name()[well] for well in ['B6', 'B7']] + + # Transfer + p300.transfer(50, source_wells, destination_wells, new_tip='once') +``` + +--- + +**Example 3: Column-wise Transfers with Multi-Channel Pipette** + +**Task:** Using a P300 Multi-Channel pipette, transfer 55 µL of sample from each column of the source plate into the corresponding columns of the destination plate, changing tips between each transfer. + +**Protocol:** + +```python +def run(protocol): + # Labware + source_plate = protocol.load_labware('source_plate_definition', 1) + destination_plate = protocol.load_labware('destination_plate_definition', 2) + tiprack = protocol.load_labware('opentrons_96_tiprack_300ul', 3) + + # Pipette + p300_multi = protocol.load_instrument('p300_multi_gen2', mount='left', tip_racks=[tiprack]) + + # Columns + src_cols = source_plate.columns() + dest_cols = destination_plate.columns() + + # Transfer + p300_multi.transfer(55, src_cols, dest_cols, new_tip='always') +``` + +--- + +**Example 4: Complex Transfers with Different Pipettes** + +**Task:** Transfer 15 µL from wells C4 and C6 in source labware 2 to wells A3 and A4 in source labware 1 using the same tip. + +**Protocol:** + +```python +def run(protocol): + # Labware + source_1 = protocol.load_labware('source_labware_1_definition', 1) + source_2 = protocol.load_labware('source_labware_2_definition', 2) + tiprack = protocol.load_labware('opentrons_96_tiprack_20ul', 3) + + # Pipette + p20 = protocol.load_instrument('p20_single_gen2', mount='right', tip_racks=[tiprack]) + + # Wells + src_wells = [source_2.wells_by_name()[well] for well in ['C4', 'C6']] + dest_wells = [source_1.wells_by_name()[well] for well in ['A3', 'A4']] + + # Transfer + p20.transfer(15, src_wells, dest_wells, new_tip='once') +``` + +--- + +**Example 5: Transfers Involving Modules** + +**Task:** Perform transfers involving thermocycler and temperature modules, handling temperature settings and PCR amplification steps. + +**Protocol:** + +```python +def run(protocol): + import math + + # Sample preparation parameters + number_of_samples = 64 + sample_volume_ul = 5 + master_mix_volume_ul = 7 + mixing_cycles = 9 + total_mix_volume_ul = sample_volume_ul + master_mix_volume_ul + + # Modules + thermocycler_module = protocol.load_module('thermocyclerModuleV2') + sample_temp_module = protocol.load_module('temperature module gen2', 1) + master_mix_temp_module = protocol.load_module('temperature module gen2', 3) + + # Labware + tips_20ul = protocol.load_labware('opentrons_96_filtertiprack_20ul', 4) + pcr_plate = thermocycler_module.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + sample_plate = sample_temp_module.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + master_mix_plate = master_mix_temp_module.load_labware('opentrons_96_wellplate_200ul_pcr_full_skirt') + + # Pipette + p20_multi = protocol.load_instrument('p20_multi_gen2', 'left', tip_racks=[tips_20ul]) + + # Well allocation + number_of_columns = math.ceil(number_of_samples / 8) + sample_source_wells = sample_plate.columns()[:number_of_columns] + sample_destination_wells = pcr_plate.columns()[:number_of_columns] + master_mix_source_wells = master_mix_plate.columns()[:number_of_columns] + master_mix_destination_wells = pcr_plate.columns()[:number_of_columns] + + # Set temperatures + sample_temp_module.set_temperature(4) + master_mix_temp_module.set_temperature(10) + + # Transfer master mix + p20_multi.transfer( + master_mix_volume_ul, + master_mix_source_wells, + master_mix_destination_wells, + new_tip='once' + ) + + # Transfer samples and mix + p20_multi.transfer( + sample_volume_ul, + sample_source_wells, + sample_destination_wells, + new_tip='always', + mix_after=(mixing_cycles, total_mix_volume_ul), + blow_out=True, + blowout_location='destination well' + ) + + # PCR cycling steps (simplified for brevity) + thermocycler_module.close_lid() + thermocycler_module.execute_profile( + steps=[ + {{'temperature': 74, 'hold_time_seconds': 65}}, + {{'temperature': 60, 'hold_time_seconds': 7}}, + {{'temperature': 84, 'hold_time_seconds': 19}}, + {{'temperature': 57, 'hold_time_seconds': 44}} + ], + repetitions=13, + block_max_volume=total_mix_volume_ul + ) + thermocycler_module.open_lid() + + # Deactivate modules + master_mix_temp_module.deactivate() + sample_temp_module.deactivate() +``` + +--- + +**Example 6: Dynamic Transfers Using CSV Data** + +**Task:** Perform transfers based on data provided in a CSV file, without using the thermocycler. + +**Protocol:** + +```python +def run(protocol): + # CSV data as a multi-line string + csv_data = ''' + Primer Tube,Destination well + A1,A1 + B1,B1 + C1,C1 + D1,D1 + A2,E1 + B2,F1 + C2,G1 + D2,H1 + ''' + + # Parse CSV data + csv_lines = [line.strip().split(',') for line in csv_data.strip().splitlines() if line.strip()] + headers = csv_lines[0] + data = csv_lines[1:] + + # Labware + tuberack = protocol.load_labware('opentrons_24_tuberack_nest_2ml_snapcap', 'C1') + dest_plate = protocol.load_labware('biorad_96_wellplate_200ul_pcr', 'B1') + tiprack_single = [protocol.load_labware('opentrons_96_tiprack_50ul', slot) for slot in ['A1']] + p50 = protocol.load_instrument('p50_single', 'right', tip_racks=tiprack_single) + + # Transfers based on CSV data + for row in data: + source_tube = row[0] + dest_well = row[1] + p50.transfer(7, tuberack.wells_by_name()[source_tube], dest_plate.wells_by_name()[dest_well], new_tip='always') +``` + +--- + +**Additional Examples** + +**Example 7: Transfer with Heater-Shaker Module** + +**Task:** Transfer liquids between a reservoir, a PCR plate, and a heater-shaker module's plate, including shaking the plate. + +**Protocol:** + +```python +def run(protocol): + # Modules + heater_shaker_module = protocol.load_module('heaterShakerModuleV1', 'D1') + heater_shaker_plate = heater_shaker_module.load_labware('corning_96_wellplate_360ul_flat') + + # Labware + reservoir = protocol.load_labware('nest_1_reservoir_195ml', 'C1') + pcr_plate = protocol.load_labware('nest_96_wellplate_200ul_flat', 'D2') + tiprack_200ul = protocol.load_labware('opentrons_96_tiprack_200ul', 'A2') + tiprack_50ul = protocol.load_labware('opentrons_96_tiprack_50ul', 'C2') + + # Pipette + pipette = protocol.load_instrument('p300_multi', mount='left', tip_racks=[tiprack_200ul, tiprack_50ul]) + + # Steps + heater_shaker_module.open_labware_latch() + protocol.pause("Please place the plate on the Heater-Shaker Module.") + heater_shaker_module.close_labware_latch() + + # Transfer 70 µL from reservoir to heater-shaker plate + pipette.transfer(70, reservoir['A1'], heater_shaker_plate['A1'], new_tip='always') + + # Transfer 10 µL from PCR plate to heater-shaker plate + pipette.transfer(10, pcr_plate['A1'], heater_shaker_plate['A1'], new_tip='always') + + # Shake the plate + heater_shaker_module.set_and_wait_for_shake_speed(rpm=2000) + protocol.delay(minutes=1) + heater_shaker_module.deactivate_shaker() +``` + +--- + +**Advanced Usage** + +Advanced features of the `transfer` function include specifying aspiration and dispense locations, mixing, air gaps, blow out, and using modules with `transfer`. + +--- + +**Specifying Aspiration and Dispense Locations** + +You can specify precise locations within wells for aspiration and dispensing. + +**Example:** + +```python +pipette.transfer( + 20, + source_well.bottom(3), # 3 mm above the bottom + destination_well.top(-7), # 7 mm below the top + new_tip='once' +) +``` + +--- + +**Using Mix After/Before** + +Mixing can be performed before or after the transfer. + +**Example:** + +```python +pipette.transfer( + 10, + source_well, + destination_well, + mix_after=(5, 10) # Mix 5 times with a volume of 10 µL after dispensing +) +``` + +--- + +**Handling Air Gaps and Blow Out** + +Air gaps and blow-out can prevent dripping and ensure complete dispensing. + +**Example:** + +```python +pipette.transfer( + 10, + source_well, + destination_well, + air_gap=5, # Add a 5 µL air gap after aspiration + blow_out=True, + blowout_location='destination well' +) +``` + +--- + +**Using Modules with `transfer`** + +The `transfer` function can be used effectively with various modules like the thermocycler, temperature modules, and heater-shaker modules. When using modules: + +- **Set Module Temperatures Before Transfers:** Ensure that temperature modules are set to the desired temperature before performing transfers. + +- **Load Labware on Modules:** Use the module's `load_labware` or `load_adapter` method to place labware on the module. + +**Example:** + +```python +# Load modules +temp_module = protocol.load_module('temperature module gen2', '1') +thermocycler_module = protocol.load_module('thermocyclerModuleV2') + +# Load labware on modules +temp_plate = temp_module.load_labware('opentrons_96_aluminumblock_biorad_wellplate_200ul') +pcr_plate = thermocycler_module.load_labware('nest_96_wellplate_100ul_pcr_full_skirt') + +# Set temperatures +temp_module.set_temperature(4) +thermocycler_module.set_block_temperature(95) +``` + +--- + +**Dynamic Transfers Based on Data** + +For protocols that require dynamic transfers based on external data (e.g., CSV files), you can parse the data and use it to control the `transfer` function. + +- **Parsing CSV Data:** Use Python's built-in functions or the `csv` module to read and parse CSV data. + +- **Using Parsed Data in Transfers:** Use the parsed data to define source wells, destination wells, and volumes. + +**Example:** + +```python +import csv +from io import StringIO + +def run(protocol): + # CSV data as a string + csv_data = ''' + Source Well,Destination Well,Volume + A1,B1,50 + A2,B2,100 + A3,B3,150 + ''' + + # Parse CSV data + reader = csv.DictReader(StringIO(csv_data.strip())) + transfers = list(reader) + + # Labware + source_plate = protocol.load_labware('nest_96_wellplate_200ul_flat', '1') + dest_plate = protocol.load_labware('nest_96_wellplate_200ul_flat', '2') + tiprack = protocol.load_labware('opentrons_96_tiprack_300ul', '3') + pipette = protocol.load_instrument('p300_single', 'left', tip_racks=[tiprack]) + + # Perform transfers based on CSV data + for transfer in transfers: + source_well = source_plate.wells_by_name()[transfer['Source Well']] + dest_well = dest_plate.wells_by_name()[transfer['Destination Well']] + volume = float(transfer['Volume']) + pipette.transfer(volume, source_well, dest_well, new_tip='always') + +``` + +