From cf2b597c3142cabf72308486a58b94bbc04de057 Mon Sep 17 00:00:00 2001 From: Amanda Birmingham Date: Wed, 8 May 2019 23:14:40 -0700 Subject: [PATCH] Fixes #438 and #505 --- labcontrol/db/process.py | 82 +++- ...-max-vol-pooling-process-echo-picklist.txt | 385 ++++++++++++++++++ labcontrol/db/tests/test_process.py | 77 +++- 3 files changed, 511 insertions(+), 33 deletions(-) create mode 100644 labcontrol/db/tests/data/low-max-vol-pooling-process-echo-picklist.txt diff --git a/labcontrol/db/process.py b/labcontrol/db/process.py index 55d4e8d3..81e1baba 100644 --- a/labcontrol/db/process.py +++ b/labcontrol/db/process.py @@ -2520,35 +2520,75 @@ def _format_picklist(vol_sample, max_vol_per_well=60000, dest_plate_shape: list of 2 elements The destination plate shape """ + + def get_well_name(offset_from_ascii_ucase_a, col_num): + row_ascii_code = ord('A') + offset_from_ascii_ucase_a + row_letter = chr(row_ascii_code) + return "%s%d" % (row_letter, col_num) + if dest_plate_shape is None: dest_plate_shape = [16, 24] contents = ['Source Plate Name,Source Plate Type,Source Well,' 'Concentration,Transfer Volume,Destination Plate Name,' 'Destination Well'] - # Write the sample transfer volumes - rows, cols = vol_sample.shape + num_input_rows, num_input_cols = vol_sample.shape + running_tot = 0 + num_dest_rows = dest_plate_shape[0] + num_dest_cols = dest_plate_shape[1] + dest_well_index = 0 + # replace NaN values with 0s to leave a trail of unpooled wells pool_vols = np.nan_to_num(vol_sample) - running_tot = 0 - d = 1 - for i in range(rows): - for j in range(cols): - well_name = "%s%d" % (chr(ord('A') + i), j + 1) - # Machine will round, so just give it enough info to do the - # correct rounding. - val = "%.2f" % pool_vols[i][j] - # test to see if we will exceed total vol per well - if running_tot + pool_vols[i][j] > max_vol_per_well: - d += 1 - running_tot = pool_vols[i][j] + + for curr_input_row_index in range(num_input_rows): + for curr_input_col_index in range(num_input_cols): + curr_input_well_name = get_well_name(curr_input_row_index, + curr_input_col_index + 1) + + # test to see if adding the volume of this input well to the + # current dest well volume will exceed total vol per well + curr_input_well_vol = (pool_vols[curr_input_row_index] + [curr_input_col_index]) + + if curr_input_well_vol > max_vol_per_well: + raise ValueError("Volume {0} in input well {1} exceeds " + "maximum volume per well of " + "{2}".format(curr_input_well_vol, + curr_input_well_name, + max_vol_per_well)) + putative_vol = running_tot + curr_input_well_vol + if putative_vol > max_vol_per_well: + dest_well_index += 1 + running_tot = curr_input_well_vol else: - running_tot += pool_vols[i][j] - dest = "%s%d" % (chr(ord('A') + - int(np.floor(d / dest_plate_shape[0]))), - (d % dest_plate_shape[1])) - contents.append(",".join(['1', '384LDV_AQ_B2_HT', well_name, - "", val, 'NormalizedDNA', dest])) + running_tot = putative_vol + + # Echo will round the volume anyway, so just give it enough + # digits to do the correct rounding. + curr_input_transfer_vol = "%.2f" % curr_input_well_vol + + curr_output_row_offset = int(np.floor( + dest_well_index / num_dest_cols)) + + # NB: offset should never be as large as number of rows because + # first row has an offset of 0 + if curr_output_row_offset >= num_dest_rows: + raise ValueError("Destination well should be in row {0} " + "but destination plate has only {1} " + "rows".format((curr_output_row_offset+1), + num_dest_rows)) + + curr_output_col_num = (dest_well_index % num_dest_cols) + 1 + curr_dest_well_name = get_well_name(curr_output_row_offset, + curr_output_col_num) + + curr_output_line = ",".join( + ['1', '384LDV_AQ_B2_HT', curr_input_well_name, + "", curr_input_transfer_vol, + 'NormalizedDNA', curr_dest_well_name] + ) + contents.append(curr_output_line) return "\n".join(contents) @@ -2569,7 +2609,7 @@ def generate_echo_picklist(self, max_vol_per_well=30000): for comp, vol in self.components: well = comp.container vol_sample[well.row - 1][well.column - 1] = vol - return PoolingProcess._format_picklist(vol_sample) + return PoolingProcess._format_picklist(vol_sample, max_vol_per_well) def generate_epmotion_file(self): """Generates an EpMotion file to perform the pooling diff --git a/labcontrol/db/tests/data/low-max-vol-pooling-process-echo-picklist.txt b/labcontrol/db/tests/data/low-max-vol-pooling-process-echo-picklist.txt new file mode 100644 index 00000000..9319a029 --- /dev/null +++ b/labcontrol/db/tests/data/low-max-vol-pooling-process-echo-picklist.txt @@ -0,0 +1,385 @@ +Source Plate Name,Source Plate Type,Source Well,Concentration,Transfer Volume,Destination Plate Name,Destination Well +1,384LDV_AQ_B2_HT,A1,,1.00,NormalizedDNA,A1 +1,384LDV_AQ_B2_HT,A2,,1.00,NormalizedDNA,A2 +1,384LDV_AQ_B2_HT,A3,,1.00,NormalizedDNA,A3 +1,384LDV_AQ_B2_HT,A4,,1.00,NormalizedDNA,A4 +1,384LDV_AQ_B2_HT,A5,,1.00,NormalizedDNA,A5 +1,384LDV_AQ_B2_HT,A6,,1.00,NormalizedDNA,A6 +1,384LDV_AQ_B2_HT,A7,,1.00,NormalizedDNA,A7 +1,384LDV_AQ_B2_HT,A8,,1.00,NormalizedDNA,A8 +1,384LDV_AQ_B2_HT,A9,,1.00,NormalizedDNA,A9 +1,384LDV_AQ_B2_HT,A10,,1.00,NormalizedDNA,A10 +1,384LDV_AQ_B2_HT,A11,,1.00,NormalizedDNA,A11 +1,384LDV_AQ_B2_HT,A12,,1.00,NormalizedDNA,A12 +1,384LDV_AQ_B2_HT,A13,,1.00,NormalizedDNA,A13 +1,384LDV_AQ_B2_HT,A14,,1.00,NormalizedDNA,A14 +1,384LDV_AQ_B2_HT,A15,,1.00,NormalizedDNA,A15 +1,384LDV_AQ_B2_HT,A16,,1.00,NormalizedDNA,A16 +1,384LDV_AQ_B2_HT,A17,,1.00,NormalizedDNA,A17 +1,384LDV_AQ_B2_HT,A18,,1.00,NormalizedDNA,A18 +1,384LDV_AQ_B2_HT,A19,,1.00,NormalizedDNA,A19 +1,384LDV_AQ_B2_HT,A20,,1.00,NormalizedDNA,A20 +1,384LDV_AQ_B2_HT,A21,,1.00,NormalizedDNA,A21 +1,384LDV_AQ_B2_HT,A22,,1.00,NormalizedDNA,A22 +1,384LDV_AQ_B2_HT,A23,,1.00,NormalizedDNA,A23 +1,384LDV_AQ_B2_HT,A24,,1.00,NormalizedDNA,A24 +1,384LDV_AQ_B2_HT,B1,,1.00,NormalizedDNA,B1 +1,384LDV_AQ_B2_HT,B2,,1.00,NormalizedDNA,B2 +1,384LDV_AQ_B2_HT,B3,,1.00,NormalizedDNA,B3 +1,384LDV_AQ_B2_HT,B4,,1.00,NormalizedDNA,B4 +1,384LDV_AQ_B2_HT,B5,,1.00,NormalizedDNA,B5 +1,384LDV_AQ_B2_HT,B6,,1.00,NormalizedDNA,B6 +1,384LDV_AQ_B2_HT,B7,,1.00,NormalizedDNA,B7 +1,384LDV_AQ_B2_HT,B8,,1.00,NormalizedDNA,B8 +1,384LDV_AQ_B2_HT,B9,,1.00,NormalizedDNA,B9 +1,384LDV_AQ_B2_HT,B10,,1.00,NormalizedDNA,B10 +1,384LDV_AQ_B2_HT,B11,,1.00,NormalizedDNA,B11 +1,384LDV_AQ_B2_HT,B12,,1.00,NormalizedDNA,B12 +1,384LDV_AQ_B2_HT,B13,,1.00,NormalizedDNA,B13 +1,384LDV_AQ_B2_HT,B14,,1.00,NormalizedDNA,B14 +1,384LDV_AQ_B2_HT,B15,,1.00,NormalizedDNA,B15 +1,384LDV_AQ_B2_HT,B16,,1.00,NormalizedDNA,B16 +1,384LDV_AQ_B2_HT,B17,,1.00,NormalizedDNA,B17 +1,384LDV_AQ_B2_HT,B18,,1.00,NormalizedDNA,B18 +1,384LDV_AQ_B2_HT,B19,,1.00,NormalizedDNA,B19 +1,384LDV_AQ_B2_HT,B20,,1.00,NormalizedDNA,B20 +1,384LDV_AQ_B2_HT,B21,,1.00,NormalizedDNA,B21 +1,384LDV_AQ_B2_HT,B22,,1.00,NormalizedDNA,B22 +1,384LDV_AQ_B2_HT,B23,,1.00,NormalizedDNA,B23 +1,384LDV_AQ_B2_HT,B24,,1.00,NormalizedDNA,B24 +1,384LDV_AQ_B2_HT,C1,,1.00,NormalizedDNA,C1 +1,384LDV_AQ_B2_HT,C2,,1.00,NormalizedDNA,C2 +1,384LDV_AQ_B2_HT,C3,,1.00,NormalizedDNA,C3 +1,384LDV_AQ_B2_HT,C4,,1.00,NormalizedDNA,C4 +1,384LDV_AQ_B2_HT,C5,,1.00,NormalizedDNA,C5 +1,384LDV_AQ_B2_HT,C6,,1.00,NormalizedDNA,C6 +1,384LDV_AQ_B2_HT,C7,,1.00,NormalizedDNA,C7 +1,384LDV_AQ_B2_HT,C8,,1.00,NormalizedDNA,C8 +1,384LDV_AQ_B2_HT,C9,,1.00,NormalizedDNA,C9 +1,384LDV_AQ_B2_HT,C10,,1.00,NormalizedDNA,C10 +1,384LDV_AQ_B2_HT,C11,,1.00,NormalizedDNA,C11 +1,384LDV_AQ_B2_HT,C12,,1.00,NormalizedDNA,C12 +1,384LDV_AQ_B2_HT,C13,,1.00,NormalizedDNA,C13 +1,384LDV_AQ_B2_HT,C14,,1.00,NormalizedDNA,C14 +1,384LDV_AQ_B2_HT,C15,,1.00,NormalizedDNA,C15 +1,384LDV_AQ_B2_HT,C16,,1.00,NormalizedDNA,C16 +1,384LDV_AQ_B2_HT,C17,,1.00,NormalizedDNA,C17 +1,384LDV_AQ_B2_HT,C18,,1.00,NormalizedDNA,C18 +1,384LDV_AQ_B2_HT,C19,,1.00,NormalizedDNA,C19 +1,384LDV_AQ_B2_HT,C20,,1.00,NormalizedDNA,C20 +1,384LDV_AQ_B2_HT,C21,,1.00,NormalizedDNA,C21 +1,384LDV_AQ_B2_HT,C22,,1.00,NormalizedDNA,C22 +1,384LDV_AQ_B2_HT,C23,,1.00,NormalizedDNA,C23 +1,384LDV_AQ_B2_HT,C24,,1.00,NormalizedDNA,C24 +1,384LDV_AQ_B2_HT,D1,,1.00,NormalizedDNA,D1 +1,384LDV_AQ_B2_HT,D2,,1.00,NormalizedDNA,D2 +1,384LDV_AQ_B2_HT,D3,,1.00,NormalizedDNA,D3 +1,384LDV_AQ_B2_HT,D4,,1.00,NormalizedDNA,D4 +1,384LDV_AQ_B2_HT,D5,,1.00,NormalizedDNA,D5 +1,384LDV_AQ_B2_HT,D6,,1.00,NormalizedDNA,D6 +1,384LDV_AQ_B2_HT,D7,,1.00,NormalizedDNA,D7 +1,384LDV_AQ_B2_HT,D8,,1.00,NormalizedDNA,D8 +1,384LDV_AQ_B2_HT,D9,,1.00,NormalizedDNA,D9 +1,384LDV_AQ_B2_HT,D10,,1.00,NormalizedDNA,D10 +1,384LDV_AQ_B2_HT,D11,,1.00,NormalizedDNA,D11 +1,384LDV_AQ_B2_HT,D12,,1.00,NormalizedDNA,D12 +1,384LDV_AQ_B2_HT,D13,,1.00,NormalizedDNA,D13 +1,384LDV_AQ_B2_HT,D14,,1.00,NormalizedDNA,D14 +1,384LDV_AQ_B2_HT,D15,,1.00,NormalizedDNA,D15 +1,384LDV_AQ_B2_HT,D16,,1.00,NormalizedDNA,D16 +1,384LDV_AQ_B2_HT,D17,,1.00,NormalizedDNA,D17 +1,384LDV_AQ_B2_HT,D18,,1.00,NormalizedDNA,D18 +1,384LDV_AQ_B2_HT,D19,,1.00,NormalizedDNA,D19 +1,384LDV_AQ_B2_HT,D20,,1.00,NormalizedDNA,D20 +1,384LDV_AQ_B2_HT,D21,,1.00,NormalizedDNA,D21 +1,384LDV_AQ_B2_HT,D22,,1.00,NormalizedDNA,D22 +1,384LDV_AQ_B2_HT,D23,,1.00,NormalizedDNA,D23 +1,384LDV_AQ_B2_HT,D24,,1.00,NormalizedDNA,D24 +1,384LDV_AQ_B2_HT,E1,,1.00,NormalizedDNA,E1 +1,384LDV_AQ_B2_HT,E2,,1.00,NormalizedDNA,E2 +1,384LDV_AQ_B2_HT,E3,,1.00,NormalizedDNA,E3 +1,384LDV_AQ_B2_HT,E4,,1.00,NormalizedDNA,E4 +1,384LDV_AQ_B2_HT,E5,,1.00,NormalizedDNA,E5 +1,384LDV_AQ_B2_HT,E6,,1.00,NormalizedDNA,E6 +1,384LDV_AQ_B2_HT,E7,,1.00,NormalizedDNA,E7 +1,384LDV_AQ_B2_HT,E8,,1.00,NormalizedDNA,E8 +1,384LDV_AQ_B2_HT,E9,,1.00,NormalizedDNA,E9 +1,384LDV_AQ_B2_HT,E10,,1.00,NormalizedDNA,E10 +1,384LDV_AQ_B2_HT,E11,,1.00,NormalizedDNA,E11 +1,384LDV_AQ_B2_HT,E12,,1.00,NormalizedDNA,E12 +1,384LDV_AQ_B2_HT,E13,,1.00,NormalizedDNA,E13 +1,384LDV_AQ_B2_HT,E14,,1.00,NormalizedDNA,E14 +1,384LDV_AQ_B2_HT,E15,,1.00,NormalizedDNA,E15 +1,384LDV_AQ_B2_HT,E16,,1.00,NormalizedDNA,E16 +1,384LDV_AQ_B2_HT,E17,,1.00,NormalizedDNA,E17 +1,384LDV_AQ_B2_HT,E18,,1.00,NormalizedDNA,E18 +1,384LDV_AQ_B2_HT,E19,,1.00,NormalizedDNA,E19 +1,384LDV_AQ_B2_HT,E20,,1.00,NormalizedDNA,E20 +1,384LDV_AQ_B2_HT,E21,,1.00,NormalizedDNA,E21 +1,384LDV_AQ_B2_HT,E22,,1.00,NormalizedDNA,E22 +1,384LDV_AQ_B2_HT,E23,,1.00,NormalizedDNA,E23 +1,384LDV_AQ_B2_HT,E24,,1.00,NormalizedDNA,E24 +1,384LDV_AQ_B2_HT,F1,,1.00,NormalizedDNA,F1 +1,384LDV_AQ_B2_HT,F2,,1.00,NormalizedDNA,F2 +1,384LDV_AQ_B2_HT,F3,,1.00,NormalizedDNA,F3 +1,384LDV_AQ_B2_HT,F4,,1.00,NormalizedDNA,F4 +1,384LDV_AQ_B2_HT,F5,,1.00,NormalizedDNA,F5 +1,384LDV_AQ_B2_HT,F6,,1.00,NormalizedDNA,F6 +1,384LDV_AQ_B2_HT,F7,,1.00,NormalizedDNA,F7 +1,384LDV_AQ_B2_HT,F8,,1.00,NormalizedDNA,F8 +1,384LDV_AQ_B2_HT,F9,,1.00,NormalizedDNA,F9 +1,384LDV_AQ_B2_HT,F10,,1.00,NormalizedDNA,F10 +1,384LDV_AQ_B2_HT,F11,,1.00,NormalizedDNA,F11 +1,384LDV_AQ_B2_HT,F12,,1.00,NormalizedDNA,F12 +1,384LDV_AQ_B2_HT,F13,,1.00,NormalizedDNA,F13 +1,384LDV_AQ_B2_HT,F14,,1.00,NormalizedDNA,F14 +1,384LDV_AQ_B2_HT,F15,,1.00,NormalizedDNA,F15 +1,384LDV_AQ_B2_HT,F16,,1.00,NormalizedDNA,F16 +1,384LDV_AQ_B2_HT,F17,,1.00,NormalizedDNA,F17 +1,384LDV_AQ_B2_HT,F18,,1.00,NormalizedDNA,F18 +1,384LDV_AQ_B2_HT,F19,,1.00,NormalizedDNA,F19 +1,384LDV_AQ_B2_HT,F20,,1.00,NormalizedDNA,F20 +1,384LDV_AQ_B2_HT,F21,,1.00,NormalizedDNA,F21 +1,384LDV_AQ_B2_HT,F22,,1.00,NormalizedDNA,F22 +1,384LDV_AQ_B2_HT,F23,,1.00,NormalizedDNA,F23 +1,384LDV_AQ_B2_HT,F24,,1.00,NormalizedDNA,F24 +1,384LDV_AQ_B2_HT,G1,,1.00,NormalizedDNA,G1 +1,384LDV_AQ_B2_HT,G2,,1.00,NormalizedDNA,G2 +1,384LDV_AQ_B2_HT,G3,,1.00,NormalizedDNA,G3 +1,384LDV_AQ_B2_HT,G4,,1.00,NormalizedDNA,G4 +1,384LDV_AQ_B2_HT,G5,,1.00,NormalizedDNA,G5 +1,384LDV_AQ_B2_HT,G6,,1.00,NormalizedDNA,G6 +1,384LDV_AQ_B2_HT,G7,,1.00,NormalizedDNA,G7 +1,384LDV_AQ_B2_HT,G8,,1.00,NormalizedDNA,G8 +1,384LDV_AQ_B2_HT,G9,,1.00,NormalizedDNA,G9 +1,384LDV_AQ_B2_HT,G10,,1.00,NormalizedDNA,G10 +1,384LDV_AQ_B2_HT,G11,,1.00,NormalizedDNA,G11 +1,384LDV_AQ_B2_HT,G12,,1.00,NormalizedDNA,G12 +1,384LDV_AQ_B2_HT,G13,,1.00,NormalizedDNA,G13 +1,384LDV_AQ_B2_HT,G14,,1.00,NormalizedDNA,G14 +1,384LDV_AQ_B2_HT,G15,,1.00,NormalizedDNA,G15 +1,384LDV_AQ_B2_HT,G16,,1.00,NormalizedDNA,G16 +1,384LDV_AQ_B2_HT,G17,,1.00,NormalizedDNA,G17 +1,384LDV_AQ_B2_HT,G18,,1.00,NormalizedDNA,G18 +1,384LDV_AQ_B2_HT,G19,,1.00,NormalizedDNA,G19 +1,384LDV_AQ_B2_HT,G20,,1.00,NormalizedDNA,G20 +1,384LDV_AQ_B2_HT,G21,,1.00,NormalizedDNA,G21 +1,384LDV_AQ_B2_HT,G22,,1.00,NormalizedDNA,G22 +1,384LDV_AQ_B2_HT,G23,,1.00,NormalizedDNA,G23 +1,384LDV_AQ_B2_HT,G24,,1.00,NormalizedDNA,G24 +1,384LDV_AQ_B2_HT,H1,,1.00,NormalizedDNA,H1 +1,384LDV_AQ_B2_HT,H2,,1.00,NormalizedDNA,H2 +1,384LDV_AQ_B2_HT,H3,,1.00,NormalizedDNA,H3 +1,384LDV_AQ_B2_HT,H4,,1.00,NormalizedDNA,H4 +1,384LDV_AQ_B2_HT,H5,,1.00,NormalizedDNA,H5 +1,384LDV_AQ_B2_HT,H6,,1.00,NormalizedDNA,H6 +1,384LDV_AQ_B2_HT,H7,,1.00,NormalizedDNA,H7 +1,384LDV_AQ_B2_HT,H8,,1.00,NormalizedDNA,H8 +1,384LDV_AQ_B2_HT,H9,,1.00,NormalizedDNA,H9 +1,384LDV_AQ_B2_HT,H10,,1.00,NormalizedDNA,H10 +1,384LDV_AQ_B2_HT,H11,,1.00,NormalizedDNA,H11 +1,384LDV_AQ_B2_HT,H12,,1.00,NormalizedDNA,H12 +1,384LDV_AQ_B2_HT,H13,,1.00,NormalizedDNA,H13 +1,384LDV_AQ_B2_HT,H14,,1.00,NormalizedDNA,H14 +1,384LDV_AQ_B2_HT,H15,,1.00,NormalizedDNA,H15 +1,384LDV_AQ_B2_HT,H16,,1.00,NormalizedDNA,H16 +1,384LDV_AQ_B2_HT,H17,,1.00,NormalizedDNA,H17 +1,384LDV_AQ_B2_HT,H18,,1.00,NormalizedDNA,H18 +1,384LDV_AQ_B2_HT,H19,,1.00,NormalizedDNA,H19 +1,384LDV_AQ_B2_HT,H20,,1.00,NormalizedDNA,H20 +1,384LDV_AQ_B2_HT,H21,,1.00,NormalizedDNA,H21 +1,384LDV_AQ_B2_HT,H22,,1.00,NormalizedDNA,H22 +1,384LDV_AQ_B2_HT,H23,,1.00,NormalizedDNA,H23 +1,384LDV_AQ_B2_HT,H24,,1.00,NormalizedDNA,H24 +1,384LDV_AQ_B2_HT,I1,,1.00,NormalizedDNA,I1 +1,384LDV_AQ_B2_HT,I2,,1.00,NormalizedDNA,I2 +1,384LDV_AQ_B2_HT,I3,,1.00,NormalizedDNA,I3 +1,384LDV_AQ_B2_HT,I4,,1.00,NormalizedDNA,I4 +1,384LDV_AQ_B2_HT,I5,,1.00,NormalizedDNA,I5 +1,384LDV_AQ_B2_HT,I6,,1.00,NormalizedDNA,I6 +1,384LDV_AQ_B2_HT,I7,,1.00,NormalizedDNA,I7 +1,384LDV_AQ_B2_HT,I8,,1.00,NormalizedDNA,I8 +1,384LDV_AQ_B2_HT,I9,,1.00,NormalizedDNA,I9 +1,384LDV_AQ_B2_HT,I10,,1.00,NormalizedDNA,I10 +1,384LDV_AQ_B2_HT,I11,,1.00,NormalizedDNA,I11 +1,384LDV_AQ_B2_HT,I12,,1.00,NormalizedDNA,I12 +1,384LDV_AQ_B2_HT,I13,,1.00,NormalizedDNA,I13 +1,384LDV_AQ_B2_HT,I14,,1.00,NormalizedDNA,I14 +1,384LDV_AQ_B2_HT,I15,,1.00,NormalizedDNA,I15 +1,384LDV_AQ_B2_HT,I16,,1.00,NormalizedDNA,I16 +1,384LDV_AQ_B2_HT,I17,,1.00,NormalizedDNA,I17 +1,384LDV_AQ_B2_HT,I18,,1.00,NormalizedDNA,I18 +1,384LDV_AQ_B2_HT,I19,,1.00,NormalizedDNA,I19 +1,384LDV_AQ_B2_HT,I20,,1.00,NormalizedDNA,I20 +1,384LDV_AQ_B2_HT,I21,,1.00,NormalizedDNA,I21 +1,384LDV_AQ_B2_HT,I22,,1.00,NormalizedDNA,I22 +1,384LDV_AQ_B2_HT,I23,,1.00,NormalizedDNA,I23 +1,384LDV_AQ_B2_HT,I24,,1.00,NormalizedDNA,I24 +1,384LDV_AQ_B2_HT,J1,,1.00,NormalizedDNA,J1 +1,384LDV_AQ_B2_HT,J2,,1.00,NormalizedDNA,J2 +1,384LDV_AQ_B2_HT,J3,,1.00,NormalizedDNA,J3 +1,384LDV_AQ_B2_HT,J4,,1.00,NormalizedDNA,J4 +1,384LDV_AQ_B2_HT,J5,,1.00,NormalizedDNA,J5 +1,384LDV_AQ_B2_HT,J6,,1.00,NormalizedDNA,J6 +1,384LDV_AQ_B2_HT,J7,,1.00,NormalizedDNA,J7 +1,384LDV_AQ_B2_HT,J8,,1.00,NormalizedDNA,J8 +1,384LDV_AQ_B2_HT,J9,,1.00,NormalizedDNA,J9 +1,384LDV_AQ_B2_HT,J10,,1.00,NormalizedDNA,J10 +1,384LDV_AQ_B2_HT,J11,,1.00,NormalizedDNA,J11 +1,384LDV_AQ_B2_HT,J12,,1.00,NormalizedDNA,J12 +1,384LDV_AQ_B2_HT,J13,,1.00,NormalizedDNA,J13 +1,384LDV_AQ_B2_HT,J14,,1.00,NormalizedDNA,J14 +1,384LDV_AQ_B2_HT,J15,,1.00,NormalizedDNA,J15 +1,384LDV_AQ_B2_HT,J16,,1.00,NormalizedDNA,J16 +1,384LDV_AQ_B2_HT,J17,,1.00,NormalizedDNA,J17 +1,384LDV_AQ_B2_HT,J18,,1.00,NormalizedDNA,J18 +1,384LDV_AQ_B2_HT,J19,,1.00,NormalizedDNA,J19 +1,384LDV_AQ_B2_HT,J20,,1.00,NormalizedDNA,J20 +1,384LDV_AQ_B2_HT,J21,,1.00,NormalizedDNA,J21 +1,384LDV_AQ_B2_HT,J22,,1.00,NormalizedDNA,J22 +1,384LDV_AQ_B2_HT,J23,,1.00,NormalizedDNA,J23 +1,384LDV_AQ_B2_HT,J24,,1.00,NormalizedDNA,J24 +1,384LDV_AQ_B2_HT,K1,,1.00,NormalizedDNA,K1 +1,384LDV_AQ_B2_HT,K2,,1.00,NormalizedDNA,K2 +1,384LDV_AQ_B2_HT,K3,,1.00,NormalizedDNA,K3 +1,384LDV_AQ_B2_HT,K4,,1.00,NormalizedDNA,K4 +1,384LDV_AQ_B2_HT,K5,,1.00,NormalizedDNA,K5 +1,384LDV_AQ_B2_HT,K6,,1.00,NormalizedDNA,K6 +1,384LDV_AQ_B2_HT,K7,,1.00,NormalizedDNA,K7 +1,384LDV_AQ_B2_HT,K8,,1.00,NormalizedDNA,K8 +1,384LDV_AQ_B2_HT,K9,,1.00,NormalizedDNA,K9 +1,384LDV_AQ_B2_HT,K10,,1.00,NormalizedDNA,K10 +1,384LDV_AQ_B2_HT,K11,,1.00,NormalizedDNA,K11 +1,384LDV_AQ_B2_HT,K12,,1.00,NormalizedDNA,K12 +1,384LDV_AQ_B2_HT,K13,,1.00,NormalizedDNA,K13 +1,384LDV_AQ_B2_HT,K14,,1.00,NormalizedDNA,K14 +1,384LDV_AQ_B2_HT,K15,,1.00,NormalizedDNA,K15 +1,384LDV_AQ_B2_HT,K16,,1.00,NormalizedDNA,K16 +1,384LDV_AQ_B2_HT,K17,,1.00,NormalizedDNA,K17 +1,384LDV_AQ_B2_HT,K18,,1.00,NormalizedDNA,K18 +1,384LDV_AQ_B2_HT,K19,,1.00,NormalizedDNA,K19 +1,384LDV_AQ_B2_HT,K20,,1.00,NormalizedDNA,K20 +1,384LDV_AQ_B2_HT,K21,,1.00,NormalizedDNA,K21 +1,384LDV_AQ_B2_HT,K22,,1.00,NormalizedDNA,K22 +1,384LDV_AQ_B2_HT,K23,,1.00,NormalizedDNA,K23 +1,384LDV_AQ_B2_HT,K24,,1.00,NormalizedDNA,K24 +1,384LDV_AQ_B2_HT,L1,,1.00,NormalizedDNA,L1 +1,384LDV_AQ_B2_HT,L2,,1.00,NormalizedDNA,L2 +1,384LDV_AQ_B2_HT,L3,,1.00,NormalizedDNA,L3 +1,384LDV_AQ_B2_HT,L4,,1.00,NormalizedDNA,L4 +1,384LDV_AQ_B2_HT,L5,,1.00,NormalizedDNA,L5 +1,384LDV_AQ_B2_HT,L6,,1.00,NormalizedDNA,L6 +1,384LDV_AQ_B2_HT,L7,,1.00,NormalizedDNA,L7 +1,384LDV_AQ_B2_HT,L8,,1.00,NormalizedDNA,L8 +1,384LDV_AQ_B2_HT,L9,,1.00,NormalizedDNA,L9 +1,384LDV_AQ_B2_HT,L10,,1.00,NormalizedDNA,L10 +1,384LDV_AQ_B2_HT,L11,,1.00,NormalizedDNA,L11 +1,384LDV_AQ_B2_HT,L12,,1.00,NormalizedDNA,L12 +1,384LDV_AQ_B2_HT,L13,,1.00,NormalizedDNA,L13 +1,384LDV_AQ_B2_HT,L14,,1.00,NormalizedDNA,L14 +1,384LDV_AQ_B2_HT,L15,,1.00,NormalizedDNA,L15 +1,384LDV_AQ_B2_HT,L16,,1.00,NormalizedDNA,L16 +1,384LDV_AQ_B2_HT,L17,,1.00,NormalizedDNA,L17 +1,384LDV_AQ_B2_HT,L18,,1.00,NormalizedDNA,L18 +1,384LDV_AQ_B2_HT,L19,,1.00,NormalizedDNA,L19 +1,384LDV_AQ_B2_HT,L20,,1.00,NormalizedDNA,L20 +1,384LDV_AQ_B2_HT,L21,,1.00,NormalizedDNA,L21 +1,384LDV_AQ_B2_HT,L22,,1.00,NormalizedDNA,L22 +1,384LDV_AQ_B2_HT,L23,,1.00,NormalizedDNA,L23 +1,384LDV_AQ_B2_HT,L24,,1.00,NormalizedDNA,L24 +1,384LDV_AQ_B2_HT,M1,,1.00,NormalizedDNA,M1 +1,384LDV_AQ_B2_HT,M2,,1.00,NormalizedDNA,M2 +1,384LDV_AQ_B2_HT,M3,,1.00,NormalizedDNA,M3 +1,384LDV_AQ_B2_HT,M4,,1.00,NormalizedDNA,M4 +1,384LDV_AQ_B2_HT,M5,,1.00,NormalizedDNA,M5 +1,384LDV_AQ_B2_HT,M6,,1.00,NormalizedDNA,M6 +1,384LDV_AQ_B2_HT,M7,,1.00,NormalizedDNA,M7 +1,384LDV_AQ_B2_HT,M8,,1.00,NormalizedDNA,M8 +1,384LDV_AQ_B2_HT,M9,,1.00,NormalizedDNA,M9 +1,384LDV_AQ_B2_HT,M10,,1.00,NormalizedDNA,M10 +1,384LDV_AQ_B2_HT,M11,,1.00,NormalizedDNA,M11 +1,384LDV_AQ_B2_HT,M12,,1.00,NormalizedDNA,M12 +1,384LDV_AQ_B2_HT,M13,,1.00,NormalizedDNA,M13 +1,384LDV_AQ_B2_HT,M14,,1.00,NormalizedDNA,M14 +1,384LDV_AQ_B2_HT,M15,,1.00,NormalizedDNA,M15 +1,384LDV_AQ_B2_HT,M16,,1.00,NormalizedDNA,M16 +1,384LDV_AQ_B2_HT,M17,,1.00,NormalizedDNA,M17 +1,384LDV_AQ_B2_HT,M18,,1.00,NormalizedDNA,M18 +1,384LDV_AQ_B2_HT,M19,,1.00,NormalizedDNA,M19 +1,384LDV_AQ_B2_HT,M20,,1.00,NormalizedDNA,M20 +1,384LDV_AQ_B2_HT,M21,,1.00,NormalizedDNA,M21 +1,384LDV_AQ_B2_HT,M22,,1.00,NormalizedDNA,M22 +1,384LDV_AQ_B2_HT,M23,,1.00,NormalizedDNA,M23 +1,384LDV_AQ_B2_HT,M24,,1.00,NormalizedDNA,M24 +1,384LDV_AQ_B2_HT,N1,,1.00,NormalizedDNA,N1 +1,384LDV_AQ_B2_HT,N2,,1.00,NormalizedDNA,N2 +1,384LDV_AQ_B2_HT,N3,,1.00,NormalizedDNA,N3 +1,384LDV_AQ_B2_HT,N4,,1.00,NormalizedDNA,N4 +1,384LDV_AQ_B2_HT,N5,,1.00,NormalizedDNA,N5 +1,384LDV_AQ_B2_HT,N6,,1.00,NormalizedDNA,N6 +1,384LDV_AQ_B2_HT,N7,,1.00,NormalizedDNA,N7 +1,384LDV_AQ_B2_HT,N8,,1.00,NormalizedDNA,N8 +1,384LDV_AQ_B2_HT,N9,,1.00,NormalizedDNA,N9 +1,384LDV_AQ_B2_HT,N10,,1.00,NormalizedDNA,N10 +1,384LDV_AQ_B2_HT,N11,,1.00,NormalizedDNA,N11 +1,384LDV_AQ_B2_HT,N12,,1.00,NormalizedDNA,N12 +1,384LDV_AQ_B2_HT,N13,,1.00,NormalizedDNA,N13 +1,384LDV_AQ_B2_HT,N14,,1.00,NormalizedDNA,N14 +1,384LDV_AQ_B2_HT,N15,,1.00,NormalizedDNA,N15 +1,384LDV_AQ_B2_HT,N16,,1.00,NormalizedDNA,N16 +1,384LDV_AQ_B2_HT,N17,,1.00,NormalizedDNA,N17 +1,384LDV_AQ_B2_HT,N18,,1.00,NormalizedDNA,N18 +1,384LDV_AQ_B2_HT,N19,,1.00,NormalizedDNA,N19 +1,384LDV_AQ_B2_HT,N20,,1.00,NormalizedDNA,N20 +1,384LDV_AQ_B2_HT,N21,,1.00,NormalizedDNA,N21 +1,384LDV_AQ_B2_HT,N22,,1.00,NormalizedDNA,N22 +1,384LDV_AQ_B2_HT,N23,,1.00,NormalizedDNA,N23 +1,384LDV_AQ_B2_HT,N24,,1.00,NormalizedDNA,N24 +1,384LDV_AQ_B2_HT,O1,,1.00,NormalizedDNA,O1 +1,384LDV_AQ_B2_HT,O2,,1.00,NormalizedDNA,O2 +1,384LDV_AQ_B2_HT,O3,,1.00,NormalizedDNA,O3 +1,384LDV_AQ_B2_HT,O4,,1.00,NormalizedDNA,O4 +1,384LDV_AQ_B2_HT,O5,,1.00,NormalizedDNA,O5 +1,384LDV_AQ_B2_HT,O6,,1.00,NormalizedDNA,O6 +1,384LDV_AQ_B2_HT,O7,,1.00,NormalizedDNA,O7 +1,384LDV_AQ_B2_HT,O8,,1.00,NormalizedDNA,O8 +1,384LDV_AQ_B2_HT,O9,,1.00,NormalizedDNA,O9 +1,384LDV_AQ_B2_HT,O10,,1.00,NormalizedDNA,O10 +1,384LDV_AQ_B2_HT,O11,,1.00,NormalizedDNA,O11 +1,384LDV_AQ_B2_HT,O12,,1.00,NormalizedDNA,O12 +1,384LDV_AQ_B2_HT,O13,,1.00,NormalizedDNA,O13 +1,384LDV_AQ_B2_HT,O14,,1.00,NormalizedDNA,O14 +1,384LDV_AQ_B2_HT,O15,,1.00,NormalizedDNA,O15 +1,384LDV_AQ_B2_HT,O16,,1.00,NormalizedDNA,O16 +1,384LDV_AQ_B2_HT,O17,,1.00,NormalizedDNA,O17 +1,384LDV_AQ_B2_HT,O18,,1.00,NormalizedDNA,O18 +1,384LDV_AQ_B2_HT,O19,,1.00,NormalizedDNA,O19 +1,384LDV_AQ_B2_HT,O20,,1.00,NormalizedDNA,O20 +1,384LDV_AQ_B2_HT,O21,,1.00,NormalizedDNA,O21 +1,384LDV_AQ_B2_HT,O22,,1.00,NormalizedDNA,O22 +1,384LDV_AQ_B2_HT,O23,,0.00,NormalizedDNA,O22 +1,384LDV_AQ_B2_HT,O24,,0.00,NormalizedDNA,O22 +1,384LDV_AQ_B2_HT,P1,,1.00,NormalizedDNA,O23 +1,384LDV_AQ_B2_HT,P2,,1.00,NormalizedDNA,O24 +1,384LDV_AQ_B2_HT,P3,,1.00,NormalizedDNA,P1 +1,384LDV_AQ_B2_HT,P4,,1.00,NormalizedDNA,P2 +1,384LDV_AQ_B2_HT,P5,,1.00,NormalizedDNA,P3 +1,384LDV_AQ_B2_HT,P6,,1.00,NormalizedDNA,P4 +1,384LDV_AQ_B2_HT,P7,,1.00,NormalizedDNA,P5 +1,384LDV_AQ_B2_HT,P8,,1.00,NormalizedDNA,P6 +1,384LDV_AQ_B2_HT,P9,,1.00,NormalizedDNA,P7 +1,384LDV_AQ_B2_HT,P10,,1.00,NormalizedDNA,P8 +1,384LDV_AQ_B2_HT,P11,,1.00,NormalizedDNA,P9 +1,384LDV_AQ_B2_HT,P12,,1.00,NormalizedDNA,P10 +1,384LDV_AQ_B2_HT,P13,,1.00,NormalizedDNA,P11 +1,384LDV_AQ_B2_HT,P14,,1.00,NormalizedDNA,P12 +1,384LDV_AQ_B2_HT,P15,,1.00,NormalizedDNA,P13 +1,384LDV_AQ_B2_HT,P16,,1.00,NormalizedDNA,P14 +1,384LDV_AQ_B2_HT,P17,,1.00,NormalizedDNA,P15 +1,384LDV_AQ_B2_HT,P18,,1.00,NormalizedDNA,P16 +1,384LDV_AQ_B2_HT,P19,,1.00,NormalizedDNA,P17 +1,384LDV_AQ_B2_HT,P20,,1.00,NormalizedDNA,P18 +1,384LDV_AQ_B2_HT,P21,,1.00,NormalizedDNA,P19 +1,384LDV_AQ_B2_HT,P22,,1.00,NormalizedDNA,P20 +1,384LDV_AQ_B2_HT,P23,,0.00,NormalizedDNA,P20 +1,384LDV_AQ_B2_HT,P24,,0.00,NormalizedDNA,P20 \ No newline at end of file diff --git a/labcontrol/db/tests/test_process.py b/labcontrol/db/tests/test_process.py index d2ccd4c7..6e87adcc 100644 --- a/labcontrol/db/tests/test_process.py +++ b/labcontrol/db/tests/test_process.py @@ -48,6 +48,8 @@ def load_data(filename): 'experimental-plus-samples-prep-example.txt') SHOTGUN_SAMPLE_SHEET = load_data("shotgun_sample_sheet.txt") POOLING_PROCESS_ECHO_PICKLIST = load_data("pooling-process-echo-picklist.txt") +LOW_MAX_VOL_POOLING_PROCESS_ECHO_PICKLIST = load_data( + "low-max-vol-pooling-process-echo-picklist.txt") def _help_compare_timestamps(input_datetime): @@ -1378,21 +1380,65 @@ def test_create(self): self.assertEqual(obs.pooling_function_data, func_data) def test_format_picklist(self): - vol_sample = np.array([[10.00, 10.00, np.nan, 5.00, 10.00, 10.00]]) - header = ['Source Plate Name,Source Plate Type,Source Well,' - 'Concentration,Transfer Volume,Destination Plate Name,' - 'Destination Well'] - exp_values = ['1,384LDV_AQ_B2_HT,A1,,10.00,NormalizedDNA,A1', - '1,384LDV_AQ_B2_HT,A2,,10.00,NormalizedDNA,A1', - '1,384LDV_AQ_B2_HT,A3,,0.00,NormalizedDNA,A1', - '1,384LDV_AQ_B2_HT,A4,,5.00,NormalizedDNA,A1', - '1,384LDV_AQ_B2_HT,A5,,10.00,NormalizedDNA,A2', - '1,384LDV_AQ_B2_HT,A6,,10.00,NormalizedDNA,A2'] - exp_str = '\n'.join(header + exp_values) + # input volumes from a hypothetical 3x4 plate + vol_sample = np.array([ + [10.00, 10.00, np.nan], + [5.00, 10.00, 10.00], + [10.00, 10.00, 10.00], + [10.00, np.nan, 10.00] + ]) + + exp_header = 'Source Plate Name,Source Plate Type,Source Well,' \ + 'Concentration,Transfer Volume,Destination Plate Name,' \ + 'Destination Well\n' + exp_body = """1,384LDV_AQ_B2_HT,A1,,10.00,NormalizedDNA,A1 +1,384LDV_AQ_B2_HT,A2,,10.00,NormalizedDNA,A1 +1,384LDV_AQ_B2_HT,A3,,0.00,NormalizedDNA,A1 +1,384LDV_AQ_B2_HT,B1,,5.00,NormalizedDNA,A1 +1,384LDV_AQ_B2_HT,B2,,10.00,NormalizedDNA,A2 +1,384LDV_AQ_B2_HT,B3,,10.00,NormalizedDNA,A2 +1,384LDV_AQ_B2_HT,C1,,10.00,NormalizedDNA,B1 +1,384LDV_AQ_B2_HT,C2,,10.00,NormalizedDNA,B1 +1,384LDV_AQ_B2_HT,C3,,10.00,NormalizedDNA,B2 +1,384LDV_AQ_B2_HT,D1,,10.00,NormalizedDNA,B2 +1,384LDV_AQ_B2_HT,D2,,0.00,NormalizedDNA,B2 +1,384LDV_AQ_B2_HT,D3,,10.00,NormalizedDNA,C1""" + exp_str = exp_header+exp_body + obs_str = PoolingProcess._format_picklist( - vol_sample, max_vol_per_well=26, dest_plate_shape=[16, 24]) + vol_sample, max_vol_per_well=26, dest_plate_shape=[3, 2]) self.assertEqual(exp_str, obs_str) + def test_format_picklist_error_dest_plate_too_small(self): + # input volumes from a hypothetical 3x4 plate + vol_sample = np.array([ + [10.00, 10.00, np.nan], + [5.00, 10.00, 10.00], + [10.00, 10.00, 10.00], + [10.00, np.nan, 10.00] + ]) + + exp_err = "Destination well should be in row 3 but destination " \ + "plate has only 2 rows" + with self.assertRaisesRegex(ValueError, exp_err): + PoolingProcess._format_picklist(vol_sample, max_vol_per_well=26, + dest_plate_shape=[2, 2]) + + def test_format_picklist_error_input_vol_too_large(self): + # input volumes from a hypothetical 3x4 plate + vol_sample = np.array([ + [1.00, 1.00, np.nan], + [5.00, 10.00, 1.00], + [1.00, 1.00, 10.00], + [10.00, np.nan, 10.00] + ]) + + exp_err = "Volume 10.0 in input well B2 exceeds maximum volume per " \ + "well of 7" + with self.assertRaisesRegex(ValueError, exp_err): + PoolingProcess._format_picklist(vol_sample, max_vol_per_well=7, + dest_plate_shape=[3, 2]) + def test_generate_echo_picklist_default(self): # With the default max_vol_per_well value of 30000 nL # (and with anything higher than that, such as the past default of @@ -1400,6 +1446,13 @@ def test_generate_echo_picklist_default(self): obs = PoolingProcess(3).generate_echo_picklist() self.assertEqual(obs, POOLING_PROCESS_ECHO_PICKLIST) + def test_generate_echo_picklist_nondefault_volume(self): + # Setting the max_vol_per_well value to 1 nL, which is the volume in + # each non-empty input well in PoolingProcess(3), puts the contents of + # one non-empty input well into each output well. + obs = PoolingProcess(3).generate_echo_picklist(1) + self.assertEqual(obs, LOW_MAX_VOL_POOLING_PROCESS_ECHO_PICKLIST) + def test_generate_epmotion_file(self): obs = PoolingProcess(1).generate_epmotion_file() obs_lines = obs.splitlines()