diff --git a/abr-testing/abr_testing/data_collection/abr_google_drive.py b/abr-testing/abr_testing/data_collection/abr_google_drive.py index 6552534c4ae..8f82567a7d1 100644 --- a/abr-testing/abr_testing/data_collection/abr_google_drive.py +++ b/abr-testing/abr_testing/data_collection/abr_google_drive.py @@ -44,6 +44,7 @@ def create_data_dictionary( headers: List[str] = [] headers_lpc: List[str] = [] list_of_heights: List[List[Any]] = [[], [], [], [], [], [], [], []] + hellma_plate_orientation = False # default hellma plate is not rotated. for filename in os.listdir(storage_directory): file_path = os.path.join(storage_directory, filename) if file_path.endswith(".json"): @@ -67,6 +68,10 @@ def create_data_dictionary( if run_id in runs_to_save: print(f"started reading run {run_id}.") robot = file_results.get("robot_name") + parameters = file_results.get("runTimeParameters", "") + for parameter in parameters: + if parameter["displayName"] == "Hellma Plate Orientation": + hellma_plate_orientation = bool(parameter["value"]) protocol_name = file_results["protocol"]["metadata"].get("protocolName", "") software_version = file_results.get("API_Version", "") left_pipette = file_results.get("left", "") @@ -123,7 +128,7 @@ def create_data_dictionary( file_results, labware_name="opentrons_tough_pcr_auto_sealing_lid" ) plate_reader_dict = read_robot_logs.plate_reader_commands( - file_results, hellma_plate_standards + file_results, hellma_plate_standards, hellma_plate_orientation ) list_of_heights = read_robot_logs.liquid_height_commands( file_results, list_of_heights diff --git a/abr-testing/abr_testing/data_collection/read_robot_logs.py b/abr-testing/abr_testing/data_collection/read_robot_logs.py index 40712118fe5..7bc83e0a54b 100644 --- a/abr-testing/abr_testing/data_collection/read_robot_logs.py +++ b/abr-testing/abr_testing/data_collection/read_robot_logs.py @@ -250,7 +250,9 @@ def liquid_height_commands( def plate_reader_commands( - file_results: Dict[str, Any], hellma_plate_standards: List[Dict[str, Any]] + file_results: Dict[str, Any], + hellma_plate_standards: List[Dict[str, Any]], + orientation: bool, ) -> Dict[str, object]: """Plate Reader Command Counts.""" commandData = file_results.get("commands", "") @@ -279,38 +281,46 @@ def plate_reader_commands( read = "yes" elif read == "yes" and commandType == "comment": result = command["params"].get("message", "") - formatted_result = result.split("result: ")[1] - result_dict = eval(formatted_result) - result_dict_keys = list(result_dict.keys()) - if len(result_dict_keys) > 1: - read_type = "multi" - else: - read_type = "single" - for wavelength in result_dict_keys: - one_wavelength_dict = result_dict.get(wavelength) - result_ndarray = plate_reader.convert_read_dictionary_to_array( - one_wavelength_dict - ) - for item in hellma_plate_standards: - wavelength_of_interest = item["wavelength"] - if str(wavelength) == str(wavelength_of_interest): - error_cells = plate_reader.check_byonoy_data_accuracy( - result_ndarray, item, False + if "result:" in result: + plate_name = result.split("result:")[0] + formatted_result = result.split("result: ")[1] + print(formatted_result) + result_dict = eval(formatted_result) + result_dict_keys = list(result_dict.keys()) + if len(result_dict_keys) > 1: + read_type = "multi" + else: + read_type = "single" + if "hellma_plate" in plate_name: + for wavelength in result_dict_keys: + one_wavelength_dict = result_dict.get(wavelength) + result_ndarray = plate_reader.convert_read_dictionary_to_array( + one_wavelength_dict ) - if len(error_cells[0]) > 0: - percent = (96 - len(error_cells)) / 96 * 100 - for cell in error_cells: - print( - "FAIL: Cell " + str(cell) + " out of accuracy spec." + for item in hellma_plate_standards: + wavelength_of_interest = item["wavelength"] + if str(wavelength) == str(wavelength_of_interest): + error_cells = plate_reader.check_byonoy_data_accuracy( + result_ndarray, item, orientation ) - else: - percent = 100 - print( - f"PASS: {wavelength_of_interest} meet accuracy specification" - ) - final_result[read_type, wavelength, read_num] = percent - read_num += 1 - read = "no" + if len(error_cells[0]) > 0: + percent = (96 - len(error_cells)) / 96 * 100 + for cell in error_cells: + print( + "FAIL: Cell " + + str(cell) + + " out of accuracy spec." + ) + else: + percent = 100 + print( + f"PASS: {wavelength_of_interest} meet accuracy spec." + ) + final_result[read_type, wavelength, read_num] = percent + read_num += 1 + else: + final_result = result_dict + read = "no" plate_dict = { "Plate Reader # of Reads": read_count, "Plate Reader Avg Read Time (sec)": avg_read_time, diff --git a/abr-testing/abr_testing/protocol_simulation/simulation_metrics.py b/abr-testing/abr_testing/protocol_simulation/simulation_metrics.py index 57695f03557..10c7ea12782 100644 --- a/abr-testing/abr_testing/protocol_simulation/simulation_metrics.py +++ b/abr-testing/abr_testing/protocol_simulation/simulation_metrics.py @@ -225,7 +225,11 @@ def parse_results_volume( else: print(f"Expected JSON object (dict) but got {type(json_data).__name__}.") commands = {} - + hellma_plate_orientation = False + parameters = json_data.get("runTimeParameters", "") + for parameter in parameters: + if parameter["displayName"] == "Hellma Plate Orientation": + hellma_plate_orientation = bool(parameter["value"]) start_time = datetime.fromisoformat(commands[0]["createdAt"]) end_time = datetime.fromisoformat(commands[len(commands) - 1]["completedAt"]) header = ["", "Protocol Name", "Date", "Time"] @@ -283,7 +287,7 @@ def parse_results_volume( temp_module_dict = read_robot_logs.temperature_module_commands(json_data) thermo_cycler_dict = read_robot_logs.thermocycler_commands(json_data) plate_reader_dict = read_robot_logs.plate_reader_commands( - json_data, hellma_plate_standards + json_data, hellma_plate_standards, hellma_plate_orientation ) instrument_dict = read_robot_logs.instrument_commands( json_data, labware_name=None @@ -499,12 +503,12 @@ def check_params(protocol_path: str) -> str: def get_extra_files(protocol_file_path: str) -> tuple[str, List[Path]]: """Get supporting files for protocol simulation if needed.""" params = check_params(protocol_file_path) - needs_files = input("Does your protocol utilize custom labware? (y/n): ") + needs_files = input("Does your protocol utilize custom labware? (Y/N): ") labware_files = [] - if needs_files == "y": + if needs_files == "Y": num_labware = input("How many custom labware?: ") for labware_num in range(int(num_labware)): - path = input("Enter custom labware definition: ") + path = input("Enter custom labware definition path: ") labware_files.append(Path(path)) return (params, labware_files) diff --git a/abr-testing/abr_testing/protocols/active_protocols/3_OT3 ABR Normalize with Tubes.py b/abr-testing/abr_testing/protocols/active_protocols/3_OT3 ABR Normalize with Tubes.py deleted file mode 100644 index 50fb82e94d5..00000000000 --- a/abr-testing/abr_testing/protocols/active_protocols/3_OT3 ABR Normalize with Tubes.py +++ /dev/null @@ -1,343 +0,0 @@ -"""FLEX Normalize with Tubes.""" -from opentrons.protocol_api import ProtocolContext, ParameterContext, Well -from abr_testing.protocols import helpers -from typing import List - -metadata = { - "protocolName": "Flex Normalize with Tubes", - "author": "Opentrons ", - "source": "Protocol Library", -} - -requirements = {"robotType": "Flex", "apiLevel": "2.21"} - -# SCRIPT SETTINGS -ABR_TEST = True -if ABR_TEST: - DRYRUN = True # True = skip incubation times, shorten mix, for testing purposes - TIP_TRASH = ( - False # True = Used tips go in Trash, False = Used tips go back into rack - ) -else: - DRYRUN = False # True = skip incubation times, shorten mix, for testing purposes - TIP_TRASH = True - - -def add_parameters(parameters: ParameterContext) -> None: - """Parameters.""" - helpers.create_csv_parameter(parameters) - helpers.create_dot_bottom_parameter(parameters) - helpers.create_two_pipette_mount_parameters(parameters) - - -def run(ctx: ProtocolContext) -> None: - """Protocol.""" - mount_pos_50ul = ctx.params.pipette_mount_1 # type: ignore[attr-defined] - mount_pos_1000ul = ctx.params.pipette_mount_2 # type: ignore[attr-defined] - dot_bottom = ctx.params.dot_bottom # type: ignore[attr-defined] - parsed_csv = ctx.params.parameters_csv.parse_as_csv() # type: ignore[attr-defined] - if DRYRUN: - ctx.comment("THIS IS A DRY RUN") - else: - ctx.comment("THIS IS A REACTION RUN") - - # labware - tiprack_50_1 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "1") - tiprack_200_1 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "4") - reagent_tube = ctx.load_labware( - "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", "5", "Reagent Tube" - ) - sample_plate = ctx.load_labware( - "armadillo_96_wellplate_200ul_pcr_full_skirt", "2", "Sample Plate" - ) - - # reagent - RSB = reagent_tube.wells()[0] - - # pipette - p1000 = ctx.load_instrument( - "flex_1channel_1000", mount_pos_1000ul, tip_racks=[tiprack_200_1] - ) - p50 = ctx.load_instrument( - "flex_1channel_50", mount_pos_50ul, tip_racks=[tiprack_50_1] - ) - - wells_with_liquids: List[Well] = [RSB] - helpers.load_wells_with_water(ctx, wells_with_liquids, [4000.0]) - helpers.find_liquid_height_of_all_wells(ctx, p50, wells_with_liquids) - MaxTubeVol = 200 - RSBVol = 0.0 - - data = parsed_csv - current = 1 - while current < len(data): - - CurrentWell = str(data[current][1]) - if float(data[current][2]) > 0: - InitialVol = float(data[current][2]) - else: - InitialVol = 0 - if float(data[current][3]) > 0: - InitialConc = float(data[current][3]) - else: - InitialConc = 0 - if float(data[current][4]) > 0: - TargetConc = float(data[current][4]) - else: - TargetConc = 0 - TotalDNA = float(InitialConc * InitialVol) - if TargetConc > 0: - TargetVol = float(TotalDNA / TargetConc) - else: - TargetVol = InitialVol - if TargetVol > InitialVol: - DilutionVol = float(TargetVol - InitialVol) - else: - DilutionVol = 0 - FinalVol = float(DilutionVol + InitialVol) - if TotalDNA > 0 and FinalVol > 0: - FinalConc = float(TotalDNA / FinalVol) - else: - FinalConc = 0 - - if DilutionVol <= 1: - ctx.comment("Sample " + CurrentWell + ": Conc. Too Low, Will Skip") - elif DilutionVol > MaxTubeVol - InitialVol: - DilutionVol = MaxTubeVol - InitialVol - ctx.comment( - "Sample " - + CurrentWell - + ": Conc. Too High, Will add, " - + str(DilutionVol) - + "ul, Max = " - + str(MaxTubeVol) - + "ul" - ) - RSBVol += MaxTubeVol - InitialVol - else: - if DilutionVol <= 20: - ctx.comment( - "Sample " - + CurrentWell - + ": Using p50, will add " - + str(round(DilutionVol, 1)) - ) - elif DilutionVol > 20: - ctx.comment( - "Sample " - + CurrentWell - + ": Using p1000, will add " - + str(round(DilutionVol, 1)) - ) - RSBVol += DilutionVol - current += 1 - - if RSBVol >= 14000: - ctx.pause("Caution, more than 15ml Required") - else: - ctx.comment("RSB Minimum: " + str(round(RSBVol / 1000, 1) + 1) + "ml") - - PiR2 = 176.71 - InitialRSBVol = RSBVol - RSBHeight = (InitialRSBVol / PiR2) + 17.5 - - ctx.pause("Proceed") - ctx.comment("==============================================") - ctx.comment("Normalizing Samples") - ctx.comment("==============================================") - - current = 1 - while current < len(data): - - CurrentWell = str(data[current][1]) - if float(data[current][2]) > 0: - InitialVol = float(data[current][2]) - else: - InitialVol = 0 - if float(data[current][3]) > 0: - InitialConc = float(data[current][3]) - else: - InitialConc = 0 - if float(data[current][4]) > 0: - TargetConc = float(data[current][4]) - else: - TargetConc = 0 - TotalDNA = float(InitialConc * InitialVol) - if TargetConc > 0: - TargetVol = float(TotalDNA / TargetConc) - else: - TargetVol = InitialVol - if TargetVol > InitialVol: - DilutionVol = float(TargetVol - InitialVol) - else: - DilutionVol = 0 - FinalVol = float(DilutionVol + InitialVol) - if TotalDNA > 0 and FinalVol > 0: - FinalConc = float(TotalDNA / FinalVol) - else: - FinalConc = 0 - - ctx.comment("Number " + str(data[current]) + ": Sample " + str(CurrentWell)) - # ctx.comment("Vol Height = "+str(round(RSBHeight,2))) - HeightDrop = DilutionVol / PiR2 - # ctx.comment("Vol Drop = "+str(round(HeightDrop,2))) - - if DilutionVol <= 0: - # If the No Volume - ctx.comment("Conc. Too Low, Skipping") - - elif DilutionVol >= MaxTubeVol - InitialVol: - # If the Required Dilution volume is >= Max Volume - DilutionVol = MaxTubeVol - InitialVol - ctx.comment( - "Conc. Too High, Will add, " - + str(DilutionVol) - + "ul, Max = " - + str(MaxTubeVol) - + "ul" - ) - p1000.pick_up_tip() - p1000.require_liquid_presence(RSB) - p1000.aspirate(DilutionVol, RSB.bottom(RSBHeight - (HeightDrop))) - RSBHeight -= HeightDrop - # ctx.comment("New Vol Height = "+str(round(RSBHeight,2))) - p1000.dispense(DilutionVol, sample_plate.wells_by_name()[CurrentWell]) - wells_with_liquids.append(sample_plate.wells_by_name()[CurrentWell]) - HighVolMix = 10 - for Mix in range(HighVolMix): - p1000.move_to(sample_plate.wells_by_name()[CurrentWell].center()) - p1000.aspirate(100) - p1000.move_to( - sample_plate.wells_by_name()[CurrentWell].bottom(0.5) - ) # original = () - p1000.aspirate(100) - p1000.dispense(100) - p1000.move_to(sample_plate.wells_by_name()[CurrentWell].center()) - p1000.dispense(100) - wells_with_liquids.append(sample_plate.wells_by_name()[CurrentWell]) - Mix += 1 - p1000.move_to(sample_plate.wells_by_name()[CurrentWell].top()) - ctx.delay(seconds=3) - p1000.blow_out() - p1000.drop_tip() if DRYRUN is False else p1000.return_tip() - - else: - if DilutionVol <= 20: - # If the Required Dilution volume is <= 20ul - ctx.comment("Using p50 to add " + str(round(DilutionVol, 1))) - p50.pick_up_tip() - if round(float(data[current][3]), 1) <= 20: - p50.require_liquid_presence(RSB) - p50.aspirate(DilutionVol, RSB.bottom(RSBHeight - (HeightDrop))) - RSBHeight -= HeightDrop - else: - p50.require_liquid_presence(RSB) - p50.aspirate(20, RSB.bottom(RSBHeight - (HeightDrop))) - RSBHeight -= HeightDrop - p50.dispense(DilutionVol, sample_plate.wells_by_name()[CurrentWell]) - wells_with_liquids.append(sample_plate.wells_by_name()[CurrentWell]) - p50.move_to( - sample_plate.wells_by_name()[CurrentWell].bottom(z=dot_bottom) - ) # original = () - # Mix volume <=20ul - if DilutionVol + InitialVol <= 20: - p50.mix(10, DilutionVol + InitialVol) - elif DilutionVol + InitialVol > 20: - p50.mix(10, 20) - p50.move_to(sample_plate.wells_by_name()[CurrentWell].top()) - ctx.delay(seconds=3) - p50.blow_out() - p50.drop_tip() if DRYRUN is False else p50.return_tip() - - elif DilutionVol > 20: - # If the required volume is >20 - ctx.comment("Using p1000 to add " + str(round(DilutionVol, 1))) - p1000.pick_up_tip() - p1000.require_liquid_presence(RSB) - p1000.aspirate(DilutionVol, RSB.bottom(RSBHeight - (HeightDrop))) - RSBHeight -= HeightDrop - if DilutionVol + InitialVol >= 120: - HighVolMix = 10 - for Mix in range(HighVolMix): - p1000.move_to( - sample_plate.wells_by_name()[CurrentWell].center() - ) - p1000.aspirate(100) - p1000.move_to( - sample_plate.wells_by_name()[CurrentWell].bottom( - z=dot_bottom - ) - ) # original = () - p1000.aspirate(DilutionVol + InitialVol - 100) - p1000.dispense(100) - p1000.move_to( - sample_plate.wells_by_name()[CurrentWell].center() - ) - p1000.dispense(DilutionVol + InitialVol - 100) - Mix += 1 - wells_with_liquids.append( - sample_plate.wells_by_name()[CurrentWell] - ) - else: - p1000.dispense( - DilutionVol, sample_plate.wells_by_name()[CurrentWell] - ) - p1000.move_to( - sample_plate.wells_by_name()[CurrentWell].bottom(z=dot_bottom) - ) # original = () - p1000.mix(10, DilutionVol + InitialVol) - p1000.move_to(sample_plate.wells_by_name()[CurrentWell].top()) - wells_with_liquids.append(sample_plate.wells_by_name()[CurrentWell]) - ctx.delay(seconds=3) - p1000.blow_out() - p1000.drop_tip() if DRYRUN is False else p1000.return_tip() - current += 1 - - ctx.comment("==============================================") - ctx.comment("Results") - ctx.comment("==============================================") - - current = 1 - while current < len(data): - - CurrentWell = str(data[current][1]) - if float(data[current][2]) > 0: - InitialVol = float(data[current][2]) - else: - InitialVol = 0 - if float(data[current][3]) > 0: - InitialConc = float(data[current][3]) - else: - InitialConc = 0 - if float(data[current][4]) > 0: - TargetConc = float(data[current][4]) - else: - TargetConc = 0 - TotalDNA = float(InitialConc * InitialVol) - if TargetConc > 0: - TargetVol = float(TotalDNA / TargetConc) - else: - TargetVol = InitialVol - if TargetVol > InitialVol: - DilutionVol = float(TargetVol - InitialVol) - else: - DilutionVol = 0 - if DilutionVol > MaxTubeVol - InitialVol: - DilutionVol = MaxTubeVol - InitialVol - FinalVol = float(DilutionVol + InitialVol) - if TotalDNA > 0 and FinalVol > 0: - FinalConc = float(TotalDNA / FinalVol) - else: - FinalConc = 0 - ctx.comment( - "Sample " - + CurrentWell - + ": " - + str(round(FinalVol, 1)) - + " at " - + str(round(FinalConc, 1)) - + "ng/ul" - ) - - current += 1 - helpers.find_liquid_height_of_all_wells(ctx, p50, wells_with_liquids) diff --git a/abr-testing/abr_testing/protocols/active_protocols/3_Tartrazine Protocol.py b/abr-testing/abr_testing/protocols/active_protocols/3_Tartrazine Protocol.py new file mode 100644 index 00000000000..05a6300e053 --- /dev/null +++ b/abr-testing/abr_testing/protocols/active_protocols/3_Tartrazine Protocol.py @@ -0,0 +1,124 @@ +"""Tartrazine Protocol.""" +from opentrons.protocol_api import ProtocolContext, ParameterContext, Well +from abr_testing.protocols import helpers +from opentrons.protocol_api.module_contexts import ( + AbsorbanceReaderContext, + HeaterShakerContext, +) +from datetime import datetime +from typing import Dict, List +import statistics + +metadata = { + "protocolName": "Tartrazine Protocol", + "author": "Opentrons ", + "source": "Protocol Library", +} + +requirements = {"robotType": "Flex", "apiLevel": "2.21"} + + +def add_parameters(parameters: ParameterContext) -> None: + """Parameters.""" + helpers.create_single_pipette_mount_parameter(parameters) + + +def run(ctx: ProtocolContext) -> None: + """Protocol.""" + mount_pos_50ul = ctx.params.pipette_mount # type: ignore[attr-defined] + # Plate Reader + plate_reader: AbsorbanceReaderContext = ctx.load_module( + helpers.abs_mod_str, "A3" + ) # type: ignore[assignment] + hs: HeaterShakerContext = ctx.load_module(helpers.hs_str, "A1") # type: ignore[assignment] + hs_adapter = hs.load_adapter("opentrons_96_pcr_adapter") + tube_rack = ctx.load_labware( + "opentrons_10_tuberack_nest_4x50ml_6x15ml_conical", "C2", "Reagent Tube" + ) + tartrazine_tube = tube_rack["A3"] + + sample_plate_1 = ctx.load_labware( + "nest_96_wellplate_200ul_flat", "D1", "Sample Plate 1" + ) + sample_plate_2 = ctx.load_labware( + "nest_96_wellplate_200ul_flat", "C1", "Sample Plate 2" + ) + sample_plate_3 = ctx.load_labware( + "nest_96_wellplate_200ul_flat", "B1", "Sample Plate 3" + ) + sample_plate_list = [sample_plate_1, sample_plate_2, sample_plate_3] + tiprack_50_1 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "D3") + tiprack_50_2 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "C3") + tiprack_50_3 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "B3") + tip_racks = [tiprack_50_1, tiprack_50_2, tiprack_50_3] + + # Pipette + p50 = ctx.load_instrument("flex_1channel_50", mount_pos_50ul, tip_racks=tip_racks) + + # Probe wells + liquid_vols_and_wells: Dict[str, List[Dict[str, Well | List[Well] | float]]] = { + "Tartrazine": [{"well": tartrazine_tube, "volume": 45.0}] + } + helpers.find_liquid_height_of_loaded_liquids(ctx, liquid_vols_and_wells, p50) + + i = 0 + all_percent_error_dict = {} + cv_dict = {} + for sample_plate in sample_plate_list: + deck_locations = ["D1", "C1", "B1"] + for well in sample_plate.wells(): + p50.pick_up_tip() + height = helpers.find_liquid_height(p50, tartrazine_tube) + p50.aspirate(10, tartrazine_tube.bottom(z=height)) + p50.air_gap(5) + p50.dispense(5, well.top()) + p50.dispense(10, well.bottom(z=0.5)) + p50.blow_out() + p50.return_tip() + helpers.move_labware_to_hs(ctx, sample_plate, hs, hs_adapter) + helpers.set_hs_speed(ctx, hs, 1500, 2.0, True) + hs.open_labware_latch() + plate_reader.close_lid() + plate_reader.initialize("single", [450]) + plate_reader.open_lid() + ctx.move_labware(sample_plate, plate_reader, use_gripper=True) + sample_plate_name = "sample plate_" + str(i + 1) + csv_string = sample_plate_name + "_" + str(datetime.now()) + plate_reader.close_lid() + result = plate_reader.read(csv_string) + for wavelength in result: + dict_of_wells = result[wavelength] + readings_and_wells = dict_of_wells.items() + readings = dict_of_wells.values() + avg = statistics.mean(readings) + # Check if every average is within +/- 5% of 2.85 + percent_error_dict = {} + percent_error_sum = 0.0 + for reading in readings_and_wells: + well_name = str(reading[0]) + measurement = reading[1] + percent_error = (measurement - 2.85) / 2.85 * 100 + percent_error_dict[well_name] = percent_error + percent_error_sum += percent_error + avg_percent_error = percent_error_sum / 96.0 + standard_deviation = statistics.stdev(readings) + try: + cv = standard_deviation / avg + except ZeroDivisionError: + cv = 0.0 + cv_percent = cv * 100 + cv_dict[sample_plate_name] = { + "CV": cv_percent, + "Mean": avg, + "SD": standard_deviation, + "Avg Percent Error": avg_percent_error, + } + all_percent_error_dict[sample_plate_name] = percent_error_dict + plate_reader.open_lid() + ctx.move_labware(sample_plate, deck_locations[i], use_gripper=True) + i += 1 + + # Print percent error dictionary + ctx.comment("Percent Error: " + str(all_percent_error_dict)) + # Print cv dictionary + ctx.comment("Plate Reader result: " + str(cv_dict)) diff --git a/abr-testing/abr_testing/protocols/active_protocols/8_Illumina and Plate Reader.py b/abr-testing/abr_testing/protocols/active_protocols/8_Illumina and Plate Reader.py index 2e835ac04dd..4894cae41d4 100644 --- a/abr-testing/abr_testing/protocols/active_protocols/8_Illumina and Plate Reader.py +++ b/abr-testing/abr_testing/protocols/active_protocols/8_Illumina and Plate Reader.py @@ -19,10 +19,7 @@ } -requirements = { - "robotType": "Flex", - "apiLevel": "2.21", -} +requirements = {"robotType": "Flex", "apiLevel": "2.21"} HELLMA_PLATE_SLOT = "D4" PLATE_READER_SLOT = "C3" @@ -58,14 +55,11 @@ def add_parameters(parameters: ParameterContext) -> None: """Add Parameters.""" helpers.create_hs_speed_parameter(parameters) helpers.create_dot_bottom_parameter(parameters) - parameters.add_str( + parameters.add_bool( variable_name="plate_orientation", display_name="Hellma Plate Orientation", - default="0_deg", - choices=[ - {"display_name": "0 degree Rotation", "value": "0_deg"}, - {"display_name": "180 degree Rotation", "value": "180_deg"}, - ], + default=True, + description="If hellma plate is rotated, set to True.", ) @@ -73,6 +67,7 @@ def plate_reader_actions( protocol: ProtocolContext, plate_reader: AbsorbanceReaderContext, hellma_plate: Labware, + hellma_plate_name: str, ) -> None: """Plate reader single and multi wavelength readings.""" wavelengths = [450, 650] @@ -84,7 +79,7 @@ def plate_reader_actions( protocol.move_labware(hellma_plate, plate_reader, use_gripper=True) plate_reader.close_lid() result = plate_reader.read(str(datetime.now())) - msg = f"result: {result}" + msg = f"{hellma_plate_name} result: {result}" protocol.comment(msg=msg) plate_reader.open_lid() protocol.move_labware(hellma_plate, HELLMA_PLATE_SLOT, use_gripper=True) @@ -95,7 +90,7 @@ def plate_reader_actions( protocol.move_labware(hellma_plate, plate_reader, use_gripper=True) plate_reader.close_lid() result = plate_reader.read(str(datetime.now())) - msg = f"result: {result}" + msg = f"{hellma_plate_name} result: {result}" protocol.comment(msg=msg) plate_reader.open_lid() protocol.move_labware(hellma_plate, HELLMA_PLATE_SLOT, use_gripper=True) @@ -107,6 +102,8 @@ def run(protocol: ProtocolContext) -> None: # LOAD PARAMETERS heater_shaker_speed = protocol.params.heater_shaker_speed # type: ignore[attr-defined] dot_bottom = protocol.params.dot_bottom # type: ignore[attr-defined] + plate_orientation = protocol.params.plate_orientation # type: ignore[attr-defined] + plate_name_str = "hellma_plate_" + str(plate_orientation) global p200_tips global p50_tips # WASTE BIN @@ -182,7 +179,7 @@ def run(protocol: ProtocolContext) -> None: PPC = reagent_plate.wells_by_name()["A6"] EPM = reagent_plate.wells_by_name()["A7"] # Load Liquids - plate_reader_actions(protocol, plate_reader, hellma_plate) + plate_reader_actions(protocol, plate_reader, hellma_plate, plate_name_str) # tip and sample tracking if COLUMNS == 1: @@ -948,4 +945,4 @@ def tipcheck() -> None: p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() p200_tips += 1 tipcheck() - plate_reader_actions(protocol, plate_reader, hellma_plate) + plate_reader_actions(protocol, plate_reader, hellma_plate, plate_name_str) diff --git a/api/src/opentrons/protocol_engine/commands/absorbance_reader/read.py b/api/src/opentrons/protocol_engine/commands/absorbance_reader/read.py index 8743fd1383b..1ca848858b6 100644 --- a/api/src/opentrons/protocol_engine/commands/absorbance_reader/read.py +++ b/api/src/opentrons/protocol_engine/commands/absorbance_reader/read.py @@ -80,6 +80,10 @@ async def execute( # noqa: C901 raise CannotPerformModuleAction( "Cannot perform Read action on Absorbance Reader without calling `.initialize(...)` first." ) + if abs_reader_substate.is_lid_on is False: + raise CannotPerformModuleAction( + "Cannot perform Read action on Absorbance Reader with the lid open. Try calling `.close_lid()` first." + ) # TODO: we need to return a file ID and increase the file count even when a moduel is not attached if (