def _normalize_address(self, address): """ Takes an address like "A1:A1" or "Ingredients:A1" and returns a tuple like ((0, 0), (0, 0)). To retain label names, use humanize_address. """ if ':' not in address: raise ValueError( "Address must be in the form of 'container:well'.") container, well = address.split(':') well = normalize_position(well) try: container = normalize_position(container) except ValueError: # Try to find the slot as a label. container = container.lower() if container not in self._container_labels: raise x.ContainerMissing( "Container not found: {}".format(address)) container = self._container_labels[container] return (container, well)
def _normalize_address(self, address): """ Takes an address like "A1:A1" or "Ingredients:A1" and returns a tuple like (0, 0) or ('ingredients', 0). Container labels are retained in the address tuples so that named containers can be assigned to different slots within the user interface. """ if ':' not in address: raise ValueError( "Address must be in the form of 'container:well'." ) container, well = address.split(':') well = normalize_position(well) try: container = normalize_position(container) except ValueError: container = container.lower() if container not in self._container_labels: raise KeyError("Container not found: {}".format(container)) return (container, well)
def _normalize_address(self, address): """ Takes an address like "A1:A1" or "Ingredients:A1" and returns a tuple like ((0, 0), (0, 0)). To retain label names, use humanize_address. """ if ':' not in address: raise ValueError( "Address must be in the form of 'container:well'." ) container, well = address.split(':') well = normalize_position(well) try: container = normalize_position(container) except ValueError: # Try to find the slot as a label. container = container.lower() if container not in self._container_labels: raise x.ContainerMissing( "Container not found: {}".format(address) ) container = self._container_labels[container] return (container, well)
def test_normalize_position(self): """ Normalize coordinate strings ('A1') """ expected = normalize_position('A1') self.assertEqual(expected, (0, 0)) expected = normalize_position('B1') self.assertEqual(expected, (1, 0)) expected = normalize_position('C2') self.assertEqual(expected, (2, 1))
def humanize_address(self, address): """ Returns a human-readable string for a particular address. If ((0, 0), (1, 0)) is passed and no labels are attached to A1, this will return 'A1:B1'. For ('label', (1, 0)), it will return the valid label with the first provided capitalization, for example "LaBeL:B1". """ start, end = address try: start = normalize_position(start) # Try to convert 'A1'. # Find a label for that tuple position. label = self.get_container_label(start) if label is not None: start = label except ValueError: # If it's not a tuple position, it's a string label. if start.lower() not in self._container_labels: raise x.ContainerMissing( "Invalid container: {}".format(start) ) start = self._label_case.get(start.lower(), start) end = humanize_position(end) return "{}:{}".format(start, end)
def get_plate(self, label_cell, **kwargs): """ Takes the 'label cell' of a plate definition and returns a Plate object set to that particular starting position within the CSV. Then you can use Plate to get the string contents of particular wells on that plate within the definition file. For example: plate_map.get_plate('K2').get_well('H12') Where 'K2' is the spreadsheet cell containing the plate label and 'H12' is the specific well in that plate. """ # If the label_cell is in labels, use that position instead of # converting it to a coordinate tuple. # # Only works for 96 well plates for now. if label_cell in self._labels: label_cell = self._labels[label_cell] # Pass default orientation option. if 'rotated' not in kwargs and self._rotated is not None: kwargs['rotated'] = self._rotated col, row = normalize_position(label_cell) start = (col, row) if start not in self._plates: self._plates[start] = Plate(start, self._sheet, **kwargs) return self._plates[start]
def add_plate(self, label, start, **kwargs): """ Allows adding plates by reference along with config vars. """ start = normalize_position(start) self._labels[label] = start self._plates[start] = self.get_plate(start, **kwargs)
def add_container(self, slot, name, label=None): slot = normalize_position(slot) self._context_handler.add_container(slot, name) self._containers[slot] = name if (label): label = label.lower() self._container_labels[label] = slot
def test_nonletter_colum(self): """ Exception on invalid coordinate string (']1'). """ # Make sure the entire valid range works. normalize_position('A1') normalize_position('Z1') # Test out of range (@ and ] are the edges of A-Z in ASCII). with self.assertRaises(ValueError): normalize_position(']1') with self.assertRaises(ValueError): normalize_position('@1')
def add_container(self, slot, name, label=None): slot = normalize_position(slot) if (label): lowlabel = label.lower() if lowlabel in self._container_labels: raise x.ContainerConflict( "Label already in use: {}".format(label)) # Maintain label capitalization, but only one form. if lowlabel not in self._label_case: self._label_case[lowlabel] = label self._container_labels[lowlabel] = slot self._context_handler.add_container(slot, name) self._containers[slot] = name
def add_container(self, slot, name, label=None): slot = normalize_position(slot) if (label): lowlabel = label.lower() if lowlabel in self._container_labels: raise x.ContainerConflict( "Label already in use: {}".format(label) ) # Maintain label capitalization, but only one form. if lowlabel not in self._label_case: self._label_case[lowlabel] = label self._container_labels[lowlabel] = slot self._context_handler.add_container(slot, name) self._containers[slot] = name
def _get_slot(self, name): """ Returns a container within a given slot, can take a slot position as a tuple (0, 0) or as a user-friendly name ('A1') or as a label ('ingredients'). """ slot = None try: slot = normalize_position(name) except TypeError: if slot in self._container_labels: slot = self._container_labels[slot] if not slot: raise KeyError("Slot not defined: " + name) if slot not in self._deck: raise KeyError("Nothing in slot: " + name) return self._deck[slot]
def get_well(self, well): """ Returns the contents of the cell of the CSV range attached to this particular plate mapping to the given well coordinates. For example, well A1 might actually be B36 in the CSV. Contents are simply whatever string is stored in the cell of that particular location in the Excel-based plate mapping. """ col, row = normalize_position(well) scol, srow = self._start if (self._rotated): cell_col = scol + col + 1 cell_row = srow + self._rows - row else: cell_row = srow + col + 1 cell_col = scol + row + 1 return self._sheet.get_cell((cell_col, cell_row))
def _get_slot(self, name): """ Returns a container within a given slot, can take a slot position as a tuple (0, 0) or as a user-friendly name ('A1') or as a label ('ingredients'). """ slot = None try: slot = normalize_position(name) except TypeError: # Try to find the slot as a label. if slot in self._container_labels: slot = self._container_labels[slot] if not slot: raise x.MissingContainer("No slot defined for {}".format(name)) if slot not in self._deck: raise x.MissingContainer("Nothing in slot: {}".format(name)) return self._deck[slot]
def convert_legacy_container(container): """ Takes the dict from an old-style legacy containers.json format and converts it to the new format for serializing to YAML. """ lines = [] wells = container['locations'] a1 = wells.get('A1') b1 = wells.get('B1', {}) a2 = wells.get('A2', {}) out = {} out['volume'] = a1.get('total-liquid-volume', 0) out['diameter'] = a1.get('diameter', 0) out['well_depth'] = a1.get('depth', 0) out['height'] = a1.get('depth', 0) out['a1_x'] = container.get('origin-offset', {'x': 0}).get('x') out['a1_y'] = container.get('origin-offset', {'y': 0}).get('y') spacing_x = b1.get('x', 0) spacing_y = a2.get('y', 0) if spacing_x == spacing_y: out['spacing'] = spacing_y else: out['col_spacing'] = spacing_x out['row_spacing'] = spacing_y max_col, max_row = normalize_position(max(wells.keys())) out['rows'] = max_row + 1 out['cols'] = max_col + 1 return out
def humanize_address(self, address): """ Returns a human-readable string for a particular address. If ((0, 0), (1, 0)) is passed and no labels are attached to A1, this will return 'A1:B1'. For ('label', (1, 0)), it will return the valid label with the first provided capitalization, for example "LaBeL:B1". """ start, end = address try: start = normalize_position(start) # Try to convert 'A1'. # Find a label for that tuple position. label = self.get_container_label(start) if label is not None: start = label except ValueError: # If it's not a tuple position, it's a string label. if start.lower() not in self._container_labels: raise x.ContainerMissing("Invalid container: {}".format(start)) start = self._label_case.get(start.lower(), start) end = humanize_position(end) return "{}:{}".format(start, end)
def convert_legacy_container(container): """ Takes the dict from an old-style legacy containers.json format and converts it to the new format for serializing to YAML. """ wells = container['locations'] a1 = wells.get('A1') b1 = wells.get('B1', {}) a2 = wells.get('A2', {}) out = {} out['volume'] = a1.get('total-liquid-volume', 0) out['diameter'] = a1.get('diameter', 0) out['well_depth'] = a1.get('depth', 0) out['height'] = a1.get('depth', 0) out['a1_x'] = container.get('origin-offset', {'x': 0}).get('x') out['a1_y'] = container.get('origin-offset', {'y': 0}).get('y') spacing_x = b1.get('x', 0) spacing_y = a2.get('y', 0) if spacing_x == spacing_y: out['spacing'] = spacing_y else: out['col_spacing'] = spacing_x out['row_spacing'] = spacing_y max_col, max_row = normalize_position(max(wells.keys())) out['rows'] = max_row + 1 out['cols'] = max_col + 1 return out
def get_cell(self, position): col, row = normalize_position(position) return self._data[row][col]
def test_mistyped_tuple(self): """ Raise exception on mistyped tuple (char, int). """ with self.assertRaises(TypeError): normalize_position(('a', 1))
def test_tuple(self): """ Passthrough normalization of 2-member tuple. """ expected = normalize_position((2, 1)) self.assertEqual(expected, (2, 1))
def test_long_tuple(self): """ Raise exception on three-member tuple. """ with self.assertRaises(TypeError): normalize_position((1, 2, 3))
def calibrate(self, position, **kwargs): if ':' in position: pos = self._normalize_address(position) else: pos = normalize_position(position) self._context_handler.calibrate(pos, **kwargs)
def test_short_tuple(self): """ Raise exception on one-member tuple. """ with self.assertRaises(TypeError): normalize_position((1))
def test_invalid_coordinate_string(self): """ Exception on invalid coordinate string ('11'). """ with self.assertRaises(ValueError): normalize_position('11')
def test_lowercase(self): """ Normalize lowercase coordinate strings ('a1') """ expected = normalize_position('c2') self.assertEqual(expected, (2, 1))
def test_multidigit_row(self): """ Multiple digits in the coordinate row ('b222') """ expected = normalize_position('b222') self.assertEqual(expected, (1, 221))