def get_sector_positions(sector_index, rack_shape, number_sectors, row_count=None, col_count=None): """ A helper function return the positions of the given sector. :param int sector_index: Sector index assuming Z-configuration. :param int number_sectors: The total number of sectors. :param rack_shape: The rack shape to be considered. :type rack_shape: :class:`thelma.entities.rack.RackShape :param int row_count: The number of sector rows - if you do not provide a number the row number is calculated assuming a square setup. :param int col_count: The number of sector columns - if you do not provide a number the row number is calculated assuming a square setup. """ translator = RackSectorTranslator(number_sectors=number_sectors, source_sector_index=sector_index, target_sector_index=0, row_count=row_count, col_count=col_count, behaviour=RackSectorTranslator.MANY_TO_MANY) positions = [] for rack_pos in get_positions_for_shape(rack_shape): # Filter for positions try: translator.translate(rack_pos) except ValueError: continue else: positions.append(rack_pos) return positions
def get_positions_without_tube(self): """ Returns the positions in the rack that are not occupied at the moment. """ shape_96 = get_96_rack_shape() free_positions = [] for rack_pos in get_positions_for_shape(shape_96): if not self.tubes.has_key(rack_pos): free_positions.append(rack_pos) return free_positions
def _init_source_data(self): """ Initialises the source rack related values and lookups. """ for rack_pos in get_positions_for_shape(self.reservoir_specs.rack_shape): self._source_volumes[rack_pos] = 0 self._source_dead_volume = self.reservoir_specs.min_dead_volume \ * VOLUME_CONVERSION_FACTOR self._source_max_volume = self.reservoir_specs.max_volume \ * VOLUME_CONVERSION_FACTOR self.__emtpy_pos_manager = EmptyPositionManager( rack_shape=self.reservoir_specs.rack_shape)
def _init_source_data(self): """ Initialises the source rack related values and lookups. """ for rack_pos in get_positions_for_shape( self.reservoir_specs.rack_shape): self._source_volumes[rack_pos] = 0 self._source_dead_volume = self.reservoir_specs.min_dead_volume \ * VOLUME_CONVERSION_FACTOR self._source_max_volume = self.reservoir_specs.max_volume \ * VOLUME_CONVERSION_FACTOR self.__emtpy_pos_manager = EmptyPositionManager( rack_shape=self.reservoir_specs.rack_shape)
def __get_layout(self): # Fetches the stock sample layout and sorts its positions into # quadrants. self.add_debug("Fetch stock sample layout ...") converter = StockSampleCreationLayoutConverter(self.entity.rack_layout, parent=self) self.__ssc_layout = converter.get_result() if self.__ssc_layout is None: msg = "Error when trying to convert stock sample creation ISO " "layout." self.add_error(msg) else: ssc_positions = self.__ssc_layout.get_positions() for rack_pos in get_positions_for_shape(RACK_SHAPE_NAMES.SHAPE_96): if not rack_pos in ssc_positions: self.__ignore_positions.append(rack_pos)
def __build_preparation_plate_map(self): for spp in self.iso.iso_sector_preparation_plates: self.__prep_plate_map[spp.sector_index] = spp # Record empty positions to ignore later when building the # dilution jobs. cnv = StockSampleCreationLayoutConverter(spp.rack_layout, parent=self) ssc_layout = cnv.get_result() self.__ssc_layout_map[spp.sector_index] = ssc_layout all_poss = get_positions_for_shape(RACK_SHAPE_NAMES.SHAPE_96) empty_poss = \ list(set(all_poss).difference(ssc_layout.get_positions())) self.__empty_stock_rack_positions_map[spp.sector_index] = \ empty_poss
def __verify_pool_stock_rack(self): # Makes sure there are empty tubes in all required positions and none # in positions that must be empty. converter = PoolCreationStockRackLayoutConverter(self.__pool_stock_rack.rack_layout, parent=self) layout = converter.get_result() if layout is None: msg = "Error when trying to convert pool stock rack layout!" self.add_error(msg) else: additional_tubes = [] non_empty_tube = [] positions = layout.get_positions() tube_positions = set() rack = self.__pool_stock_rack.rack for tube in rack.containers: rack_pos = tube.location.position tube_positions.add(rack_pos) info = "%s (%s)" % (tube.barcode, rack_pos.label) if not rack_pos in positions: additional_tubes.append(info) elif not tube.sample is None: non_empty_tube.append(info) missing_tube = [] for rack_pos in get_positions_for_shape(layout.shape): sr_pos = layout.get_working_position(rack_pos) if sr_pos is None: continue elif rack_pos not in tube_positions: missing_tube.append(rack_pos.label) if len(additional_tubes) > 0: msg = ( "There are unexpected tubes in the pool stock rack " "(%s): %s. Please remove them and try again." % (rack.barcode, self._get_joined_str(additional_tubes)) ) self.add_error(msg) if len(non_empty_tube) > 0: msg = ( "The following tubes in the pool stock rack (%s) are " "not empty: %s. Please replace them by empty tubes and " "try again." % (rack.barcode, self._get_joined_str(non_empty_tube)) ) self.add_error(msg) if len(missing_tube) > 0: msg = "There are tubes missing in the following positions " "of the pool stock rack (%s): %s." % ( rack.barcode, self._get_joined_str(missing_tube), ) self.add_error(msg)
def __get_layout(self): # Fetches the stock sample layout and sorts its positions into # quadrants. self.add_debug('Fetch stock sample layout ...') converter = StockSampleCreationLayoutConverter(self.entity.rack_layout, parent=self) self.__ssc_layout = converter.get_result() if self.__ssc_layout is None: msg = 'Error when trying to convert stock sample creation ISO ' \ 'layout.' self.add_error(msg) else: ssc_positions = self.__ssc_layout.get_positions() for rack_pos in get_positions_for_shape(RACK_SHAPE_NAMES.SHAPE_96): if not rack_pos in ssc_positions: self.__ignore_positions.append(rack_pos)
def _create_iso_layout(self): """ Creates a :class:`StockSampleCreationLayout` for a single ISO. Positions are populated column-wise. """ ssc_layout = StockSampleCreationLayout() for rack_pos in get_positions_for_shape(ssc_layout.shape, vertical_sorting=True): if not self._have_candidates: break pool_cand = self._pool_candidates.pop(0) ssc_pos = \ StockSampleCreationPosition(rack_pos, pool_cand.pool, pool_cand.get_tube_barcodes()) ssc_layout.add_position(ssc_pos) return ssc_layout
def __verify_pool_stock_rack(self): # Makes sure there are empty tubes in all required positions and none # in positions that must be empty. converter = PoolCreationStockRackLayoutConverter( self.__pool_stock_rack.rack_layout, parent=self) layout = converter.get_result() if layout is None: msg = 'Error when trying to convert pool stock rack layout!' self.add_error(msg) else: additional_tubes = [] non_empty_tube = [] positions = layout.get_positions() tube_positions = set() rack = self.__pool_stock_rack.rack for tube in rack.containers: rack_pos = tube.location.position tube_positions.add(rack_pos) info = '%s (%s)' % (tube.barcode, rack_pos.label) if not rack_pos in positions: additional_tubes.append(info) elif not tube.sample is None: non_empty_tube.append(info) missing_tube = [] for rack_pos in get_positions_for_shape(layout.shape): sr_pos = layout.get_working_position(rack_pos) if sr_pos is None: continue elif rack_pos not in tube_positions: missing_tube.append(rack_pos.label) if len(additional_tubes) > 0: msg = 'There are unexpected tubes in the pool stock rack ' \ '(%s): %s. Please remove them and try again.' \ % (rack.barcode, self._get_joined_str(additional_tubes)) self.add_error(msg) if len(non_empty_tube) > 0: msg = 'The following tubes in the pool stock rack (%s) are ' \ 'not empty: %s. Please replace them by empty tubes and ' \ 'try again.' % (rack.barcode, self._get_joined_str(non_empty_tube)) self.add_error(msg) if len(missing_tube) > 0: msg = 'There are tubes missing in the following positions ' \ 'of the pool stock rack (%s): %s.' % (rack.barcode, self._get_joined_str(missing_tube)) self.add_error(msg)
def get_empty_position(self): """ Returns the position with the lowest column and row index (and removes it from the pool). :raises ValueError: If there is no empty position left. """ picked_position = None for rack_pos in get_positions_for_shape(self.rack_shape): if rack_pos in self._all_empty_positions: picked_position = rack_pos break if picked_position is None: raise ValueError('No empty position left!') self._all_empty_positions.discard(picked_position) return picked_position
def __create_transfers(self): # Creates a :class:`PlannedSampleDilution` for each rack position # in a 8x12 rack shape. self.add_debug('Create transfers ...') self._run_and_record_error(self.volume_calculator.calculate, 'Error when trying to determine buffer volume: ', ValueError) buffer_volume = self.volume_calculator.get_buffer_volume() if buffer_volume is not None: volume = buffer_volume / VOLUME_CONVERSION_FACTOR wl_label = LABELS.create_buffer_worklist_label( self.iso_request_label) wl = PlannedWorklist(wl_label, TRANSFER_TYPES.SAMPLE_DILUTION, get_pipetting_specs_cybio()) for rack_pos in get_positions_for_shape(RACK_SHAPE_NAMES.SHAPE_96): psd = PlannedSampleDilution.get_entity(volume=volume, target_position=rack_pos, diluent_info=DILUENT_INFO) wl.planned_liquid_transfers.append(psd) self.__worklist_series.add_worklist(self.BUFFER_WORKLIST_INDEX, wl)
def compare_ignoring_untreated_types(layout1, layout2): """ Compares two transfection layouts ignoring potential untreated positions. """ if layout1.shape != layout2.shape: return False for rack_pos in get_positions_for_shape(layout1.shape): tf1 = layout1.get_working_position(rack_pos) tf2 = layout2.get_working_position(rack_pos) if tf1 is None and tf2 is None: continue if tf1 is not None and (tf1.is_untreated_type or tf1.is_empty): tf1 = None if tf2 is not None and (tf2.is_untreated_type or tf2.is_empty): tf2 = None if tf1 is None and tf2 is None: continue elif not tf1 == tf2: return False return True
def __create_transfers(self): # Creates a :class:`PlannedSampleDilution` for each rack position # in a 8x12 rack shape. self.add_debug('Create transfers ...') self._run_and_record_error( self.volume_calculator.calculate, 'Error when trying to determine buffer volume: ', ValueError) buffer_volume = self.volume_calculator.get_buffer_volume() if buffer_volume is not None: volume = buffer_volume / VOLUME_CONVERSION_FACTOR wl_label = LABELS.create_buffer_worklist_label( self.iso_request_label) wl = PlannedWorklist(wl_label, TRANSFER_TYPES.SAMPLE_DILUTION, get_pipetting_specs_cybio()) for rack_pos in get_positions_for_shape(RACK_SHAPE_NAMES.SHAPE_96): psd = PlannedSampleDilution.get_entity( volume=volume, target_position=rack_pos, diluent_info=DILUENT_INFO) wl.planned_liquid_transfers.append(psd) self.__worklist_series.add_worklist(self.BUFFER_WORKLIST_INDEX, wl)
def __create_layout_from_map(self): """ Creates the actual working layout object. """ self.add_debug('Convert position map into working layout.') working_layout = self._initialize_working_layout(self.rack_layout.shape) for rack_position in get_positions_for_shape(self.rack_layout.shape): working_position = self.__position_map[rack_position] if working_position is None: continue self._run_and_record_error(working_layout.add_position, base_msg='Error when trying to add position to layout: ', error_types=set([ValueError, AttributeError, KeyError, TypeError]), **dict(working_position=working_position)) if self.has_errors(): break if self.has_errors(): return None self._perform_layout_validity_checks(working_layout) if self.has_errors(): return None return working_layout
def __get_values_for_rack_layout(self, validators, rack_layout): """ Finds the parameters values for each position in a design rack layout. """ shape_positions = get_positions_for_shape(self._rack_shape) # Initialise the value maps value_maps = dict() for parameter in validators.keys(): rack_pos_dict = dict() for rack_pos in shape_positions: rack_pos_dict[rack_pos] = None value_maps[parameter] = rack_pos_dict for trps in rack_layout.tagged_rack_position_sets: for tag in trps.tags: for parameter, validator in validators.iteritems(): if validator.has_alias(tag.predicate): value_map = value_maps[parameter] for rack_pos in trps.rack_position_set: value_map[rack_pos] = tag.value return value_maps
def run(self): """ Runs the conversion. """ self.reset() self.add_info('Start conversion ...') self._check_input() if not self.has_errors(): self._initialize_parameter_validators() self._initialize_other_attributes() self.__check_parameter_completeness() if not self.has_errors(): for rack_position in get_positions_for_shape(self.rack_layout.shape): tag_set = self.rack_layout.get_tags_for_position(rack_position) parameter_map = self._get_parameter_map(tag_set, rack_position) working_position = self.__obtain_working_position(parameter_map) self.__position_map[rack_position] = working_position self._record_errors() if not self.has_errors(): self.return_value = self.__create_layout_from_map() self.add_info('Layout conversion completed.')
def __check_value_presence(self, value_maps, label): """ Checks the presence of mandatory, optional and forbidden parameters for each rack position in a layout. """ pool_map = value_maps[TransfectionParameters.MOLECULE_DESIGN_POOL] pool_forbidden = TransfectionParameters.MOLECULE_DESIGN_POOL in \ self.__forbidden_parameters non_empty = [] missing_value = dict() additional_values = dict() inconsistent_optionals = dict() for rack_pos in get_positions_for_shape(self._rack_shape): pool_id = pool_map[rack_pos] is_untreated = (isinstance(pool_id, basestring) and \ TransfectionParameters.is_untreated_type(pool_id.lower())) if is_untreated: continue if pool_id is None: # Empty position should not have values for parameter, value_map in value_maps.iteritems(): if not value_map[rack_pos] is None: non_empty.append(rack_pos.label) break if not pool_forbidden: continue for parameter, value_map in value_maps.iteritems(): if parameter == TransfectionParameters.MOLECULE_DESIGN_POOL: continue elif pool_id == MOCK_POSITION_TYPE and \ parameter == TransfectionParameters.FINAL_CONCENTRATION: continue value = value_map[rack_pos] # Check consistency of definition present_parameter = self.__parameter_presence[parameter] if present_parameter and value is None: if not inconsistent_optionals.has_key(parameter): inconsistent_optionals[parameter] = [] inconsistent_optionals[parameter].append(rack_pos.label) elif not present_parameter and not value is None and \ not pool_forbidden: self.__parameter_presence[parameter] = True # Check mandatory and forbidden parameters if parameter in self.__mandatory_parameters and \ value is None: if not missing_value.has_key(parameter): missing_value[parameter] = [] missing_value[parameter].append(rack_pos.label) elif parameter in self.__forbidden_parameters and \ not value is None: if not additional_values.has_key(parameter): additional_values[parameter] = [] info = '%s (%s)' % (rack_pos.label, value) additional_values[parameter].append(info) # Error recording if len(non_empty) > 0 and not pool_forbidden: msg = 'Some rack positions in design rack %s contain values ' \ 'although there are no molecule designs for them: %s.' \ % (label, ', '.join(sorted(non_empty))) self.add_error(msg) if len(missing_value) > 0: records_str = self.__get_error_record_string(missing_value) msg = 'There are mandatory values missing for some rack ' \ 'positions in design rack %s: %s. Assumed scenario: %s.' \ % (label, records_str, self.scenario.display_name) self.add_error(msg) if len(additional_values) > 0: records_str = self.__get_error_record_string(additional_values) msg = 'Some factors must not be specified in %s scenarios. The ' \ 'following position in design rack %s contain ' \ 'specifications for forbidden factors: %s.' \ % (self.scenario.display_name, label, records_str) self.add_error(msg) if len(inconsistent_optionals) > 0: records_str = self.__get_error_record_string( inconsistent_optionals) msg = 'If you specify a factor, you have to specify it for each ' \ 'non-empty well in all design racks. The following wells ' \ 'in design rack %s lack a specification: %s.' \ % (label, records_str) self.add_error(msg)
def __check_value_presence(self, value_maps, label): """ Checks the presence of mandatory, optional and forbidden parameters for each rack position in a layout. """ pool_map = value_maps[TransfectionParameters.MOLECULE_DESIGN_POOL] pool_forbidden = TransfectionParameters.MOLECULE_DESIGN_POOL in \ self.__forbidden_parameters non_empty = [] missing_value = dict() additional_values = dict() inconsistent_optionals = dict() for rack_pos in get_positions_for_shape(self._rack_shape): pool_id = pool_map[rack_pos] is_untreated = (isinstance(pool_id, basestring) and \ TransfectionParameters.is_untreated_type(pool_id.lower())) if is_untreated: continue if pool_id is None: # Empty position should not have values for parameter, value_map in value_maps.iteritems(): if not value_map[rack_pos] is None: non_empty.append(rack_pos.label) break if not pool_forbidden: continue for parameter, value_map in value_maps.iteritems(): if parameter == TransfectionParameters.MOLECULE_DESIGN_POOL: continue elif pool_id == MOCK_POSITION_TYPE and \ parameter == TransfectionParameters.FINAL_CONCENTRATION: continue value = value_map[rack_pos] # Check consistency of definition present_parameter = self.__parameter_presence[parameter] if present_parameter and value is None: if not inconsistent_optionals.has_key(parameter): inconsistent_optionals[parameter] = [] inconsistent_optionals[parameter].append(rack_pos.label) elif not present_parameter and not value is None and \ not pool_forbidden: self.__parameter_presence[parameter] = True # Check mandatory and forbidden parameters if parameter in self.__mandatory_parameters and \ value is None: if not missing_value.has_key(parameter): missing_value[parameter] = [] missing_value[parameter].append(rack_pos.label) elif parameter in self.__forbidden_parameters and \ not value is None: if not additional_values.has_key(parameter): additional_values[parameter] = [] info = '%s (%s)' % (rack_pos.label, value) additional_values[parameter].append(info) # Error recording if len(non_empty) > 0 and not pool_forbidden: msg = 'Some rack positions in design rack %s contain values ' \ 'although there are no molecule designs for them: %s.' \ % (label, ', '.join(sorted(non_empty))) self.add_error(msg) if len(missing_value) > 0: records_str = self.__get_error_record_string(missing_value) msg = 'There are mandatory values missing for some rack ' \ 'positions in design rack %s: %s. Assumed scenario: %s.' \ % (label, records_str, self.scenario.display_name) self.add_error(msg) if len(additional_values) > 0: records_str = self.__get_error_record_string(additional_values) msg = 'Some factors must not be specified in %s scenarios. The ' \ 'following position in design rack %s contain ' \ 'specifications for forbidden factors: %s.' \ % (self.scenario.display_name, label, records_str) self.add_error(msg) if len(inconsistent_optionals) > 0: records_str = self.__get_error_record_string(inconsistent_optionals) msg = 'If you specify a factor, you have to specify it for each ' \ 'non-empty well in all design racks. The following wells ' \ 'in design rack %s lack a specification: %s.' \ % (label, records_str) self.add_error(msg)
def _init_empty_positions(self): """ Initialises the maps. """ for rack_pos in get_positions_for_shape(self.rack_shape): self._all_empty_positions.add(rack_pos)