Source code for plateo.Well

from box import Box

[docs]class TransferError(ValueError): pass
[docs]class WellContent: """Class to represent the volume and quantities of a well. Having the well content represented as a separate object makes it possible to have several wells share the same content, e.g. in throughs. """ def __init__(self, quantities=None, volume=0): if quantities is None: quantities = {} self.volume = volume self.quantities = Box(quantities) def concentration(self, component=None, default=0): if self.quantities == {}: return default if self.volume == 0: return default if component is None: component = list(self.quantities.keys())[0] if component not in self.quantities: return default return 1.0 * self.quantities[component] / self.volume
[docs] def to_dict(self): """Return a dict {volume: 0.0001, quantities: {...:...}}""" return { "volume": self.volume, "quantities": self.quantities }
def make_empty(self): self.volume = 0 self.quantities = Box({})
[docs] def components_as_string(self, separator=" "): """Return a string representation of what's in the well mix""" return separator.join(sorted(self.quantities.keys()))
[docs]class Well: """Generic class for a well. Parameters ---------- plate The plate on which the well is located row The well's row (a number, starting from 0) column The well's column (a number, starting from 0) name The well's name, for instance "A1" data A dictionnary storing data on the well, used in algorithms and reports. """ capacity = None def __init__(self, plate, row, column, name, data=None): self.plate = plate self.row = row self.column = column self.name = name self.data = Box({} if data is None else data) self.sources = [] self.content = WellContent() @property def volume(self): return self.content.volume def iterate_sources_tree(self): for source in self.sources: if isinstance(source, Well): for parent in source.iterate_sources_tree(): yield parent else: yield source yield self def transfer_to_other_well(self, destination_well, transfer_volume): if self.is_empty: raise TransferError( "Transfer %s => %s impossible: %s is empty" % ( self, destination_well, self)) factor = float(transfer_volume) / self.volume # pre-check in both source and destination wells that transfers # are valid if factor > 1: raise TransferError( ("Substraction of %.2e L from %s impossible." " Current volume: %.2e L") % (transfer_volume, self, self.volume) ) final_destination_volume = destination_well.volume + transfer_volume if ((destination_well.capacity is not None) and (final_destination_volume > destination_well.capacity)): raise TransferError( "Transfer of %.2e L from %s to %s brings volume over capacity." % (transfer_volume, self, destination_well) ) # If you arrive here, it means that the transfer is valid, do it. quantities_transfered = { component: quantity * factor for component, quantity in self.content.quantities.items() } destination_well.add_content(quantities_transfered, volume=transfer_volume) self.subtract_content(quantities_transfered, volume=transfer_volume) if self not in destination_well.sources: destination_well.sources.append(self) def add_content(self, components_quantities, volume=None): if volume > 0: final_volume = self.content.volume + volume if (self.capacity is not None) and (final_volume > self.capacity): raise TransferError( "Transfer of %.2e L to %s brings volume over capacity." % (volume, self) ) self.content.volume = final_volume for component, quantity in components_quantities.items(): if component not in self.content.quantities: self.content.quantities[component] = 0 self.content.quantities[component] += quantity def subtract_content(self, components_quantities, volume=0): if volume > 0: if volume > self.volume: raise TransferError( ("Substraction of %.2e L from %s impossible." " Current volume: %.2e L") % (volume, self, self.volume) ) self.content.volume -= volume for component, quantity in components_quantities.items(): if self.content.quantities[component] == quantity: self.content.quantities.pop(component) else: self.content.quantities[component] -= quantity def empty_completely(self): self.content.quantities = {} self.content.volume = 0 @property def coordinates(self): """Return (well.row, well.column)""" return (self.row, self.column) @property def is_empty(self): """Return true iff the well's volume is 0""" return (self.volume == 0) def __repr__(self): return "(%s-%s)" % (self.plate.name, self.name) def pretty_summary(self): data = "\n ".join([""] + [ ("%s: %s" % (key, value)) for key, value in self.data.items()]) content = "\n ".join([""] + [ ("%s: %s" % (key, value)) for key, value in self.content.quantities.items()]) return ( "{self}\n" " Volume: {self.volume}\n" " Content: {content}\n" " Metadata: {data}" ).format(self=self, content=content, data=data) def to_dict(self): return dict( [ ["name", self.name], ["content", self.content.to_dict()], ["row", self.row], ["column", self.column], ] + list(self.data.items()) )
[docs] def index_in_plate(self, direction='row'): """Return the index of the well in the plate.""" return self.plate.wellname_to_index(self.name, direction=direction)
[docs] def is_after(self, other, direction='row'): """Return whether this well is located strictly after the other well. Examples -------- To iterate over all free wells after the last non-free well of a plate: >>> direction = 'row' >>> last_occupied_well = plate.last_nonempty_well(direction=direction) >>> free_wells = (w for w in plate.iter_wells(direction=direction) >>> if w.is_after(last_occupied_well)) >>> for well in free_wells: ... """ well_index = self.index_in_plate(direction=direction) other_index = other.index_in_plate(direction=direction) return well_index > other_index
def __lt__(self, other): return str(self) < str(other)