Source code for s4.clarity.container

# Copyright 2016 Semaphore Solutions, Inc.
# ---------------------------------------------------------------------------

from ._internal import WrappedXml, ClarityElement, FieldsMixin
from s4.clarity._internal.props import subnode_link, subnode_property, subnode_element
from s4.clarity import types, lazy_property


[docs]class ContainerDimension(WrappedXml): is_alpha = subnode_property("is-alpha", types.BOOLEAN) offset = subnode_property("offset", types.NUMERIC) # type: float size = subnode_property("size", types.NUMERIC) # type: float
[docs] @lazy_property def dimension_range(self): """ List of the labels for the given dimension :return: list[int]|list[str] """ # Cast these to integers from floats to avoid deprecation warnings from range. start = int(self.offset) end = int(self.offset + self.size) if not self.is_alpha: return list(range(start, end)) else: return list(map(chr, range(65 + start, 65 + end)))
[docs] def as_index(self, label): if label.isdigit(): return int(label) - int(self.offset) else: return ord(label[0]) - 65 - int(self.offset)
[docs] def as_label(self, index): if self.is_alpha: return chr(65 + index + int(self.offset)) else: return str(index + int(self.offset))
[docs]class ContainerType(ClarityElement): """ A class to handle container types, with helper functions to create and encode well positions For the purposes of this class, the y-dimension is considered the columns, and the x-dimension is considered the rows. """ UNIVERSAL_TAG = "{http://genologics.com/ri/containertype}container-type" is_tube = subnode_property("is-tube", types.BOOLEAN) # type: bool x_dimension = subnode_element(ContainerDimension, "x-dimension") # type: ContainerDimension y_dimension = subnode_element(ContainerDimension, "y-dimension") # type: ContainerDimension
[docs] def well_to_rc(self, well): """ Converts a Clarity well position to the zero based index of the row and column. Example:: 'B:4' -> (1, 3) :param well: A Clarity formatted well position :type well: str :return: The zero based index of the row and the column. :rtype: tuple[int] """ location_pieces = well.split(":") return self.y_dimension.as_index(location_pieces[0]), self.x_dimension.as_index(location_pieces[1])
[docs] def rc_to_well(self, rc): """ Converts a zero based index of the row and column to a Clarity well position. Example:: (1, 3) -> 'B:4' :param rc: The zero based index of the row and the column. :type rc: tuple[int] :return: A Clarity formatted well position :rtype: str """ return "%s:%s" % (self.y_dimension.as_label(rc[0]), self.x_dimension.as_label(rc[1]))
[docs] def row_major_order_wells(self): """ Returns wells in the container type in row major order. This will return the wells ordered: ["y1:x1", "y1:x2", "y1:x3", [...], "y1,xn", "y2:x1", "y2:x2", [...]] Unavailable wells are omitted. :rtype: list[str] """ l = [] for y in self.y_dimension.dimension_range: for x in self.x_dimension.dimension_range: well_name = "%s:%s" % (str(y), str(x)) if well_name not in self.unavailable_wells: l.append(well_name) return l
[docs] def column_major_order_wells(self): """ Returns wells in the container type in column major order. This will return the wells ordered: ["y1:x1", "y2:x1", "y3:x1", [...], "yn:x1", "y1:x2", "y2:x2", [...]] Unavailable wells are omitted. :rtype: list[str] """ l = [] for x in self.x_dimension.dimension_range: for y in self.y_dimension.dimension_range: well_name = "%s:%s" % (str(y), str(x)) if well_name not in self.unavailable_wells: l.append(well_name) return l
[docs] @lazy_property def unavailable_wells(self): """ :type: set[str] """ unavailable_well_nodes = self.xml_findall("unavailable-well") return set(node.text for node in unavailable_well_nodes)
[docs] @lazy_property def total_capacity(self): """ :type: int """ return len(self.x_dimension.dimension_range) * len(self.y_dimension.dimension_range) - len(self.unavailable_wells)
[docs] def row_order_wells(self): """ :deprecated: use :class:`ContainerType.row_major_order_wells()` instead. """ return self.row_major_order_wells()
[docs] def column_order_wells(self): """ :deprecated: use :class:`ContainerType.column_major_order_wells()` instead. """ return self.column_major_order_wells()
[docs] def x_dimension_range(self): """ :deprecated: use :class:`ContainerType.x_dimension.dimension_range` instead """ return self.x_dimension.dimension_range
[docs] def y_dimension_range(self): """ :deprecated: use :class:`ContainerType.y_dimension.dimension_range` instead """ return self.y_dimension.dimension_range
[docs]class Container(FieldsMixin, ClarityElement): UNIVERSAL_TAG = "{http://genologics.com/ri/container}container" ATTACH_TO_NAME = "Container" container_type = subnode_link(ContainerType, "type", attributes=('name', 'uri')) occupied_wells = subnode_property("occupied-wells", typename=types.NUMERIC, readonly=True) state = subnode_property("state", typename=types.STRING, readonly=True) @property def type_name(self): """ Read-only shortcut to containertype name, which we know without doing another GET. :type: str """ typenode = self.xml_find('./type') return typenode.get('name') @property def placements(self): """ Dict of string "Y:X" -> Artifacts. :type: dict[str, Artifact] """ return self.xml_all_as_dict("placement", lambda n: n.find("value").text, # key lambda n: self.lims.artifacts.from_link_node(n) # value )
[docs] def artifact_at(self, well): """ :param well: String matching "Y:X" where Y is a column index and X is a row index. The string may use letters or numbers depending on the container type. :type well: str :rtype: Artifact or None """ try: return self.placements[well] except KeyError: raise KeyError("Container '%s' has no artifact at '%s'." % (self.name, well))