Week 3 HW: Lab Automation

Assignment : Python Script for Opentrons Artwork

I had to write a Python script for a art design. I chose to create a silhouette of the Indian subcontinent, with my city being highlighted. I did that using the Opentrons Artwork website. I thought I will make a pattern of sorts with code but I realized that would time consuming and not very symbolic as such. I got a clipart of India from google and cropped it and then used that too generate my artwork. It didn’t look very good, I had to fiddle around with the contrast, brightness and other values to make it work. It still wasn’t looking how I’d expected it too. I decided to redo it.

![[Screenshot 2026-02-22 153034.png]]

This is the India 2.0 Art (image below), I like this one much better. The green outline in the previous one was not a very good design choice. I don’t know what I was thinking. I created this one by editing my original clipart then striking a balance in the contrast, brightness values. This one look much better in my opinion.

![[Screenshot 2026-02-23 172213.png]]

![[Screenshot 2026-02-22 153040.png]]

The coordinates were right below my art on the opentrons art website, I just decided to download the script. I edited the script a bit in the PyCharm (adding my name).

![[Pasted image 20260223175557.png]]

Reading further, I found out that I didn’t even need to download my script. I could just publish my design on the opentrons art website and then share the link. I did exactly that and submitted my script to the google form. But in case, Murphy’s Law decides to apply, here’s my code:

The CODEfrom opentrons import types

import string

metadata = {
‘protocolName’: ‘Deep Dalvi - Opentrons Art - HTGAA’,
‘author’: ‘HTGAA’,
‘source’: ‘HTGAA 2026’,
‘apiLevel’: ‘2.20’
}

Z_VALUE_AGAR = 2.0
POINT_SIZE = 0.75

electra2_points = [(-18.7,29.7), (-16.5,29.7), (-14.3,29.7), (-12.1,29.7), (-9.9,29.7), (-16.5,27.5), (-14.3,27.5), (-12.1,27.5), (-9.9,27.5), (-7.7,27.5), (-16.5,25.3), (-14.3,25.3), (-12.1,25.3), (-9.9,25.3), (-7.7,25.3), (-5.5,25.3), (-16.5,23.1), (-14.3,23.1), (-12.1,23.1), (-9.9,23.1), (-7.7,23.1), (-5.5,23.1), (-14.3,20.9), (-12.1,20.9), (-9.9,20.9), (-7.7,20.9), (-14.3,18.7), (-12.1,18.7), (-9.9,18.7), (-7.7,18.7), (-5.5,18.7), (-16.5,16.5), (-14.3,16.5), (-12.1,16.5), (-9.9,16.5), (-7.7,16.5), (-5.5,16.5), (23.1,16.5), (25.3,16.5), (27.5,16.5), (-18.7,14.3), (-16.5,14.3), (-14.3,14.3), (-12.1,14.3), (-9.9,14.3), (-7.7,14.3), (-5.5,14.3), (23.1,14.3), (25.3,14.3), (27.5,14.3), (-20.9,12.1), (-18.7,12.1), (-16.5,12.1), (-14.3,12.1), (-12.1,12.1), (-9.9,12.1), (-7.7,12.1), (-5.5,12.1), (-3.3,12.1), (-1.1,12.1), (12.1,12.1), (20.9,12.1), (23.1,12.1), (25.3,12.1), (27.5,12.1), (-25.3,9.9), (-23.1,9.9), (-20.9,9.9), (-18.7,9.9), (-16.5,9.9), (-14.3,9.9), (-12.1,9.9), (-9.9,9.9), (-7.7,9.9), (-5.5,9.9), (-3.3,9.9), (-1.1,9.9), (1.1,9.9), (3.3,9.9), (5.5,9.9), (7.7,9.9), (9.9,9.9), (12.1,9.9), (14.3,9.9), (16.5,9.9), (18.7,9.9), (20.9,9.9), (23.1,9.9), (25.3,9.9), (-23.1,7.7), (-20.9,7.7), (-18.7,7.7), (-16.5,7.7), (-14.3,7.7), (-12.1,7.7), (-9.9,7.7), (-7.7,7.7), (-5.5,7.7), (-3.3,7.7), (-1.1,7.7), (1.1,7.7), (3.3,7.7), (5.5,7.7), (7.7,7.7), (9.9,7.7), (12.1,7.7), (16.5,7.7), (18.7,7.7), (20.9,7.7), (23.1,7.7), (25.3,7.7), (-23.1,5.5), (-20.9,5.5), (-18.7,5.5), (-16.5,5.5), (-14.3,5.5), (-12.1,5.5), (-9.9,5.5), (-7.7,5.5), (-5.5,5.5), (-3.3,5.5), (-1.1,5.5), (1.1,5.5), (3.3,5.5), (5.5,5.5), (7.7,5.5), (9.9,5.5), (12.1,5.5), (20.9,5.5), (23.1,5.5), (25.3,5.5), (-29.7,3.3), (-27.5,3.3), (-25.3,3.3), (-23.1,3.3), (-20.9,3.3), (-18.7,3.3), (-16.5,3.3), (-14.3,3.3), (-12.1,3.3), (-9.9,3.3), (-7.7,3.3), (-5.5,3.3), (-3.3,3.3), (-1.1,3.3), (1.1,3.3), (3.3,3.3), (5.5,3.3), (7.7,3.3), (9.9,3.3), (12.1,3.3), (14.3,3.3), (20.9,3.3), (-27.5,1.1), (-25.3,1.1), (-23.1,1.1), (-20.9,1.1), (-18.7,1.1), (-16.5,1.1), (-14.3,1.1), (-12.1,1.1), (-9.9,1.1), (-7.7,1.1), (-5.5,1.1), (-3.3,1.1), (-1.1,1.1), (1.1,1.1), (3.3,1.1), (5.5,1.1), (7.7,1.1), (9.9,1.1), (12.1,1.1), (14.3,1.1), (-27.5,-1.1), (-25.3,-1.1), (-23.1,-1.1), (-18.7,-1.1), (-16.5,-1.1), (-14.3,-1.1), (-12.1,-1.1), (-9.9,-1.1), (-7.7,-1.1), (-5.5,-1.1), (-3.3,-1.1), (-1.1,-1.1), (1.1,-1.1), (3.3,-1.1), (5.5,-1.1), (7.7,-1.1), (9.9,-1.1), (12.1,-1.1), (14.3,-1.1), (-20.9,-3.3), (-18.7,-3.3), (-16.5,-3.3), (-14.3,-3.3), (-12.1,-3.3), (-9.9,-3.3), (-7.7,-3.3), (-5.5,-3.3), (-3.3,-3.3), (-1.1,-3.3), (1.1,-3.3), (3.3,-3.3), (5.5,-3.3), (7.7,-3.3), (-20.9,-5.5), (-18.7,-5.5), (-16.5,-5.5), (-14.3,-5.5), (-12.1,-5.5), (-9.9,-5.5), (-7.7,-5.5), (-5.5,-5.5), (-3.3,-5.5), (-1.1,-5.5), (1.1,-5.5), (3.3,-5.5), (5.5,-5.5), (7.7,-5.5), (-20.9,-7.7), (-18.7,-7.7), (-16.5,-7.7), (-14.3,-7.7), (-12.1,-7.7), (-9.9,-7.7), (-7.7,-7.7), (-5.5,-7.7), (-3.3,-7.7), (-1.1,-7.7), (1.1,-7.7), (3.3,-7.7), (5.5,-7.7), (-18.7,-9.9), (-16.5,-9.9), (-14.3,-9.9), (-12.1,-9.9), (-9.9,-9.9), (-7.7,-9.9), (-5.5,-9.9), (-3.3,-9.9), (-1.1,-9.9), (1.1,-9.9), (-18.7,-12.1), (-16.5,-12.1), (-14.3,-12.1), (-12.1,-12.1), (-9.9,-12.1), (-7.7,-12.1), (-5.5,-12.1), (-3.3,-12.1), (-1.1,-12.1), (-18.7,-14.3), (-16.5,-14.3), (-14.3,-14.3), (-12.1,-14.3), (-9.9,-14.3), (-7.7,-14.3), (-5.5,-14.3), (-3.3,-14.3), (-16.5,-16.5), (-14.3,-16.5), (-12.1,-16.5), (-9.9,-16.5), (-7.7,-16.5), (-5.5,-16.5), (-16.5,-18.7), (-14.3,-18.7), (-12.1,-18.7), (-9.9,-18.7), (-7.7,-18.7), (-5.5,-18.7), (-16.5,-20.9), (-14.3,-20.9), (-12.1,-20.9), (-9.9,-20.9), (-7.7,-20.9), (-5.5,-20.9), (-14.3,-23.1), (-12.1,-23.1), (-9.9,-23.1), (-7.7,-23.1), (-5.5,-23.1), (-12.1,-25.3), (-9.9,-25.3), (-7.7,-25.3), (-5.5,-25.3), (-12.1,-27.5), (-9.9,-27.5), (-7.7,-27.5), (-9.9,-29.7)]
azurite_points = [(29.7,12.1), (18.7,-16.5), (20.9,-16.5), (18.7,-18.7), (20.9,-18.7), (-5.5,-27.5)]
mrfp1_points = [(-20.9,-1.1)]
mko2_points = [(12.1,-12.1), (14.3,-12.1), (16.5,-12.1), (18.7,-12.1), (20.9,-12.1), (23.1,-12.1), (25.3,-12.1), (27.5,-12.1), (12.1,-14.3), (14.3,-14.3), (16.5,-14.3), (18.7,-14.3), (20.9,-14.3), (23.1,-14.3), (25.3,-14.3), (27.5,-14.3)]
sfgfp_points = [(12.1,-20.9), (14.3,-20.9), (16.5,-20.9), (18.7,-20.9), (20.9,-20.9), (23.1,-20.9), (25.3,-20.9), (27.5,-20.9), (12.1,-23.1), (14.3,-23.1), (16.5,-23.1), (18.7,-23.1), (20.9,-23.1), (23.1,-23.1), (25.3,-23.1), (27.5,-23.1)]

point_name_pairing = [(“electra2”, electra2_points),(“azurite”, azurite_points),(“mrfp1”, mrfp1_points),(“mko2”, mko2_points),(“sfgfp”, sfgfp_points)]

Robot deck setup constants

TIP_RACK_DECK_SLOT = 9
COLORS_DECK_SLOT = 6
AGAR_DECK_SLOT = 5
PIPETTE_STARTING_TIP_WELL = ‘A1’

Place the PCR tubes in this order

well_colors = {
‘A1’: ‘sfGFP’,
‘A2’: ‘mRFP1’,
‘A3’: ‘mKO2’,
‘A4’: ‘Venus’,
‘A5’: ‘mKate2_TF’,
‘A6’: ‘Azurite’,
‘A7’: ‘mCerulean3’,
‘A8’: ‘mClover3’,
‘A9’: ‘mJuniper’,
‘A10’: ‘mTurquoise2’,
‘A11’: ‘mBanana’,
‘A12’: ‘mPlum’,
‘B1’: ‘Electra2’,
‘B2’: ‘mWasabi’,
‘B3’: ‘mScarlet_I’,
‘B4’: ‘mPapaya’,
‘B5’: ’eqFP578’,
‘B6’: ’tdTomato’,
‘B7’: ‘DsRed’,
‘B8’: ‘mKate2’,
‘B9’: ‘EGFP’,
‘B10’: ‘mRuby2’,
‘B11’: ‘TagBFP’,
‘B12’: ‘mChartreuse_TF’,
‘C1’: ‘mLychee_TF’,
‘C2’: ‘mTagBFP2’,
‘C3’: ‘mEGFP’,
‘C4’: ‘mNeonGreen’,
‘C5’: ‘mAzamiGreen’,
‘C6’: ‘mWatermelon’,
‘C7’: ‘avGFP’,
‘C8’: ‘mCitrine’,
‘C9’: ‘mVenus’,
‘C10’: ‘mCherry’,
‘C11’: ‘mHoneydew’,
‘C12’: ‘TagRFP’,
‘D1’: ‘mTFP1’,
‘D2’: ‘Ultramarine’,
‘D3’: ‘ZsGreen1’,
‘D4’: ‘mMiCy’,
‘D5’: ‘mStayGold2’,
‘D6’: ‘PA_GFP’
}

volume_used = {
’electra2’: 0,
‘azurite’: 0,
‘mrfp1’: 0,
‘mko2’: 0,
‘sfgfp’: 0
}

def update_volume_remaining(current_color, quantity_to_aspirate):
rows = string.ascii_uppercase
for well, color in list(well_colors.items()):
if color == current_color:
if (volume_used[current_color] + quantity_to_aspirate) > 250:
# Move to next well horizontally by advancing row letter, keeping column number
row = well[0]
col = well[1:]

            # Find next row letter  
            next_row = rows[rows.index(row) + 1]  
            next_well = f"{next_row}{col}"  
            del well_colors[well]  
            well_colors[next_well] = current_color  
            volume_used[current_color] = quantity_to_aspirate  
        else:  
            volume_used[current_color] += quantity_to_aspirate  
        break  

def run(protocol):
# Load labware, modules and pipettes
protocol.home()

# Tips  
tips_20ul = protocol.load_labware('opentrons_96_tiprack_20ul', TIP_RACK_DECK_SLOT, 'Opentrons 20uL Tips')  

# Pipettes  
pipette_20ul = protocol.load_instrument("p20_single_gen2", "right", [tips_20ul])  

# Deep Well Plate  
temperature_plate = protocol.load_labware('nest_96_wellplate_2ml_deep', 6)  

# Agar Plate  
agar_plate = protocol.load_labware('htgaa_agar_plate', AGAR_DECK_SLOT, 'Agar Plate')  
agar_plate.set_offset(x=0.00, y=0.00, z=Z_VALUE_AGAR)  

# Get the top-center of the plate, make sure the plate was calibrated before running this  
center_location = agar_plate['A1'].top()  

pipette_20ul.starting_tip = tips_20ul.well(PIPETTE_STARTING_TIP_WELL)  
  
# Helper function (dispensing)  
def dispense_and_jog(pipette, volume, location):  
    assert(isinstance(volume, (int, float)))  
    # Go above the location  
    above_location = location.move(types.Point(z=location.point.z + 2))  
    pipette.move_to(above_location)  
    # Go downwards and dispense  
    pipette.dispense(volume, location)  
    # Go upwards to avoid smearing  
    pipette.move_to(above_location)  

# Helper function (color location)  
def location_of_color(color_string):  
    for well,color in well_colors.items():  
        if color.lower() == color_string.lower():  
            return temperature_plate[well]  
    raise ValueError(f"No well found with color {color_string}")  

# Print pattern by iterating over lists  
for i, (current_color, point_list) in enumerate(point_name_pairing):  
    # Skip the rest of the loop if the list is empty  
    if not point_list:  
        continue  

    # Get the tip for this run, set the bacteria color, and the aspirate bacteria of choice  
    pipette_20ul.pick_up_tip()  
    max_aspirate = int(18 // POINT_SIZE) * POINT_SIZE  
    quantity_to_aspirate = min(len(point_list)*POINT_SIZE, max_aspirate)  
    update_volume_remaining(current_color, quantity_to_aspirate)  
    pipette_20ul.aspirate(quantity_to_aspirate, location_of_color(current_color))  

    # Iterate over the current points list and dispense them, refilling along the way  
    for i in range(len(point_list)):  
        x, y = point_list[i]  
        adjusted_location = center_location.move(types.Point(x, y))  

        dispense_and_jog(pipette_20ul, POINT_SIZE, adjusted_location)  
          
        if pipette_20ul.current_volume == 0 and len(point_list[i+1:]) > 0:  
            quantity_to_aspirate = min(len(point_list[i:])*POINT_SIZE, max_aspirate)  
            update_volume_remaining(current_color, quantity_to_aspirate)  
            pipette_20ul.aspirate(quantity_to_aspirate, location_of_color(current_color))  

    # Drop tip between each color  
    pipette_20ul.drop_tip()
</details>

### Post-Lab Questions 

##### Question 1. **Find and describe** a published paper that utilizes the Opentrons or an automation tool to achieve novel biological applications.

I thought that I will need to put in effort to find a paper but the Opentrons website has its own embedded search box thingie to find papers. I just selected the area of my preference and then got a paper I liked. 

![[Pasted image 20260224222356.png]]

The paper I chose was titled "Optimizing automated layer‑by‑layer deposition of engineered ECM‑like microenvironments for mammalian cell culture" I chose the paper because last year I'd attended a workshop on 3D Bioprinting and I'd learnt about organoid cultures and replication of the ECM-like conditions for better cell cultures, so I was aware about the what the paper was about. Here's a link to the paper. https://doi.org/10.1557/s43579-025-00912-9

![[Pasted image 20260224222741.png]]

##### Question 1 Detour: Reading a Paper and Consolidation.

Back in my graduate coursework, I didn't have to read a lot of papers but my final dissertation did require me to read a lot of papers. I'd chosen a broad topic "Machine Learning in Life Sciences" There were a lot of papers to be read and a lot of distillation to be done. I used to read papers like header-to-footer but I realized that it was not an optimal approach. The very act of reading 30+ papers for my bachelor's dissertation led me to evolve, iterate on my paper reading method. So now what I do is this. 

1. ABSTRACT! I read the abstract to get a basic idea of what the paper is about. If I don't get it, perhaps because of a lot of jargons. I search up on the jargons, revisit the fundamentals and then read the abstract again. I write it all down (what I understood from the abstract)
2. QUESTIONS! I try and see the abstract as a summary of the paper and the expectations I can make, then I skim the paper looking for the references to the abstract. For example, in this case the abstract talked about using heparin and collagen, I skimmed and found out the information related to it, WHY they were using it, WHAT were they creating. I try to connect the components via questions (you can see what I mean in my distillation sheet of sorts)
3. SKETCHES and FLOWS! I make a lot of sketches where there's methods/compositions involved. I also try to properly write workflows in a sequence as it aids in a narratively coherent understanding. I DO NOT try to polish it, nobody else has to understand it, only me. I try to not be a perfectionist as then looking of a proper sketch/metaphor often leads to time wasting. (In case, I am making some kind of content, I use this sheet as a start and polish it further. NEVER try to be clean while understanding especially if your sheet isn't going to be used by someone else!)
4. DATA! I have an inherent problem with graphs (I am actively trying to mitigate it) so I spend a lot of time, trying to understand graphs and then convert them into legit statements (for my ease, graphs don't instantly make sense to me for some reason) I verify if the data makes sense with respect to the abstract, how they've validated the results, the metrics they've chosen (this part can take some time, but with exposure to more papers, it starts getting efficient as validation methods/approaches often follow a core principle that can repeat (it's like a six basic plots type thing))

I don't try to make it clean. This is one might seem clean but as I've read a lot of papers before this and iterated on my method, I have sort of gained a knowledge of what to write, where and what can be expected so due to prior experience/iterations, the running clutter is naturally reduced.

My distillation sheet for the paper 
![[Scan_20260224.jpg]]


##### Question 2. **Write a description** about what you intend to do with automation tools for your final project. You may include example pseudocode, Python scripts, 3D printed holders, a plan for how to use Ginkgo Nebula, and more. 



You may reference this week’s [recitation slide deck](https://docs.google.com/presentation/d/e/2PACX-1vQc3zo7Z0b6HK7YeC56p_n2RbHNjUHh1HI66DH0cHbFk0db1HlbF7gILE__NCvhUiYMjIGSOHwHPv2_/pub?start=false&loop=false&delayms=3000) for lab automation details.


### Final Project Ideas

1. Organstrument 
2. Space Fermenter
3. Skwiz