-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathflow-cytometry.aq
1 lines (1 loc) · 55.9 KB
/
flow-cytometry.aq
1
{"config":{"title":"Flow Cytometry","description":"UW BIOFAB Flow Cytometry protocols and libraries","copyright":"University of Washington","version":"0.0.1","authors":[{"name":"Ben Keller","affiliation":"University of Washington"},{"name":"Devin Strickland","affilation":"","affiliation":"University of Washington"},{"name":"Erriberto Lopez","affilation":"","affiliation":"University of Washington"},{"name":"Abe Miller","affilation":"","affiliation":"University of Washington"}],"maintainer":{"name":"Ben Keller","email":"[email protected]"},"acknowledgements":null,"github":{"user":"bjkeller","repo":"flow-cytometry","organization":"klavinslab"},"keywords":null,"aquadoc_version":"1.0.2","aquarium_version":"\u003c%= Bioturk::Application.config.aquarium_version %\u003e"},"components":[{"sample_types":[],"object_types":[],"operation_type":{"name":"Cytometer Bead Calibration","category":"Flow Cytometry","deployed":false,"on_the_fly":false,"field_types":[{"ftype":"sample","role":"input","name":"calibration beads","sample_types":[],"object_types":[],"part":false,"array":false,"routing":"B","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null}],"protocol":"# frozen_string_literal: true\n\n# Instructions to Calibrate cytometer, calibration measurements are retrieved and\n# cytometer is prepared for flow.\n#\n# if operations are batched and use the same bead type,\n# then we only calibrate once and assign the measurement\n# results from that calibration to all operations in that bead type group\n#\nneeds 'Flow Cytometry/Cytometers'\nneeds 'Standard Libs/Debug'\nclass Protocol\n include Cytometers\n include Debug\n\n BEAD_STOCK = 'calibration beads'\n KEY_BEAD = 'BEAD_UPLOAD'\n\n def intro\n show do\n title 'Calibrating the Flow Cytometer'\n separator\n note 'To compare different experiments against each other we must have a way to compare the fluorescence intensities across different flow cytometers.'\n note 'To achieve this we will be using small beads that fluoresce multiple colors.'\n note '\u003cb\u003e1.\u003c/b\u003e Take chosen beads and dilute, if necessary.'\n note '\u003cb\u003e2.\u003c/b\u003e Setup flow cytometer workspace and measure bead sample.'\n note '\u003cb\u003e3.\u003c/b\u003e Upload .fcs file to Aquarium.'\n end\n end\n\n def main\n cytometer = Cytometers::BDAccuri.instance\n intro\n op_groups_by_bead = operations.group_by { |op| op.input(BEAD_STOCK).item }\n take_beads(op_groups_by_bead: op_groups_by_bead)\n op_groups_by_bead.each do |bead, ops|\n cytometer.clean\n # perform a calibration\n uploads, diluted_bead_item = cytometer.bead_calibration(bead_stock: bead, reuse_beads: true)\n cytometer.associate_uploads_to_bead_item('BEADS_uploads', bead, uploads)\n cytometer.associate_uploads_to_bead_item('BEADS_uploads', diluted_bead_item, uploads)\n\n cytometer.associate_uploads(KEY_BEAD, diluted_bead_item, uploads)\n\n # associate calibration measurments. If measurements already exist in this plan then dont overwrite\n ops.each do |op|\n # Add the diluted beads to the operations FieldValues\n # fv_name = 'Measured Diluted Beads'\n # op.add_input fv_name, diluted_bead_item.sample, diluted_bead_item.object_type\n # op.input(fv_name).set item: diluted_bead_item\n cytometer.associate_uploads(KEY_BEAD, op, uploads)\n safe_key = KEY_BEAD; i = 1\n until op.plan.get(safe_key).nil?\n safe_key = KEY_BEAD + i.to_s\n i += 1\n end\n cytometer.associate_uploads(KEY_BEAD, op.plan, uploads)\n end\n end\n cytometer.clean\n\n return_beads op_groups_by_bead, cytometer\n {}\n end\n\n # Create hashmap from bead sample to list of operations that\n # use that bead item\n def group_operations_by_bead\n ops_by_bead = {}\n operations.each do |op|\n bead_stock = op.input(BEAD_STOCK).item\n ops_by_bead[bead_stock] = [] if ops_by_bead[bead_stock].nil?\n ops_by_bead[bead_stock] \u003c\u003c op\n end\n ops_by_bead\n end\n\n def take_beads(op_groups_by_bead:)\n show do\n title 'Collect Calibration Beads'\n note 'In the next step retrieve the following calibration beads:'\n op_groups_by_bead.each do |bead, _ops|\n bullet \"Grab #{bead.sample.name} #{bead} Bead dispensers from Lot #{bead.get('Lot No.')}\"\n end\n note 'If a valid leftover eppendorf of diluted beads exists for the given Lot, grab that instead'\n note 'Make sure to pay attention to Lot No. of beads throughout this protocol.'\n end\n take op_groups_by_bead.keys, interactive: true\n end\n\n def return_beads(op_groups_by_bead, _cytometer)\n show do\n title 'Return Calibration Beads'\n note 'Return beads to the correct Lot'\n op_groups_by_bead.each do |bead, _ops|\n check \"Return bead dispensers and diluted beads to #{bead.get('Lot No.')}\"\n end\n end\n release op_groups_by_bead.keys, interactive: true\n end\nend\n","precondition":"def precondition(_op)\n true\nend","cost_model":"def cost(_op)\n { labor: 0, materials: 0 }\nend","documentation":"For most plan which runs **Flow Cytometry** operation, a **Cytometer Calibration** operation is required to be in the **plan**. \n\n*Flow Cytometery operations in a plan will not run until the calibration is run, if they are set up to require calibration.*\n\n**In most cases only one calibration should be done per plan**","test":"","timing":null}},{"sample_types":[],"object_types":[],"operation_type":{"name":"Flow Cytometry 96 well","category":"Flow Cytometry","deployed":false,"on_the_fly":false,"field_types":[{"ftype":"sample","role":"input","name":"96 well plate","sample_types":[],"object_types":[],"part":false,"array":false,"routing":"A","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"number","role":"input","name":"well volume (µL)","sample_types":[],"object_types":[],"part":false,"array":false,"routing":null,"preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"string","role":"input","name":"sample type","sample_types":[],"object_types":[],"part":false,"array":false,"routing":null,"preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":"E coli,Yeast"},{"ftype":"string","role":"input","name":"require calibration?","sample_types":[],"object_types":[],"part":false,"array":false,"routing":null,"preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":"yes, no"}],"protocol":"# frozen_string_literal: true\n\nneeds 'Flow Cytometry/Cytometers'\n\nclass Protocol\n include Cytometers\n\n INPUT_NAME = '96 well plate'\n WELL_VOL = 'well volume (L)' # not really used\n\n SAMPLE_TYPE = 'sample type'\n\n def main\n operations.retrieve # should be in loop!!!\n\n cytometer = Cytometers::BDAccuri.instance\n\n operations.each do |op|\n show do\n title 'Flow cytometery - info'\n warning \"The following should be run on a browser window on the #{cytometer.cytometer_name} computer!\"\n end\n\n cytometer.clean\n\n cytometer.run_sample_96(sample_string: op.input(SAMPLE_TYPE).val,\n collection: op.input(INPUT_NAME).collection,\n operation: op,\n plan: op.plan)\n end\n cytometer.clean\n\n # dispose plates\n # operations.each { |op| op.input(INPUT_NAME).item.mark_as_deleted } # Why are we diposing the input collection?\n {}\n\n # rescue CytometerInputError =\u003e e\n # show { \"Error: #{e.message}\" }\n end\nend\n","precondition":"def precondition(_op)\n \n if _op.input('require calibration?').value == 'yes'\n calibration_operation_type = OperationType.find_by_name(\"Cytometer Bead Calibration\")\n calibration_op = _op.plan.operations.find { |op| op.operation_type_id == calibration_operation_type.id}\n \n if calibration_op.nil?\n _op.associate('Waiting for Calibration','In order to use Cytometer, `Cytometer Bead Calibration` must be run in the same plan')\n return false\n elsif calibration_op.status != 'done'\n _op.associate(\"Waiting for Calibration\",\"Flow Cytometry cannot begin until Cytometer Calibration completes.\")\n return false\n else\n _op.get_association('Waiting for Calibration').delete if _op.get_association('Waiting for Calibration')\n return true\n end\n else\n return true\n end\nend","cost_model":"def cost(op)\n { labor: 0, materials: 3.50 }\nend","documentation":"Protocol for running flow cytometry, including cleaning before and after, and running beads as a calibration sample. Samples are in 96-well plate. Plate can be partially or fully occupied. ","test":"","timing":null}},{"library":{"name":"BDAriaIII","category":"Flow Cytometry","code_source":"# frozen_string_literal: true\n\nneeds 'Flow Cytometry/Cytometers'\n\nmodule Cytometers\n # Module for the BD Aria III cytometer.\n module BDAriaIII\n include Cytometer\n\n CYTOMETER_NAME = 'BD Aria III'\n TEMPLATE_DIR = ''\n\n # TODO: handle secrets differently\n LOGIN_USER = 'dummy_user'\n LOGIN_PASSWORD = 'dummy_password'\n\n TUBE_LABEL_STUB = 'Tube_%03d'\n\n DEFAULT_LOCATION = 'Health Sciences Building room H-581'\n\n def cytometer_name\n CYTOMETER_NAME\n end\n\n def location\n DEFAULT_LOCATION\n end\n\n def image_path\n FACS_IMAGE_PATH\n end\n\n def path_to(filename, extension = 'png')\n \"#{image_path}/#{filename}.#{extension}\"\n end\n\n def required_sample_tube\n '5 ml polystyrene round-bottom tube (Falcon 352054)'\n end\n\n def go_to_facs_and_login\n show do\n title 'Go to the FACS'\n note 'Take the cooler and the box with you.'\n note \"The FACS is in #{location}.\"\n end\n\n show do\n title 'Log into the FACS instrument'\n\n note \"User: #{LOGIN_USER}\"\n note \"Password: #{LOGIN_PASSWORD}\"\n end\n end\n\n def set_up_instrument(experiment_name:, template:, events_to_record:, target_events:, args: {})\n check_sweet_spot\n\n create_new_experiment(\n name: experiment_name,\n template: template\n )\n\n verify_settings(\n events_to_record: events_to_record\n )\n\n set_up_sorting(\n target_events: target_events\n )\n end\n\n def check_sweet_spot\n show do\n title 'Check the Sweet Spot'\n\n check 'Make sure the Sweet Spot is engaged.'\n image path_to('sweetspot')\n note 'The purple and yellow shapes should be touching as in the figure.'\n end\n end\n\n def create_new_experiment(name:, template:)\n show do\n title 'Load the experiment template'\n\n note 'In the menu bar, click on \u003cb\u003eExperiment\u003c/b\u003e, then \u003cb\u003eNew Experiment\u003c/b\u003e.'\n image path_to('new_experiment')\n\n note \"In the \u003cb\u003eExperiment Templates\u003c/b\u003e window, select \u003cspan style=\\\"background-color:yellow\\\"\u003e\u003cb\u003e#{template}\u003c/b\u003e\u003c/span\u003e.\"\n image path_to('select_template')\n end\n\n show do\n title 'Open and rename the experiment'\n\n note 'Double click on the notebook so that it opens up.'\n image path_to('open_workbook')\n\n note \"Rename the experiment as \u003cspan style=\\\"background-color:yellow\\\"\u003e\u003cb\u003e#{name}\u003c/b\u003e\u003c/span\u003e.\"\n image path_to('rename')\n end\n end\n\n def verify_settings(events_to_record:)\n show do\n title 'Verify settings'\n\n note 'Verify that the \u003cb\u003eStopping Gate\u003c/b\u003e is set to \u003cb\u003eAll Events\u003c/b\u003e.'\n note \"Verify that \u003cb\u003eEvents To Record\u003c/b\u003e is set to \u003cb\u003e#{number_with_delimiter(events_to_record)}\u003c/b\u003e.\"\n end\n end\n\n def set_up_sorting(target_events:)\n show do\n title 'Set up sorting'\n\n note 'Click the \u003cb\u003e+\u003c/b\u003e next to \u003cb\u003eGlobal Worksheets\u003c/B\u003e, then the one next to \u003cb\u003eGlobal Sheet1\u003c/b\u003e, then \u003cb\u003eSort Layout\u003c/b\u003e.'\n\n note 'Select or verify the following settings:'\n check '\u003cb\u003eDevice:\u003c/b\u003e 2 Tube'\n check '\u003cb\u003ePrecision:\u003c/b\u003e Purity'\n check \"\u003cb\u003eTarget Events:\u003c/b\u003e #{number_with_delimiter(target_events)}\"\n check '\u003cb\u003eSave Sort Reports:\u003c/b\u003e Save All'\n\n note 'Check that the settings match this image, except \u003cb\u003eTarget Events\u003c/b\u003e and the number next to \u003cb\u003eP1\u003c/b\u003e.'\n image path_to('sort_setup_complete')\n end\n end\n\n def set_and_run_tube(item:, tube_label:, tube_ct:, sort:)\n set_tube(item: item, tube_label: tube_label, tube_ct: tube_ct, sort: sort)\n load_tube(item: item, tube_label: tube_label)\n end\n\n def sort_tube(item:, tube_label:, default_to_sort:, gate_name: nil, debug:)\n place_tube_in_sorter(item_id: item.id, tube_label: tube_label)\n sort_library(item: item, default_to_sort: default_to_sort, debug: debug)\n end\n\n def set_tube(item:, tube_label:, tube_ct:, sort:)\n action = sort ? 'sort' : 'collect data for'\n\n software_tube_id = item.associations[:software_tube_id]\n\n if software_tube_id\n set_existing_tube(action, tube_label, software_tube_id)\n else\n software_tube_id = set_new_tube(\n action: action,\n tube_label: tube_label,\n tube_ct: tube_ct\n )\n item.associate(:software_tube_id, software_tube_id)\n end\n end\n\n def set_existing_tube(action, tube_label, software_tube_id)\n show do\n title \"Set to #{action} #{tube_label}\"\n\n check click_green_arrow(software_tube_id)\n image path_to('green_arrow')\n end\n end\n\n def set_new_tube(action:, tube_label:, tube_ct:)\n software_tube_id = format(TUBE_LABEL_STUB, tube_ct)\n\n show do\n title \"Set to #{action} #{tube_label}\"\n\n if tube_ct \u003e 1\n check new_tube\n image path_to('new_tube')\n end\n\n check verify_tube(software_tube_id)\n\n check click_green_arrow(software_tube_id)\n image path_to('green_arrow')\n end\n\n software_tube_id\n end\n\n def place_tube_in_sorter(item_id:, tube_label:)\n show do\n title 'Prepare collection tube'\n\n note \"Label a collection tube as \\\"#{item_id} - #{tube_label}.\\\"\"\n note 'Put 1 ml of PBS into the tube.'\n\n note 'Place the labeled tube into the left side of the holder like this.'\n image path_to('place_tube', 'JPG')\n\n note 'Insert the holder into the sorter and push it back as far as it will go.'\n image path_to('holder_placement', 'JPG')\n end\n end\n\n def load_tube(item:, tube_label:)\n show do\n title \"Load tube #{tube_label}\"\n\n note 'Vortex the tube for 3 Mississippi.'\n note 'Load the tube onto the stand and close the door.'\n # image path_to('sample_tube')\n\n note 'Click \u003cb\u003eLoad\u003c/b\u003e in the Acquisition Dashboard.'\n note 'After about 15 seconds, you will begin to see events displayed in the analysis sheet.'\n\n unless item.associations[:data_collected]\n check 'Click \u003cb\u003eRecord Data\u003c/b\u003e.'\n end\n end\n end\n\n def sort_library(item:, default_to_sort:, debug:)\n data = show do\n title 'Record percentage of positive cells'\n\n note 'Look in the table at the bottom of the data display for the \u003cb\u003e%Total\u003c/b\u003e in the \u003cb\u003eP1\u003c/b\u003e gate.'\n get 'number', var: 'pct_positive', label: 'Enter the \u003cb\u003e%Total\u003c/b\u003e', default: 10\n end\n\n data[:pct_positive] = [10, 20, 50].sample if debug\n\n frac_positive = data[:pct_positive] / 100.0\n\n item.associate(:frac_positive, frac_positive)\n\n target_events = target_events(default_to_sort: default_to_sort, frac_positive: frac_positive)\n\n show do\n title 'Start sorting'\n\n note 'In the \u003cb\u003eSort Layout\u003c/b\u003e window:'\n note \"1. Set \u003cb\u003eTarget Events\u003c/b\u003e to #{number_with_delimiter(target_events)}\"\n note '2. Right click on the \u003cb\u003eP1\u003c/b\u003e field (under \u003cb\u003eLeft\u003c/b\u003e) and select \u003cb\u003eRemove\u003c/b\u003e'\n note '3. Right click on the same field and \u003cb\u003eadd P1\u003c/b\u003e'\n note 'Click \u003cb\u003eSort\u003c/b\u003e'\n note 'Adjust the flow rate as needed, up to \u003cb\u003e8.0\u003c/b\u003e, keeping the efficiency \u003cb\u003e\u003e 90%\u003c/b\u003e'\n note \"Allow the sorter to run until #{number_with_delimiter(target_events)} have been collected in the \u003cb\u003eP1\u003c/b\u003e gate.\"\n end\n end\n\n def remove_tube_from_sorter(collection_tube:, item: nil)\n show do\n title 'Remove tube from sorter'\n\n note 'Once the \u003cb\u003eTarget Events\u003c/b\u003e has been reached, or the sample is running too low:'\n note 'Click \u003cb\u003eStop Sorting\u003c/b\u003e.'\n note \"Remove the #{collection_tube} from the sorter, put the cap back on, and place it on ice.\"\n image path_to('place_tube', 'JPG')\n end\n end\n\n def remove_sample_tube(sample_tube:)\n show do\n title 'Remove sample tube'\n\n note 'Click \u003cb\u003eUnload\u003c/b\u003e.'\n note \"Remove the #{sample_tube} from the platform, put the cap back on, and place it on ice.\"\n end\n end\n\n def shutdown\n show do\n title 'Copy the sort reports to our data folder'\n\n note 'Open two new Explorer windows'\n note 'In one window, navigate to the \u003cb\u003esort reports folder (D:\\\\BD\\\\FACSDiva\\\\SortReports)\u003c/b\u003e'\n note 'Sort the files by \u003cb\u003eDate modified\u003c/b\u003e'\n note \"In the other window, navigate to \u003cb\u003eour data folder (D:\\\\AriaData\\\\#{LOGIN_USER}\\\\SortReports)\u003c/b\u003e\"\n note '\u003cb\u003eCOPY\u003c/b\u003e the most recently-modified folder in the \u003cb\u003esort reports folder\u003c/b\u003e to \u003cb\u003eour data folder\u003c/b\u003e'\n image path_to('sort_reports')\n end\n show do\n title 'Disengage the Sweet Spot'\n\n check 'Click once on the purple and yellow shapes to turn the Sweet Spot OFF.'\n image path_to('sweetspot')\n end\n\n show do\n title 'Shutdown the instrument'\n\n note 'Logout of the software'\n note 'Log back in as \u003cb\u003eUser:\u003c/b\u003e ShutdownDaily'\n note 'Run shutdown: 10 min bleach and 4 min water.'\n end\n end\n\n ########## COMMON LANGUAGE ##########\n\n def new_tube\n 'Click \u003cb\u003eNext tube\u003c/b\u003e.'\n end\n\n def verify_tube(software_tube_id)\n \"Verify that the #{@tube_ct \u003e 1 ? 'new' : 'first'} tube is labeled \u003cb\u003e\\\"#{software_tube_id}\\\"\u003c/b\u003e\"\n end\n\n def click_green_arrow(software_tube_id)\n \"Click on the arrow next to \u003cb\u003e\\\"#{software_tube_id}\\\"\u003c/b\u003e and make sure it is \u003cb\u003e\u003cspan style=\\\"color: green\\\"\u003eGREEN\u003c/span\u003e\u003c/b\u003e\"\n end\n end\nend\n"}},{"library":{"name":"Cytometers","category":"Flow Cytometry","code_source":"# frozen_string_literal: true\n\nneeds 'Standard Libs/AssociationManagement'\nneeds 'Standard Libs/MatrixTools'\nneeds 'Standard Libs/Debug'\n\n# Library for flow cytometry.\n# Protocols should create an instance of the appropriate cytometer class with\n# using the singleton instance method; e.g., `BDAccuri.instance`.\n# All methods of the {Cytometer} module are included in these classes.\n#\n# [Need text on settings]\nmodule Cytometers\n # Module to represent functions on flow cytometers.\n # Individual cytometers should mixin this module and {Singleton}.\n module Cytometer\n include ActionView::Helpers::NumberHelper\n include AssociationManagement\n include MatrixTools\n include Debug\n\n require 'json'\n\n KEY_SAMPLE = 'SAMPLE_UPLOAD'\n KEY_BEAD = 'BEAD_UPLOAD'\n\n FACS_IMAGE_PATH = 'Actions/FACS'\n\n # Run clean cycle of cytometer.\n def clean(args = {})\n args = clean_defaults.merge(args)\n labels = args[:labels]\n\n # check that there is sufficient volume of cleaning reagent\n show do\n title 'Check Levels of Cleaning Reagents'\n check \"Locate the #{args[:container]}. It should contain #{labels.length} eppendorfs.\"\n labels.length.times do |ii|\n check \"Check that eppendorf labeled \u003cb\u003e#{labels[ii]}\u003c/b\u003e at position \u003cb\u003e#{args[:position][ii]}\u003c/b\u003e contains at least #{args[:min_volume]} mL. If not, add \u003cb\u003e#{args[:add_volume]}\u003c/b\u003e mL from jar labeled \u003cb\u003e#{args[:jar_labels][ii]}\u003c/b\u003e located in cabinet above cytometer.\"\n end\n end\n\n # generate a 'dummy' sample_matrix\n samp_matrix = WellMatrix.create_empty(96, -1)\n samp_matrix.set('D4', 1)\n samp_matrix.set('D5', 1)\n samp_matrix.set('D6', 1)\n\n # run\n title_string = \"Cleaning Template on #{cytometer_name}\"\n run_template(template_file: args[:template_file],\n settings: clean_settings,\n title_string: title_string,\n container: args[:container],\n sample_matrix: samp_matrix)\n\n # clean-specific message\n show do\n note 'Cytometer does not require supervision during cleaning.'\n end\n end\n\n # Default arguments for {clean}\n def clean_defaults\n {\n template_file: 'CleanRegular.c6t',\n container: '24 tube rack',\n labels: %w[C D S],\n position: %w[D4 D5 D6],\n jar_labels: %w[Cleaning Decontamination Sheath],\n min_volume: 0.5, # mL\n add_volume: 1 # mL\n }\n end\n\n # Run bead calibration sample on cytometer from an eppendorf tube.\n # Associates data to item and plan.\n #\n # @param [Hash] args\n # @option args [Item] :bead_stock stock of calibration beads\n # @option args [Operation] :operation the operation where calibration\n # is perfomed\n # @option args [Plan] :plan\n # @option args [Boolean] :reuse_beads whether or not to use already made diluted beads if there are some leftover\n # @return new sample item representing diluted beads\n def bead_calibration(args)\n args = calibration_defaults.merge(args)\n bead_stock = args[:bead_stock]\n plan = args[:plan]\n op = args[:operation]\n\n bead_diluted = nil\n if args[:reuse_beads] \u0026\u0026 check_for_diluted_beads(bead_stock)\n args[:leftovers] = true\n # get most recent beads for this lot no to use as the leftovers. Its fine if this query misses sometimes since\n # diluted bead inventory is informally maintained, and all that really matters is the Lot No.\n bead_diluted = bead_stock.sample.in('Diluted beads').select { |item| item.get('Lot No.') == bead_stock.get('Lot No.') } # this is probably an incredibly slow query. Can we check for the value of data associations in active record queries?\n else\n args[:leftovers] = false\n # create new diluted bead item, same beads as bead stock, in aquarium\n bead_diluted = produce new_sample(bead_stock.sample.name, of: 'Bead', as: 'Diluted beads')\n bead_diluted.associate('Lot No.', bead_stock.get('Lot No.'))\n end\n\n show_bead_setup(args)\n\n samp_matrix = WellMatrix.create_empty(96, -1)\n samp_matrix.set('A1', bead_diluted.sample.id)\n\n # run\n title_string = \"Calibration Template on #{cytometer_name}\"\n run_template(template_file: args[:template_file],\n settings: calibration_settings,\n title_string: title_string,\n container: args[:container],\n sample_matrix: samp_matrix,\n item_number: bead_diluted.id)\n uploads = upload_files(expected_num_uploads: 1)\n\n [uploads, bead_diluted]\n end\n\n # Inquire if there is an already made\n # diluted beads available from the target lot\n # that can be used for calibration instead of stock\n #\n # @stock [Item] the bead stock we were going to use\n def check_for_diluted_beads(stock)\n lotno = stock.get('Lot No.')\n resp = show do\n title 'Check for Existing Diluted Beads'\n note \"If there is diluted beads of the right type left over from a previous calibration that aren't too old, then we will use those\"\n note 'Diluted beads should be next to where the droplet dispensers are'\n select %w[yes no], label: \"Could you find diluted beads from Lot no. #{lotno} that are less than one week old?\", var: 'bead_answer', default: 1\n warning 'Do not use beads that are too old or are from a Lot no. other than the one specified'\n end\n\n (resp.get_response('bead_answer') == 'yes') # return\n end\n\n # Display instructions for preparing calibration bead sample.\n #\n # @param [Hash] args\n # @option args [Item] bead_stock the item for the bead stock\n def show_bead_setup(args)\n bead_stock = args[:bead_stock]\n container = args[:container]\n source = \"#{args[:bead_volume]} of #{bead_stock.sample.name}\"\n destination = \"#{args[:media_volume]} of #{args[:media]}\"\n show do\n title 'Prepare Calibration sample'\n if !args[:leftovers]\n check \"Add #{source}, brown cap to #{destination}\"\n check \"Add #{source}, white cap to \u003cb\u003esame\u003c/b\u003e #{destination}\"\n else\n note 'Use leftover diluted beads.'\n end\n check \"Place eppendorf containing calibration sample in #{container} at position(s) #{args[:position].join(',')} of #{container}\"\n end\n end\n\n # Default arguments for {bead_calibration}\n def calibration_defaults\n {\n container: '24 tube rack',\n template_file: 'calibration_beads_template.c6t',\n position: ['A1'],\n bead_volume: '1 drop',\n media_volume: '1 mL',\n media: 'PBS',\n reuse_beads: false\n }\n end\n\n # Run this cytometer on a 96 well plate and associate data.\n #\n # @param [Hash] args\n # @option args [Collection] :collection\n # @option args [Operation] :operation\n # @option args [String] :sample_string\n # @option args [Plan] :plan\n def run_sample_96(args)\n args = run_defaults.merge(args)\n sample_string = args[:sample_string]\n coll = args[:collection]\n plan = args[:plan]\n op = args[:operation]\n\n if debug # for testing only - ignore this\n matrix = coll.matrix\n matrix[0][0] = 1\n coll.matrix = matrix\n coll.save\n end\n\n if coll.nil?\n raise CytometerInputError.new, 'collection expected for 96 well plate'\n end\n\n dimensions = coll.dimensions\n\n if dimensions.empty?\n raise CytometerInputError.new, 'empty dimensions array for 96 well plate'\n end\n\n if !(dimensions.all? { |dim| dim \u003e 0 }) || dimensions.length != 2\n raise CytometerInputError.new, 'bad dimensions for 96 well plate'\n end\n\n # count positions within plate that contain samples\n num_samples = 0\n dimensions[0].times do |rr|\n dimensions[1].times do |cc|\n num_samples += 1 if coll.matrix[rr][cc] \u003e 0\n end\n end\n\n raise CytometerInputError.new, 'No samples to run' if num_samples \u003c 1\n\n # convert collection's sample matrix to WellMatrix object for compatibility\n samp_matrix = WellMatrix.from_array(coll.matrix)\n\n run_template(template_file: args[:templates][sample_string],\n settings: run_settings[sample_string],\n title_string: \"#{sample_string} measurement\",\n container: args[:container],\n sample_matrix: samp_matrix,\n item_number: coll.id)\n uploads = upload_files(expected_num_uploads: 1)\n\n associate_uploads(KEY_SAMPLE, plan, uploads) # associate each upload individually to plan\n associate_uploads(KEY_SAMPLE, op, uploads) # associate each upload individually to plan\n unless debug\n associate_uploads_to_plate(KEY_SAMPLE.pluralize, coll, uploads)\n end # associate a matrix of uploads to the plate\n\n uploads\n end\n\n # Default arguments for {run_sample_96}.\n def run_defaults\n {\n templates: { 'E coli' =\u003e 'Ecoli.c6t', 'Yeast' =\u003e 'Yeast_gates.c6t' },\n container: '96 well plate: Flat Bottom (Black)'\n }\n end\n\n # Export FCS files from this cytometer.\n #\n # Associates the {Upload} object for each to the input item and and the list\n # of objects to the plan of the operation.\n #\n # @param [Hash] args\n # @option args [Fixnum] :expected_num_uploads the number of expected files\n # (default: 1)\n # @return [Array\u003cUpload\u003e] the array of uploads for exported files\n private def upload_files(args)\n args = upload_defaults.merge(args)\n expected_num_uploads = args[:expected_num_uploads]\n dirname = export_and_select_directory\n gather_uploads(expected_num_uploads, dirname)\n end\n\n # Defines the default values for arguments to {upload_files}\n #\n # @return [Hash] the default argument values\n private def upload_defaults\n { expected_num_uploads: 1 }\n end\n\n # Upload and check that have expected number of files\n # Give user 3 attempts to get all files. Return an Array of upload objects.\n # @param [Integer] expected_num_uploads the number of expected files\n # @param [String] dirname the name of the directory where the files reside\n # @return [Array\u003cUpload\u003e] an array of uploads for the exported files, or nil if nothing was uploaded\n private def gather_uploads(expected_num_uploads, dirname)\n uploads_from_show = {}\n num_uploads = 0\n attempt = 0; # number of upload attempts\n while (attempt \u003c 3) \u0026\u0026 (num_uploads \u003c expected_num_uploads)\n attempt += 1\n if attempt \u003e 1\n show { warning 'Number of uploaded files was incorrect, please try again!' }\n end\n uploads_from_show = show do\n title \"Select and highlight all .fcs files in directory \u003cb\u003eDesktop/FCS Exports/#{dirname}\u003c/b\u003e\"\n upload var: 'fcs_files'\n end\n\n unless uploads_from_show[:fcs_files].nil?\n num_uploads = uploads_from_show[:fcs_files].length\n end\n end\n uploads_from_show_to_array(uploads_from_show, :fcs_files)\n end\n\n # Converts the output of a show block that recieves uploads into a\n # list of uploads.\n #\n # @param [Hash] uploads_from_show the hash return by a show block which accepts user input\n # @param [Symbol] upload_var the symbol key which the target uploads are stored under in uploads_from_show\n # @return [Array\u003cUpload\u003e] an array of uploads contained in the uploads_From_show at the given key\n private def uploads_from_show_to_array(uploads_from_show, upload_var)\n return spoof_uploads if debug\n\n upload_list = []\n if uploads_from_show[upload_var].nil?\n return upload_list\n else\n uploads_from_show[upload_var].each_with_index do |upload_hash, _ii|\n up = Upload.find(upload_hash[:id])\n upload_list.push(up)\n # upload_list[ii] = up\n end\n end\n\n upload_list\n end\n\n # Ignore, this is for debugging only\n private def spoof_uploads\n [Upload.find(1), Upload.find(2)]\n end\n\n # Associate all `uploads` to the `target` DataAssociator. The keys of each upload will be\n # the concatenation of `key_name` and that upload's id.\n # Associating fcs files to the plan and operation makes fcs data of any specific well\n # easily accessible to users\n #\n # @param [String] key_name the name which describes this upload set\n # @param [Plan] plan the plan that the uploads will be associated to\n # @param [Array\u003cUpload\u003e] uploads An Array containing several Uploads\n # @effects associates all the given uploads to `plan`, each with a\n # unique key generated from the combining `keyname` and upload id\n def associate_uploads(key_name, target, uploads)\n if target\n associations = AssociationMap.new(target)\n uploads.each do |up|\n associations.put(\"U#{up.id}_#{key_name}\", up)\n end\n associations.save\n end\n end\n\n # Associate a matrix containing all `uploads` to `collection`.\n # The upload matrix will map exactly to the sample matrix of\n # `collection`, and it will be associated to `collection` as a value\n # of `key_name`\n #\n # @param [String] key_name the key that the upload matrix will\n # be associated under\n # @param [Collection] collection what the upload matrix will be\n # associated to\n # @param [Array\u003cUpload\u003e] uploads An Array containing several Uploads\n # @effects associates all the given uploads to `collection` as a 2D array inside a singleton hash\n private def associate_uploads_to_plate(key_name, coll, uploads)\n # figure out size of collection (24 or 96)\n dims = coll.dimensions\n size = dims[0] * dims[1]\n well_uploads = WellMatrix.create_empty(size, -1)\n uploads.each do |up|\n # the first 3 letters of the upload filename will be the\n # alphanumeric well coordinate\n alpha_coord = up.name[0..2]\n well_uploads.set(alpha_coord, up.id)\n end\n coll_associations = AssociationMap.new(coll)\n # ensure we aren't overwriting an existing association\n unless coll_associations.get(key_name).nil?\n i = 0\n i += 1 until coll_associations.get(\"#{key_name}_#{i}\").nil?\n key_name = \"#{key_name}_#{i}\"\n end\n\n coll_associations.put(key_name, 'upload_matrix' =\u003e well_uploads.to_a)\n coll_associations.save\n end\n\n def associate_uploads_to_bead_item(key_name, bead_item, uploads)\n bead_item_associations = AssociationMap.new(bead_item)\n unless bead_item_associations.get(key_name).nil?\n i = 0\n i += 1 until bead_item_associations.get(\"#{key_name}_#{i}\").nil?\n key_name = \"#{key_name}_#{i}\"\n end\n bead_item_associations.put(key_name, uploads.first)\n bead_item_associations.save\n end\n\n # Display instructions for setting up a template and running it.\n #\n # @param [Hash] args\n # @option args [String] :template_file the name of the template file\n # @option args [Hash] :settings the hash of the settings for this run\n # @option args [String] :title_string the title for the show\n # @option args [String] :container previously known as \"holder\"\n # @option args [WellMatrix] :sample_matrix an 8x12 WellMatrix with sample IDs for\n # occupied wells, -1 otherwise\n # @option args [Fixnum] :item_number the item.id used in naming .c6 file\n # @raises CytometerInputError if the provided sample_matrix is nil\n def run_template(args)\n args = template_defaults.merge(args)\n template_file = args[:template_file]\n title_string = args[:title_string]\n container = args[:container]\n samp_matrix = args[:sample_matrix]\n\n raise CytometerInputError.new, 'sample_matrix is nil' if samp_matrix.nil?\n\n # build string, .c6 filename\n item_string = args[:item_number]\n item_string = \"_#{item_string}_\" unless item_string.nil?\n c6_filename = File.basename(template_file, '.c6t') +\n item_string +\n Time.zone.now.to_date.to_s +\n '.c6'\n\n # visually check culture positions\n show do\n title \"Check new sample #{title_string}\"\n note 'Only the shaded positions should contain samples:'\n table samp_matrix.display_position_table { |samp| samp \u003e 0 } # highlight cells with sample id \u003e 0\n end\n\n choose_template(title_string, template_file, container)\n load_samples(title_string, container)\n enter_settings(title_string, args[:settings])\n start_run(title_string, samp_matrix, c6_filename)\n end\n\n ### FACS methods\n\n def template_defaults\n { item_number: '' }\n end\n\n def target_events(default_to_sort:, frac_positive:)\n (default_to_sort * frac_positive).to_i\n end\n\n def required_sample_tube; end\n\n def check_sweet_spot; end\n\n # Error class for bad cytometer parameters.\n class CytometerInputError \u003c StandardError; end\n end\n\n # Singleton class for the BDAccuri cytometer.\n # The interface of this class is defined by a mixin of the {Cytometer} module,\n # which provides the main interface for interacting with the cytometer.\n class BDAccuri\n include Singleton\n include Cytometer\n\n CYTOMETER_NAME = 'BD Accuri'\n TEMPLATE_DIR = 'aq_templates'\n\n # Gets Cytometer name. Otherwise it would be unavailable in 'parent' module.\n def cytometer_name\n CYTOMETER_NAME\n end\n\n # The settings used for calibration beads with this cytometer.\n def calibration_settings\n {\n settings: {\n 'Run Limits' =\u003e '30 L',\n 'Fluidics' =\u003e 'Slow',\n 'Set Threshold' =\u003e 'FSC-H less than 300,000, SSC-H less than 250,000',\n 'Wash Settings' =\u003e 'None',\n 'Agitate Plate' =\u003e 'None'\n }\n }\n end\n\n # The settings used for cleaning this cytometer.\n def clean_settings\n {\n settings: {\n 'Run Limits' =\u003e '2 Min',\n 'Fluidics' =\u003e 'Slow',\n 'Set Threshold' =\u003e 'FSC-H less than 80,000',\n 'Wash Settings' =\u003e 'None',\n 'Agitate Plate' =\u003e 'None'\n }\n }\n end\n\n # The settings for running this cytometer by organism.\n def run_settings\n {\n settings: {\n 'E coli' =\u003e { 'Run Limits' =\u003e '60,000 events, 1 Min, 50 L',\n 'Fluidics' =\u003e 'Medium',\n 'Set Threshold' =\u003e 'FSC-H less than 8,000',\n 'Wash Settings' =\u003e 'None',\n 'Agitate Plate' =\u003e '1 Cycle every 12th well' },\n 'Yeast' =\u003e { 'Run Limits' =\u003e '30,000 events',\n 'Fluidics' =\u003e 'Fast',\n 'Set Threshold' =\u003e 'FSC-H less than 400,000',\n 'Wash Settings' =\u003e 'None',\n 'Agitate Plate' =\u003e '1 Cycle every 12th well' }\n }\n }\n end\n\n # Display instructions for exporting FCS files and selecting the directory.\n #\n # @return [String] the name of the directory containing the exported files\n private def export_and_select_directory\n show do\n title \"Export Data from Flow Cytometer #{CYTOMETER_NAME}\"\n check 'Make sure that flow cytometer run is \u003cb\u003eDONE!\u003c/b\u003e'\n check 'Press \u003cb\u003eCLOSE RUN DISPLAY\u003c/b\u003e'\n check 'Select \u003cb\u003eFile\u003c/b\u003e =\u003e \u003cb\u003eExport ALL Samples as FCS...\u003c/b\u003e (see below)'\n image 'Actions/FlowCytometry/saveFCS_menu_cropped.png'\n end\n ui = show do\n title \"Select Data Directory for Flow Cytometer #{CYTOMETER_NAME}\"\n warning 'Look for the name of the FCS directory that is created, as in example below. Your directory name will be different!'\n image 'Actions/FlowCytometry/saveFCS_dirname_cropped.png'\n get 'text', var: 'dirname', label: 'Enter the name of the export directory in \u003cb\u003eDesktop/FCS Exports/\u003c/b\u003e'\n end\n ui[:dirname]\n end\n\n # Displays instructions for selecting the template file for the container type.\n #\n # @param [String] title_string the title for the page\n # @param [String] template_file the name of the template file\n # @param [String] container the container type\n def choose_template(title_string, template_file, container)\n show do\n title \"Select #{title_string}\"\n check 'Open the BD Accuri software BD CSampler if not already open'\n check 'If the program is open and displaying \u003cb\u003eDONE!\u003c/b\u003e, press \u003cb\u003eCLOSE RUN DISPLAY\u003c/b\u003e'\n check 'Go to \u003cb\u003eFile\u003c/b\u003e =\u003e Select \u003cb\u003eOpen workspace or template\u003c/b\u003e'\n warning 'Do not save changes to workspace'\n check \"Under \u003cb\u003e#{TEMPLATE_DIR}\u003c/b\u003e, find and open \u003cb\u003e#{template_file}\u003c/b\u003e\"\n warning 'The filename should end in \u003cb\u003e.c6t\u003c/b\u003e!'\n check \"Make sure the \u003cb\u003ePlate Type\u003c/b\u003e is \u003cb\u003e#{container}\u003c/b\u003e\"\n end\n end\n\n # Displays instructions for loading samples in the cytometer.\n #\n # @param [String] title_string the title for the page\n # @param [String] container the container type\n def load_samples(title_string, container)\n # load samples\n show do\n title \"Load sample #{title_string}\"\n warning 'Beware of moving parts! Keep black tray beneath cytometer free!'\n check 'Press \u003cb\u003eEject Plate\u003c/b\u003e'\n check 'Remove the plate from the cytometer arm and place it on cytometer lid'\n check \"For \u003cb\u003e#{container}\u003c/b\u003e containing \u003cb\u003e#{title_string}\u003c/b\u003e: remove all seals or lids, uncap any capped samples\"\n check \"Place \u003cb\u003e#{container}\u003c/b\u003e containing \u003cb\u003e#{title_string}\u003c/b\u003e on cytometer arm\"\n warning 'Make sure well \u003cb\u003eA1\u003c/b\u003e is aligned with the red sticker on the cytometer arm!'\n check 'Press \u003cb\u003eLoad Plate\u003c/b\u003e'\n end\n end\n\n # Displays instructions for loading settings to the cytometer.\n #\n # @param [String] title_string the title for the page\n # @param [Hash] settings the settings for the cytometer\n # @see calibration_settings\n # @see clean_settings\n # @see run_settings\n def enter_settings(title_string, settings)\n show do\n title \"Settings for #{title_string}\"\n check 'Select the \u003cb\u003eAuto Collect\u003c/b\u003e table towards the top of the window'\n note 'You will be using the settings listed below'\n warning 'You may enter settings manually, \u003cb\u003eOR\u003c/b\u003e press Control+left mouse button click to select a well that already has these settings'\n warning 'A red box will appear around the selected well'\n settings\u0026.each do |key, value|\n check \"Make sure that \u003cb\u003e#{key}\u003c/b\u003e is set to:\"\n value.each { |type, val| bullet \"#{type} - #{val}\" }\n end\n end\n end\n\n # Displays instructions to start the cytometer run.\n #\n # @param [String] title_string the title for the page\n # @param [WellMatrix] sample_matrix the matrix indicating where samples occur\n # @param [String] c6_filename the name for the workspace file\n def start_run(title_string, samp_matrix, c6_filename)\n # run\n show do\n title \"Select wells and run #{title_string}\"\n check 'On the BD Accuri, using the left mouse button (or the Select/Deselect All links), click on all plate positions to be measured:'\n table samp_matrix.display_position_table { |samp| samp \u003e 0 } # highlight cells with sample id \u003e 0\n warning 'Only the well(s) that are listed in the Aquarium table should be checked'\n check 'Click \u003cb\u003eApply Settings\u003c/b\u003e to apply the seetings to all checked wells. You will be prompted to save the workspace'\n check \"Save file as \u003cb\u003e#{c6_filename}\u003c/b\u003e\"\n check 'Click \u003cb\u003eOPEN RUN DISPLAY\u003c/b\u003e'\n check 'Click \u003cb\u003eAUTORUN\u003c/b\u003e'\n end\n end\n end\nend\n"}},{"library":{"name":"SonySH800S","category":"Flow Cytometry","code_source":"needs 'Flow Cytometry/Cytometers'\n\nmodule Cytometers\n # module for the Sony SH800S cytometer.\n module SonySH800S\n include Cytometer\n\n CYTOMETER_NAME = 'Sony SH800S'\n TEMPLATE_DIR = ''\n\n # TODO: handle secrets differently\n LOGIN_USER = 'dummy_user'\n LOGIN_PASSWORD = 'dummy_password'\n\n TUBE_LABEL_STUB = \"Tube - %d\"\n\n DEFAULT_LOCATION = \"NanoES 380B\"\n\n def cytometer_name\n CYTOMETER_NAME\n end\n\n def location\n DEFAULT_LOCATION\n end\n\n def image_path\n \"#{FACS_IMAGE_PATH}/sony_sh800s\"\n end\n\n def path_to(filename, extension='png')\n \"#{image_path}/#{filename}.#{extension}\"\n end\n\n def go_to_facs_and_login\n show do\n title \"Go to the FACS\"\n note \"Take the cooler and the box with you.\"\n note \"The FACS is in #{location}.\"\n end\n\n show do\n title \"Log into the FACS instrument\"\n\n note \"If the software is logged out, log back in with the following credentials:\"\n note \"User: #{LOGIN_USER}\"\n note \"Password: #{LOGIN_PASSWORD}\"\n end\n end\n\n def set_up_instrument(experiment_name:, template:, events_to_record:, target_events:, args:{})\n insert_tube_holder\n\n create_new_experiment(\n name: experiment_name,\n template: template\n )\n\n verify_settings(\n events_to_record: events_to_record\n )\n\n set_up_sorting(\n target_events: target_events,\n args: args\n )\n end\n\n def insert_tube_holder\n show do\n title \"Insert sample tube holder\"\n\n note \"Find the correct sample tube holder based on the type of tube \" \\\n \"containing the cells to be sorted.\"\n image path_to('all_sample_tube_holders')\n\n note \"Place the sample tube holder in the instrument. \" \\\n \"It is held in place by a magnet.\"\n image path_to('sample_tube_location')\n end\n end\n\n def create_new_experiment(name:, template:)\n show do\n title \"Create a new experiment\"\n\n note \"If the \u003cb\u003eCreate Experiment\u003c/b\u003e window is not displayed, \" \\\n \"click \u003cb\u003eNew\u003c/b\u003e on the \u003cb\u003e File\u003c/b\u003e tab of the ribbon.\"\n\n image path_to('new_experiment')\n note \"1. Under \u003cb\u003eMy Templates\u003c/b\u003e, click \" \\\n \"\u003cspan style=\\\"background-color:yellow\\\"\u003e\u003cb\u003e#{template}\u003c/b\u003e\u003c/span\u003e.\"\n\n note \"2. Enter \u003cspan style=\\\"background-color:yellow\\\"\u003e\u003cb\u003e#{name}\u003c/b\u003e\" \\\n \"\u003c/span\u003e in \u003cb\u003eName\u003c/b\u003e.\"\n\n note \"3. Click \u003cb\u003eCreate New Experiment\u003c/b\u003e.\"\n end\n end\n\n def verify_settings(events_to_record:)\n show do\n title \"Verify settings\"\n\n note \"Verify that \u003cb\u003eSample Stop Condition\u003c/b\u003e is set to \u003cb\u003eRecording and Sorting\u003c/b\u003e.\"\n note \"Under \u003cb\u003eRecording\u003c/b\u003e, verify that the \u003cb\u003eStop Condition\u003c/b\u003e is set to \" \\\n \"\u003cb\u003eEvent Count\u003c/b\u003e.\"\n note \"Verify that \u003cb\u003eStop Value\u003c/b\u003e is set to \" \\\n \"\u003cb\u003e#{number_with_delimiter(events_to_record)}\u003c/b\u003e.\"\n image path_to('stop_condition')\n end\n end\n\n def set_up_sorting(target_events: nil, args:{})\n args = default_sort_settings.merge(args)\n args[:left_stop_value] = target_events if target_events.present?\n [:left_stop_value, :right_stop_value].each do |stop_value|\n args[stop_value] = number_with_delimiter(args[stop_value])\n end\n auto_record = args[:auto_record] ? \"\u0026#10003;\" : \"\u0026#9634;\"\n sort_settings_table = [\n [\"\",\"\",\"\",\"\",\"\u003cb\u003eL\u003c/b\u003e\",\"\u003cb\u003eR\u003c/b\u003e\"],\n [\n \"\u003cb\u003eMethod:\u003c/b\u003e\",\n args[:method],\n \"#{auto_record} Auto Record\",\n \"\u003cb\u003eTo Sort:\u003c/b\u003e\",\n args[:left_gate],\n args[:right_gate]\n ],\n [\n \"\u003cb\u003eMode:\u003c/b\u003e\",\n args[:mode],\n args[:cell_size],\n \"\u003cb\u003eStop Value:\u003c/b\u003e\",\n args[:left_stop_value],\n args[:right_stop_value]\n ]\n ]\n\n show do\n title \"Set up sorting\"\n\n note \"Show/hide the \u003cb\u003eSort Control\u003c/b\u003e by clicking the \u003cb\u003e^\u003c/b\u003e along \" \\\n \"the bottom edge of the screen.\"\n\n note 'Select or verify the following settings:'\n table sort_settings_table\n end\n end\n\n def default_sort_settings\n {\n method: \"2 Way Tubes\",\n mode: \"Purity\",\n cell_size: \"Regular Cell\",\n left_gate: \"FITC positive\",\n right_gate: \"\",\n left_stop_value: \"\u0026nbsp;\" * 12,\n right_stop_value: \"\u0026nbsp;\" * 12,\n auto_record: true\n }\n end\n\n def set_and_run_tube(item:, tube_label:, tube_ct:, sort:)\n set_tube(item: item, tube_label: tube_label, tube_ct: tube_ct, sort: sort)\n load_tube(item: item, tube_label: tube_label, sort: sort)\n end\n\n def sort_tube(item:, tube_label:, default_to_sort:, gate_name:, debug:)\n place_tube_in_sorter(item_id: item.id, tube_label: tube_label)\n sort_library(item: item, default_to_sort: default_to_sort, gate_name: gate_name, debug: debug)\n end\n\n def set_tube(item:, tube_label:, tube_ct:, sort:)\n action = sort ? 'sort' : 'collect data for'\n software_tube_id = set_new_tube(\n action: action,\n tube_label: tube_label,\n tube_ct: tube_ct\n )\n\n add_software_tube_id(item: item, software_tube_id: software_tube_id)\n end\n\n def set_new_tube(action: , tube_label:, tube_ct:)\n software_tube_id = TUBE_LABEL_STUB % [tube_ct]\n\n show do\n title \"Set to #{action} #{tube_label}\"\n\n if tube_ct \u003e 1\n check new_tube\n image path_to('new_tube')\n end\n\n check verify_tube(software_tube_id)\n end\n\n software_tube_id\n end\n\n def add_software_tube_id(item:, software_tube_id:)\n sti = item.associations[:software_tube_id]\n\n if sti.present?\n sti = JSON.parse(sti)\n unless sti.kind_of?(Array)\n raise \"Corrupt :software_tube_id (#{sti}) for #{item}\"\n end\n else\n sti = []\n end\n\n sti \u003c\u003c software_tube_id\n item.associate(:software_tube_id, sti)\n end\n\n # TODO: Make this specific to Sony\n def load_tube(item:, tube_label:, sort:)\n show do\n title \"Load tube #{tube_label}\"\n\n note \"Vortex the tube for 3 Mississippi.\"\n note \"Place the sample tube holder in the sample loader.\"\n note \"If the sample loader door is closed, press the sample\n loader door button to open the sample loader door.\"\n\n note \"Click \u003cb\u003eStart\u003c/b\u003e.\"\n note \"After about 15 seconds, you will begin to see events displayed on the plots.\"\n\n if sort\n note \"Once a few thousand events have been recorded, click \u003cb\u003ePause\u003c/b\u003e.\"\n else\n note \"Click \u003cb\u003eRecord\u003c/b\u003e.\"\n note \"Once the instrument finishes recording, click \u003cb\u003eStop\u003c/b\u003e.\"\n end\n end\n end\n\n def place_tube_in_sorter(item_id:, tube_label:)\n show do\n title \"Prepare collection tube\"\n\n note \"Label a collection tube as \\\"#{item_id} - #{tube_label}.\\\"\"\n note \"Put 1.0 ml of PBS into the tube.\"\n\n note 'Place the holder in the sorter, if it is not already in.'\n image path_to('holder_placement', 'JPG')\n\n note \"Place the labeled tube into the \u003cb\u003eleft\u003c/b\u003e side of the holder.\"\n image path_to('place_tube', 'JPG')\n\n note \"Close the door and click \u003cb\u003eLoad Collection\u003c/b\u003e in the \u003cb\u003eSort Control\u003c/b\u003e pane.\"\n end\n end\n\n def sort_library(item:, default_to_sort:, gate_name:, debug:)\n data = show do\n title \"Record percentage of positive cells\"\n note \"Look in the table at the bottom of the data display for the \u003cb\u003e%Total\u003c/b\u003e \" \\\n \"in the \u003cb\u003e#{gate_name}\u003c/b\u003e gate.\"\n get \"number\", var: \"pct_positive\", label: \"Enter the \u003cb\u003e%Total\u003c/b\u003e\", default: 10\n end\n\n data[:pct_positive] = [10, 20, 50].sample if debug\n\n frac_positive = data[:pct_positive] / 100.0\n\n item.associate(:frac_positive, frac_positive)\n\n target_events = target_events(default_to_sort: default_to_sort, frac_positive: frac_positive)\n\n show do\n title \"Start sorting\"\n\n note \"In the \u003cb\u003eSort Control\u003c/b\u003e panel, set \u003cb\u003eTarget Events\u003c/b\u003e to \" \\\n \"#{number_with_delimiter(target_events)}\"\n\n note 'Click \u003cb\u003eSort \u0026 Record Start\u003c/b\u003e in the \u003cb\u003eSort Control\u003c/b\u003e pane.'\n warning 'Do not open the collection area door during sorting!'\n\n note \"Allow the sorter to run until #{number_with_delimiter(target_events)} \" \\\n \"have been collected in the \u003cb\u003e#{gate_name}\u003c/b\u003e gate.\"\n\n note \"If needed, increase the \u003cb\u003eSample Pressure\u003c/b\u003e until the \u003cb\u003eEvent Rate\u003c/b\u003e \" \\\n \"is up to \u003cb\u003e6000 eps\u003c/b\u003e, keeping the \u003cb\u003eSort Efficiency\u003c/b\u003e greater than 70%.\"\n end\n end\n\n def remove_tube_from_sorter(item:, collection_tube:)\n data = show do\n title \"Remove tube from sorter\"\n\n note \"Once the \u003cb\u003eTarget Events\u003c/b\u003e has been reached, or the sample is \" \\\n \"running too low:\"\n note \"Click \u003cb\u003eStop\u003c/b\u003e.\"\n\n separator\n\n get \"number\", var: \"sort_count\", label: \"Record the actual \u003cb\u003eSort Count\u003c/b\u003e\"\n\n separator\n\n note \"Remove the #{collection_tube} from the sorter, put the cap back on, \" \\\n \"and place it on ice.\"\n image path_to('place_tube', 'JPG')\n end\n\n data[:sort_count] = [10257, 205982, 501237].sample if debug\n\n item.associate(:sort_count, data[:sort_count])\n end\n\n def remove_sample_tube(sample_tube:)\n show do\n title \"Remove sample tube\"\n\n note \"Click \u003cb\u003eStop\u003c/b\u003e.\"\n note \"Remove the #{sample_tube} from the loader, put the cap back on, \" \\\n \"and place it on ice.\"\n end\n end\n\n def shutdown\n show do\n title \"Shutdown\"\n note \"Ask a lab manager how to shut down this instrument\"\n end\n end\n\n ########## COMMON LANGUAGE ##########\n\n def new_tube\n \"Click \u003cb\u003eNext tube\u003c/b\u003e.\"\n end\n\n def verify_tube(software_tube_id)\n \"Verify that the tube is labeled \u003cb\u003e#{software_tube_id}\u003c/b\u003e\"\n end\n\n end\nend"}}]}