Class: CherrypickTask::ControlLocator

Inherits:
Object
  • Object
show all
Defined in:
app/models/cherrypick_task/control_locator.rb

Overview

A cherrypick Batch can source one or more controls from a ControlPlate For the initial destination plate in a batch these controls are distributed (pseudo)randomly across available wells. (Two controls can’t occupy the same wells) Subsequent destination plates within the same batch will offset the controls by a fixed amount to ensure destination plates in a batch have different control locations. This is especially important for negative controls, as it allows plate swaps to be identified (the negative control location can be thought of as a fingerprint).

Once all well locations have been used, an new set of random locations will be generated, and the cycle will begin again.

We need to be particularly careful with the offset value, as otherwise we risk reusing wells before the full cycle has been completed. For this reason we select prime numbers that are NOT a factor of the number of available wells.

Constant Summary collapse

BETWEEN_PLATE_OFFSETS =

A cherrypick batch may contain multiple destination plates. In this case the control wells should be located at different locations on each destination. The positions on the first plate in a batch are determined randomly, and then the locations are advanced by BETWEEN_PLATE_OFFSET for each subsequent plate. This is done to avoid the risk of subsequent plates having the same negative control location, which would reduce the ability to detect plate swaps. WARNING! These needs to be a prime number (which isn’t also a factor of the available well size) to avoid re-using wells prematurely. These offsets are prioritised in order. Technically any number that only shares 1 as a common factor with the available well size would work, but we limit ourself to primes to simplify validation.

[53, 59].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(params) ⇒ ControlLocator

Note:

wells_to_leave_free was originally hardcoded for 96 well plates at 24, in order to avoid

control wells being missed in cDNA quant QC. This requirement was removed in github.com/sanger/sequencescape/issues/2967 however I’ve avoided stripping out the behaviour completely in case controls are used in other pipelines.

Parameters:

  • params (Hash)

    A hash containing the following keys: - :batch_id [Integer] The id of the batch, used to generate a starting position - :total_wells [Integer] The total number of wells on the plate - :num_control_wells [Integer] The number of control wells to lay out - :wells_to_leave_free [Enumerable] Array or range indicating the wells to leave free from controls - :control_source_plate [ControlPlate] The plate to source controls from - :template [PlateTemplate] The template of the destination plate



52
53
54
55
56
57
58
59
60
# File 'app/models/cherrypick_task/control_locator.rb', line 52

def initialize(params)
  @batch_id = params[:batch_id]
  @total_wells = params[:total_wells]
  @num_control_wells = params[:num_control_wells]
  @wells_to_leave_free = params[:wells_to_leave_free].to_a || []
  @available_positions = (0...@total_wells).to_a - @wells_to_leave_free
  @control_source_plate = params[:control_source_plate]
  @plate_template = params[:template]
end

Instance Attribute Details

#available_positionsObject (readonly)

Returns the value of attribute available_positions.



32
33
34
# File 'app/models/cherrypick_task/control_locator.rb', line 32

def available_positions
  @available_positions
end

#batch_idObject (readonly)

Returns the value of attribute batch_id.



32
33
34
# File 'app/models/cherrypick_task/control_locator.rb', line 32

def batch_id
  @batch_id
end

#control_source_plateObject (readonly)

Returns the value of attribute control_source_plate.



32
33
34
# File 'app/models/cherrypick_task/control_locator.rb', line 32

def control_source_plate
  @control_source_plate
end

#num_control_wellsObject (readonly)

Returns the value of attribute num_control_wells.



32
33
34
# File 'app/models/cherrypick_task/control_locator.rb', line 32

def num_control_wells
  @num_control_wells
end

#total_wellsObject (readonly)

Returns the value of attribute total_wells.



32
33
34
# File 'app/models/cherrypick_task/control_locator.rb', line 32

def total_wells
  @total_wells
end

#wells_to_leave_freeObject (readonly)

Returns the value of attribute wells_to_leave_free.



32
33
34
# File 'app/models/cherrypick_task/control_locator.rb', line 32

def wells_to_leave_free
  @wells_to_leave_free
end

Instance Method Details

#control_positions(num_plate) ⇒ Array<Integer>

Returns a list with the destination positions for the control wells distributed randomly using batch_id as seed and num_plate to increase position with plates in same batch.

Parameters:

  • num_plate (Integer)

    The plate number within the batch

Returns:

  • (Array<Integer>)

    The indexes of the control well positions

Raises:

  • (StandardError)


70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'app/models/cherrypick_task/control_locator.rb', line 70

def control_positions(num_plate)
  raise StandardError, 'More controls than free wells' if num_control_wells > total_available_positions

  # Check that all elements in wells_to_leave_free fall within the acceptable range
  raise StandardError, 'More wells left free than available' unless wells_to_leave_free.all?(0...total_wells)
  return [] if num_control_wells.zero?

  # If num plate is equal to the available positions, the cycle is going to be repeated.
  # To avoid it, every num_plate=available_positions we start a new cycle with a new seed.

  placement_type = control_placement_type
  if placement_type.nil? || %w[fixed random].exclude?(placement_type)
    raise StandardError, 'Control placement type is not set or is invalid'
  end

  handle_control_placement_type(placement_type, num_plate)
end

#handle_incompatible_platesObject



88
89
90
91
92
93
94
95
96
97
98
# File 'app/models/cherrypick_task/control_locator.rb', line 88

def handle_incompatible_plates
  return false if control_placement_type == 'random'
  return false if @plate_template.wells.empty?

  control_assets = control_source_plate.wells.joins(:samples)

  converted_control_assets = convert_assets(control_assets.map(&:map_id))
  converted_template_assets = convert_assets(@plate_template.wells.map(&:map_id))

  converted_control_assets.intersect?(converted_template_assets)
end