Skip to content

Commit

Permalink
Merge pull request #1293 from sanger/bioscan-tube-label
Browse files Browse the repository at this point in the history
DPL-749 Bioscan - traction compatible tube label class
  • Loading branch information
yoldas authored Jul 22, 2023
2 parents 7f443b2 + ba2b8a8 commit f9d866f
Show file tree
Hide file tree
Showing 21 changed files with 493 additions and 35 deletions.
5 changes: 5 additions & 0 deletions app/helpers/search_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ def self.stock_plate_names
Settings.purposes.values.select(&:input_plate).map(&:name)
end

# Returns purpose names of stock plates using stock_plate flag instead of input_plate.
def self.stock_plate_names_with_flag
Settings.purposes.values.select(&:stock_plate).map(&:name)
end

def self.purpose_config_for_purpose_name(purpose_name)
Settings.purposes.values.find { |obj| obj[:name] == purpose_name }
end
Expand Down
92 changes: 92 additions & 0 deletions app/models/labels/plate_384_single_label.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# frozen_string_literal: true

# This label class provides attributes for Bioscan 384-well plate labels,
# which have only a single sticker. The placement of barcode and text fields
# is similar to 96-well plate labels. Note that other 384-well plate labels
# have double stickers.
#
# The label contains a barcode image in the middle and text on the corners.
# * The barcode image in the middle contains human barcode in code128, which
# is more compact and readable than code39. It needs empty space on both
# sides for readabilty.
# * Top-left contains date of printing
# * Bottom-left contains human barcode of the plate
# * Top-right contains the barcode of the an ancestor stock plate. For Bioscan
# 384-well plates, we show the first LILYS barcode, or the first LYSATE
# barcode; not last.
#
# We show an ancestor stock plate barcode; not the parent. The algorithm
# below is generic to handle the cases when ancestor plates with the same
# purpose can be stock plates or non stock plates because of alternative
# pipeline paths and/or when ancestor plates from different paths are used
# together to make a plate. We show the first of the configured alternative
# or the first of closest stock plate ancestors.
#
# Only Squix printers are used for printing 384-well plate labels. We do not
# send print requests to PMB service, instead we send them directly to SPrint
# service, which talks to Squix printers.
#
class Labels::Plate384SingleLabel < Labels::PlateLabelBase
def attributes
{
top_left: date_today,
bottom_left: labware.barcode.human,
top_right: workline_identifier,
bottom_right: labware.purpose_name,
barcode: labware.barcode.human
}
end

def workline_identifier
workline_reference&.barcode&.human
end

# Returns stock plate with fallbacks
def workline_reference
# Find the plates with configured purpose (using alternative_workline_identifier setting) and return the first.
plate = first_of_configured_purpose
return plate if plate.present?

# Find the plates of the last purpose (using input_plate setting) and return the first.
plate = first_of_last_purpose(SearchHelper.stock_plate_names)
return plate if plate.present?

# Find the plates of last purpose (using stock_plate setting) and return the first.
first_of_last_purpose(SearchHelper.stock_plate_names_with_flag)
end

# Returns the first stock plate of the configured purpose
def first_of_configured_purpose
alternative_workline_identifier_purpose = SearchHelper.alternative_workline_reference_name(labware)
return if alternative_workline_identifier_purpose.blank?

labware.ancestors.where(purpose_name: alternative_workline_identifier_purpose).first
end

# Returns the first stock plate of the last purpose
def first_of_last_purpose(purpose_names)
return if purpose_names.blank?

last_purpose_name = labware.ancestors.where(purpose_name: purpose_names).order(id: :asc).last&.purpose&.name

return if last_purpose_name.blank?

labware.ancestors.where(purpose_name: [last_purpose_name]).order(id: :asc).first
end

# There are no defaults configured for this label in the label_templates config.
# It is configured using its section in there. The following methods are to make
# sure that inherited defaults are not captured accidentially.

def default_printer_type
default_printer_type_for(:plate_384_single)
end

def default_label_template
default_label_template_for(:plate_384_single)
end

def default_sprint_label_template
default_sprint_label_template_for(:plate_384_single)
end
end
58 changes: 58 additions & 0 deletions app/models/labels/tube_label_traction_compatible.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

# This label class provides attributes for Bioscan tube labels. The labels have
# two stickers, side and cap.
#
# The side contain a 2D barcode image and four text lines.
# * The barcode image contains human barcode.
# * The first line contains parent barcode and well range for PCR2 Pool tube
# and only the parent parcode for others.
# * The second line contains current tube barcode without prefix and number of
# pooled samples, the third line contains the labware purpose.
# * The third line contains current tube labware purpose
# # The last line contains date of printing.
#
# The cap contains two text lines.
# * The first contains barcode prefix.
# * The last contains current tube barcode without prefix.
#
# Initially, this arrangement was only intended for Traction compatibility of
# Lib PCR XP (final) tube. However, difficulty of printing 1D barcodes for PCR2
# and Lib PCR tubes made us using 2D barcodes for all three Bioscan tubes.
#
# Only Squix printers are used for printing labels for Bioscan labware from
# Limber. We do not send the print requests to PMB service, instead we send them
# directly to SPrint service, which talks to Squix printers.
#
class Labels::TubeLabelTractionCompatible < Labels::TubeLabel
def attributes
{
first_line: first_line,
second_line: second_line,
third_line: labware.purpose_name,
fourth_line: date_today,
round_label_top_line: labware.barcode.prefix,
round_label_bottom_line: barcode_human_without_prefix,
barcode: labware.barcode.human
}
end

def first_line
# Parent barcode for PCR 2 Pool tube.
# This is the asset name (plate barcode and well range)
barcode_and_wells_format = /^.+?\s[A-Z]\d{1,2}:[A-Z]\d{1,2}$/
return labware.name if labware.name&.match?(barcode_and_wells_format)

# Parent barcode for Lib PCR Pool and Lib PCR XP tubes
labware.parents[0].barcode.human
end

def second_line
pools_size = @options[:pool_size] || labware.aliquots.count
"#{barcode_human_without_prefix}, P#{pools_size}"
end

def barcode_human_without_prefix
labware.barcode.human.sub(/\A#{Regexp.escape(labware.barcode.prefix)}/, '')
end
end
11 changes: 4 additions & 7 deletions app/models/presenters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
module Presenters # rubocop:todo Style/Documentation
def self.lookup_for(labware)
presentation_classes = Settings.purposes[labware.purpose&.uuid || :unknown]
return presentation_classes[:presenter_class].constantize if presentation_classes

if presentation_classes
presentation_classes[:presenter_class].constantize
else
return Presenters::UnknownPlatePresenter if labware.plate?
return Presenters::UnknownTubePresenter if labware.tube?
return Presenters::UnknownPlatePresenter if labware.plate?
return Presenters::UnknownTubePresenter if labware.tube?

raise UnknownLabwareType
end
raise UnknownLabwareType
end
end
19 changes: 19 additions & 0 deletions app/models/presenters/tag_plate_384_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module Presenters
# This presenter enables printing labels for 'Tag Plate - 384' plates.
class TagPlate384Presenter < UnknownPlatePresenter
def label
label_class = purpose_config.fetch(:label_class) || 'Labels::Plate384SingleLabel'
label_class.constantize.new(labware)
end

def add_unknown_plate_warnings
errors.add(
:plate,
"type '#{labware.purpose_name}' is not a limber plate. " \
'You can still use this page to print labels.'
)
end
end
end
4 changes: 3 additions & 1 deletion app/models/presenters/tube_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ class TubePresenter # rubocop:todo Style/Documentation
delegate :purpose, :state, :human_barcode, to: :labware

def label
Labels::TubeLabel.new(labware)
# fetch label class from purpose if present
label_class = purpose_config.fetch(:label_class) || 'Labels::TubeLabel'
label_class.constantize.new(labware)
end

def sample_count
Expand Down
8 changes: 7 additions & 1 deletion app/models/presenters/tubes_with_sources.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ def tube_labels
# Optimization: To avoid needing to load in the tube aliquots, we use the transfers into the
# tube to work out the pool size. This information is already available. Two values are different
# for ISC though. TODO: MUST RE-JIG
@array.map { |tube| Labels::TubeLabel.new(tube, pool_size: tube.pool_size) }

# Tubes might have different labels in purpose config. Load the right labels to avoid different behaviour.
@array.map do |tube|
config = Settings.purposes.fetch(tube.purpose&.uuid, {})
label_class = config.fetch(:label_class, 'Labels::TubeLabel')
label_class.constantize.new(tube, pool_size: tube.pool_size)
end
end

delegate_missing_to :array
Expand Down
4 changes: 4 additions & 0 deletions app/sequencescape/sequencescape/api/v2/tube.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,8 @@ def stock_plate(purpose_names: SearchHelper.stock_plate_names)
# max_by naturally sorts in ascending order
@stock_plate ||= ancestors.where(purpose_name: purpose_names).max_by(&:id)
end

def workline_identifier
stock_plate&.barcode&.human
end
end
16 changes: 16 additions & 0 deletions config/label_templates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,19 @@ plate_ltn_al_lib:
plate_cellaca_qc:
:label_class: Labels::PlateLabelCellacaQc
:pmb_template: sqsc_96plate_label_template_code39

# Only Squix printers are used through SPrint for tube_traction_compatible
# pmb_template setting is for completeness; it is not used.
tube_traction_compatible:
:label_class: Labels::TubeLabelTractionCompatible
:printer_type: 1D Tube
:pmb_template: tube_label_template_1d
:sprint_template: tube_label_traction_compatible.yml.erb

# Only Squix printers are used through SPrint for plate_384_single
# pmb_template setting is for completeness; it is not used.
plate_384_single:
:label_class: Labels::Plate384SingleLabel
:printer_type: 384 Well Plate Double
:pmb_template: sqsc_384plate_label_template_code39
:sprint_template: plate_384_single.yml.erb
13 changes: 11 additions & 2 deletions config/purposes/bioscan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ LILYS-96 Stock:
# and we use the AutomaticPlateStateChanger for this.
LBSN-96 Lysate:
:asset_type: plate
# LYSATE plate can be starting plate as well. The following must be true.
:stock_plate: true
:cherrypickable_target: false
# LYSATE plate can be created from LILYS as well. The following must be false.
:input_plate: false
:type: PlatePurpose::AdditionalInput
:presenter_class: Presenters::StockPlatePresenter
Expand Down Expand Up @@ -45,14 +47,18 @@ LBSN-384 PCR 1:
:creator_class: LabwareCreators::QuadrantStamp
:merger_plate: true
:presenter_class: Presenters::MinimalPcrPlatePresenter
:label_template: plate_6mm_double
:label_template: plate_384_single
:size: 384
# LILYS plate barcode is shown on barcode label top-right if possible; LYSATE otherwise.
:alternative_workline_identifier: LILYS-96 Stock
LBSN-384 PCR 2:
:asset_type: plate
:creator_class: LabwareCreators::TaggedPlate
:presenter_class: Presenters::MinimalPcrPlatePresenter
:label_template: plate_6mm_double
:label_template: plate_384_single
:size: 384
# LILYS plate barcode is shown on barcode label top-right if possible; LYSATE otherwise.
:alternative_workline_identifier: LILYS-96 Stock
# NB. will need to update this list to include new versions of the templates
# as we generate new sets after changes from the scripts in Sequencescape
:tag_layout_templates:
Expand Down Expand Up @@ -106,6 +112,7 @@ LBSN-384 PCR 2:
- Bioscan_384_template_24_v1
LBSN-384 PCR 2 Pool:
:asset_type: tube
:label_template: tube_traction_compatible
:target: StockMultiplexedLibraryTube
:type: IlluminaHtp::StockTubePurpose
:creator_class: LabwareCreators::PooledTubesFromWholePlates
Expand All @@ -114,13 +121,15 @@ LBSN-384 PCR 2 Pool:
:number_of_parent_labwares: 1
LBSN-9216 Lib PCR Pool:
:asset_type: tube
:label_template: tube_traction_compatible
:target: StockMultiplexedLibraryTube
:type: IlluminaHtp::StockTubePurpose
:creator_class: LabwareCreators::PooledTubesFromWholeTubes
:number_of_parent_labwares: 24
:presenter_class: Presenters::SimpleTubePresenter
LBSN-9216 Lib PCR Pool XP:
:asset_type: tube
:label_template: tube_traction_compatible
:target: MultiplexedLibraryTube
:type: IlluminaHtp::MxTubePurpose
:creator_class: LabwareCreators::TubeFromTube
Expand Down
7 changes: 7 additions & 0 deletions config/purposes/tag_plate_384.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
# Tag Plate - 384 is not a Limber plate. This config is used for printing.
Tag Plate - 384:
:asset_type: plate
:presenter_class: Presenters::TagPlate384Presenter
:size: 384
:label_template: plate_384_single
44 changes: 44 additions & 0 deletions config/sprint/label_templates/plate_384_single.yml.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[
{
"barcodeFields": [
{
"x": 17,
"y": 1,
"cellWidth": 0.2,
"barcodeType": "code128",
"value": "<%= merge_fields[:barcode] %>",
"height": 5
}
],
"textFields": [
{
"x": 1,
"y": 2,
"value": "<%= merge_fields[:top_left] %>",
"fontSize": 1.5,
"font": "proportional"
},
{
"x": 1,
"y": 5,
"value": "<%= merge_fields[:bottom_left] %>",
"fontSize": "1.5",
"font": "proportional"
},
{
"x": 55,
"y": 2,
"value": "<%= merge_fields[:top_right] %>",
"fontSize": 1.5,
"font": "proportional"
},
{
"x": 55,
"y": 5,
"value": "<%= merge_fields[:bottom_right] %>",
"fontSize": 1.5,
"font": "proportional"
}
]
}
]
Loading

0 comments on commit f9d866f

Please sign in to comment.