Using Stocks
The Basics
In LabGuru, Stock items represent a physical instantiation of a Collections item. So Stock attributes
are used to store information like location, concentration, and storage buffer rather than things like genotype or
molecular weight, which are conceptual properties of every instance of the Collections item. These two concepts
are linked via the stockable attribute.
A Stock object’s location is recorded by the storage and location_in_box
attributes. It’s generally easier to set this through the Plate or Box that the stock will be stored in.
Barcodes
The barcode attribute of a Stock is used to record a barcode that has been printed on the
tube. LabGuru’s API allows for the direct lookup of a stock via its barcode, so in addition to from_name()
and from_id(), we can use the from_barcode() method to get a stock from the API.
Warning
By GRO convention, a stock’s name in generally the same as it’s stockable. This results in many stocks with the same
name, making from_name() pretty much useless. Please don’t use it unless you know your stock has been
given a unique name.
Warning
from_barcode() will also match a stock’s ID if no matching barcode is found (LabGuru implementation,
not ours). When designing stock barcodes, please ensure the numbers are large enough to not collide with stock IDs
or are alphanumeric.
Concentrations
One of the main utilities of the Stock class is recording the stock’s concentration. LabGuru records this as two
separate attributes concentration and concentration_unit_id. While separating
these and using a controlled vocabulary for units makes a lot of sense, it also makes concentrations hard to work with.
So most of the Stock class’s code is dedicated to unit conversion.
Available Units
Category |
Units (unit ID) |
|---|---|
Molarity |
M (11), mM (12), µM (13), nM (20) |
Molality |
mg/mL (9), g/L (10), µg/mL (14), ng/µL (15), g/mL (18) |
Other |
ratio (16), % (17), OD (19) |
Setting Concentration using Stock properties
The easiest way to set a stock’s concentration is to use one of the following properties: molarity,
micromolarity, nanomolarity, or ng_ul.
Once you set one value, you can retrieve any of the different units. If the object is a subclass of Weighted,
you can even convert between molarity and ng/μL.
>>> from LabGuruAPI import Plasmid
>>> p = Plasmid.from_name('pGRO-C1228')
>>> s = p.add_stock(p.name, None, update=False)
>>> s.molarity = 1.0
>>> f'{s.micromolarity:,}'
'1,000,000.0'
>>> f'{s.nanomolarity:,}'
'1,000,000,000.0'
>>> f'{s.ng_ul:,}'
'3,655,887,400.0'
Setting Concentration using Strings
The Stock class’s concentration_string property can be used to set or
retrieve the stock’s concentration from a sting. This is usually used to read the concentration from a spreadsheet or
to display it in a human-readable fashion. If you want it to be easily readable, make sure to set the correct unit.
>>> s.concentration_string
'1.00 M'
>>> s.concentration_string = '1 mM'
>>> s.concentration_string
'1.00 mM'
>>> f'{s.micromolarity:,}'
'1,000.0'
>>> s.micromolarity = s.micromolarity
>>> s.concentration_string
'1000.00 µM'
Setting Concentrations Directly
You can also set the concentration and
concentration_unit_id/concentration_unit_name
properties directly. This can be useful if the value and unit are split between spreadsheet columns or if you need a unit
not covered by the defined properties above.
>>> rows = [{'concentration': 27, 'units': 'ng/μL'}]
>>> for row in rows:
>>> s.concentration = row['concentration']
>>> s.concentration_unit_name = row['units']
>>> s.ng_ul
27.0
>>> s.nanomolarity
7.385347809125631
Plates and Boxes
Stocks’ storage locations can be described with the Box and Plate classes. Plate
is a subclass of Box and both are subclasses of HasWells,
which provides methods for adding/retrieving stocks and converting between well names, LabGuru well numbering, and
Tecan well numbering. Example location IDs for a standard 96-well layout in each convention are shown below.
Numbering Convention |
A1 |
B1 |
C1 |
A2 |
A3 |
H12 |
|---|---|---|---|---|---|---|
By well name (short) |
A1 |
B1 |
C1 |
A2 |
A3 |
H12 |
By well name (long) |
A01 |
B01 |
C01 |
A02 |
A03 |
H12 |
By LabGuru position |
1 |
13 |
25 |
2 |
3 |
96 |
By Tecan position |
1 |
2 |
3 |
9 |
17 |
96 |
Note
The module’s code is based on the “LabGuru position” convention. Using methods for another convention simply converts the position to the LabGuru convention and then calls the LabGuru version of the method. So, if there isn’t a reason to use well names or Tecan positions, it’s faster to use the LabGuru convention.
Note
Both Box and Plate map to the Box collection in LabGuru. LabGuru also has
a Plate collection, which are used as part of the ELN and very much not useful for inventory management. Trust us,
we tried…
Creating a Plate/Box
Creating a new Plate or Box is handled the same way as adding a new Collections item (see Adding New Items).
The item’s size will default to an 8x12 standard 96-well plate. If you want something else, you need to include the
rows and cols keywords in the make_new() call. For instance, if you had
a new 9x9 box for 1.5/2 mL tubes, you would instantiate it like this:
new_box = Box.make_new(name='My Next Box', rows=9, cols=9)
new_box.lg_sync()
Instead of the name attribute, Plate objects should be instantiated with:
Attribute |
Usage |
|---|---|
The scannable and/ or human-readable barcode (this will map to |
|
A human-readable description of the plate. It should be written on the physical plate somewhere. |
|
The type of plate you’re storing. ie) Bio-Rad 96-well PCR, Echo 384-well PP. It should help someone find it in storage. |
Adding Samples to a Plate
Adding things into wells/positions on a Plate is easily done with a few helper methods. All of these methods will
return a Stock object that has already been synced to LabGuru. There’s no need to run lg_sync() unless you
update it further. A table of helper functions by position convention is listed below:
Class you want to add |
well_name |
LabGuru numbering |
Tecan numbering |
|---|---|---|---|
Getting Samples from a Plate
Retrieving samples from a Plate object can be achieved using various helper methods provided by the API. These methods
allow you to access samples in specific wells or positions based on the numbering convention you use. Each method will
return a list of Stock object that represents the samples stored in the respective position. The available helper
methods are listed in the table below:
Position Convention |
Well Name |
LabGuru Numbering |
Tecan Numbering |
|---|---|---|---|
Method |
The example below illustrates how to retrieve a stock object from a specific well in a Plate using different numbering
conventions.
# Retrieve a plate object first
my_plate = Plate.from_name('Example Plate')
# Get a sample using the well name (e.g., A1)
stock = my_plate.get_stock_from_well('A1')[0]
print(stock.name)
# Get a sample using LabGuru numbering (e.g., position 1)
stock = my_plate.get_stock_from_position(1)[0]
print(stock.name)
# Get a sample using Tecan numbering (e.g., position 2)
stock = my_plate.get_stock_from_tecan_position(2)[0]
print(stock.name)
Iterating through a Plate
You can use the Plate class’s iter_well_names(),
iter_positions(), and iter_tecan_positions()
methods to iterate over positions in a plate.
For example, you could use these methods to add a strain to every well of an overnight plate.
s = Strain.from_name('GRO24-6734')
plate = Plate.add_new(barcode='0000-ONC-001', label='3xU Overnight plate', plate_type='2mL Deepwell 96')
for pos in plate.iter_positions():
plate.add_sample_to_position(s, pos)
You can also use these methods to get plasmids from a transformation plate and generate daughter strains.
base_strain = Strain.from_name('GRO24-6763')
plas_plate = Plate.from_barcode('0000-PLA-003')
out_plate = Plate.add_new(barcode='0000-CCT-003', label='pAMF expression transformations', plate_type='Bio-Rad 96-well PCR')
for pos in plas_plate.iter_positions():
stocks = plas_plate.stocks_from_position(pos)[0]
if not (stocks and isinstance(stocks[0], Plasmid)):
continue
plas = stocks[0]
daughter = base_strain.make_new_derived_strain('next_name', plas.name, additonal_plasmids=plas.name)
daughter.lg_sync()
Working with Reactions in Plates
Each well in a Plate can only hold 1 stock. This is usually fine in the context of inventory management, but is quite
limiting when looking at plates that are used in reactions because they have many components we may want to track. To
enable this type of tracking, we implemented a number of methods that can add a stock to a plate while assigning it a
reaction role. Those roles are:
Role |
Description |
|---|---|
Reactant |
Collection items that are consumed during the reaction. |
Product |
The major or desired product of the reaction. |
Side Product |
Other products of the reaction. |
Catalyst |
A collection item that aids in the reaction, but is neither consumed nor destroyed (enzymes) |
Bystander |
A collection item that does not participate in the reaction (buffers) |
To add reaction components into a Plate, use the following methods depending on the numbering convention:
Component |
Well Name |
LabGuru Numbering |
Tecan Numbering |
|---|---|---|---|
Reactants |
|||
Products |
|||
Side Products |
|||
Catalysts |
|||
Bystanders |
Here’s an example of how to add reactants, products, and side products using different conventions:
# Retrieve a plate object first
my_plate = Plate.from_name('Example Plate')
# Add a reactant to well A1
my_plate.add_reactant_to_well('A1', reactant_name='Reactant A')
# Add a product using LabGuru numbering (e.g., position 1)
my_plate.add_product_to_position(1, product_name='Product B')
# Add a side product using Tecan numbering (e.g., position 5)
my_plate.add_side_product_to_tecan_position(5, side_product_name='Side Product C')
To retrieve reaction components from wells or positions in a Plate, use the following helper methods:
Component |
Well Name |
LabGuru Numbering |
Tecan Numbering |
|---|---|---|---|
Reactants |
|||
Products |
|||
Side Products |
|||
Catalysts |
|||
Bystanders |
Here’s an example of how to retrieve reactants, products, and side products using different numbering conventions:
# Retrieve a plate object first
my_plate = Plate.from_name('Example Plate')
# Retrieve reactants from well A1
reactants = my_plate.reactants_from_well('A1')
print([reactant.name for reactant in reactants])
# Retrieve products from position 1 (LabGuru numbering)
products = my_plate.products_from_position(1)
print([product.name for product in products])
# Retrieve side products from position 5 (Tecan numbering)
side_products = my_plate.side_products_from_tecan_position(5)
print([side_product.name for side_product in side_products])