diff --git a/docs/examples/data/gen5_data_export_test_12-1-2025.xlsx b/docs/examples/data/gen5_data_export_test_12-1-2025.xlsx new file mode 100644 index 0000000..eca9c17 Binary files /dev/null and b/docs/examples/data/gen5_data_export_test_12-1-2025.xlsx differ diff --git a/mtphandler/readers/biotek.py b/mtphandler/readers/biotek.py index 952b305..7b67777 100644 --- a/mtphandler/readers/biotek.py +++ b/mtphandler/readers/biotek.py @@ -61,11 +61,22 @@ def read_biotek( wavelengths = extract_integers(wavelengths_cell) - measurement_int_cell = str(df.iloc[15, 1]) - if "add final component" in measurement_int_cell.lower(): - measurement_int_cell = str(df.iloc[16, 1]) - - measurement_interval = parse_measurement_interval(measurement_int_cell) + measurement_interval = None + for index, row_value in df.iloc[:, 0].items(): + if isinstance(row_value, str) and "start kinetic" in row_value.lower(): # this is the start of a kinetic read block + interval_candidate = str(df.iloc[index, 1]) + try: + measurement_interval = parse_measurement_interval(interval_candidate) + break # Found a valid interval, stop searching. Note that there could be more kinetic intervals later. + except ValueError: + continue # Keep searching if this one fails + + if measurement_interval is None: + # Fallback to original logic if 'Start Kinetic' not found or parsed + measurement_int_cell = str(df.iloc[15, 1]) + if "add final component" in measurement_int_cell.lower(): + measurement_int_cell = str(df.iloc[16, 1]) + measurement_interval = parse_measurement_interval(measurement_int_cell) plate = Plate( date_measured=timestamp, diff --git a/tests/unit_tests/test_readers/test_biotek_epoch_2.py b/tests/unit_tests/test_readers/test_biotek_epoch_2.py index 674f835..db01422 100644 --- a/tests/unit_tests/test_readers/test_biotek_epoch_2.py +++ b/tests/unit_tests/test_readers/test_biotek_epoch_2.py @@ -34,3 +34,43 @@ def test_read_biotek_epoch_2(): if well.id == "D9": assert well.measurements[0].absorption[-1] == pytest.approx(0.226, rel=1e-2) + + # Test another BioTek plate example + """Tests that read_biotek can correctly parse the gen5 data file with dynamic kinetic interval detection.""" + test_file_path = "docs/examples/data/gen5_data_export_test_12-1-2025.xlsx" + + # Ensure the test file exists + #assert os.path.exists(test_file_path), f"Test file not found: {test_file_path}" + + # Use the read_biotek function with the test file + # This should now run without raising a ValueError due to the fix + plate = read_biotek(path=test_file_path, ph=7.4) + + # Assert that a Plate object is returned + assert isinstance(plate, Plate) + + # Assert that the plate contains wells and measurements + assert len(plate.wells) > 0 + assert len(plate.wells[0].measurements) > 0 + + # Get the first measurement from the first well + first_measurement = plate.wells[0].measurements[0] + + # Assert that the time data is populated and has more than one point + assert len(first_measurement.time) > 1 + + # The expected interval from the file is 'Interval 0:08:00', which is 8 minutes. + # As currently implemented, `np.arange` uses this minute value directly + # to generate the time series, even though `time_unit='s'` is specified. + expected_interval_minutes = 8.0 + actual_interval = first_measurement.time[1] - first_measurement.time[0] + + # Assert that the actual interval matches the expected 8.0 minutes + assert actual_interval == pytest.approx(expected_interval_minutes) + + # Note: There's a potential inconsistency in the read_biotek function + # where time points are generated using 'minutes' but `time_unit` is set to 's' (seconds). + # This test validates the numerical interval extracted, but a future enhancement + # might involve ensuring strict unit consistency (e.g., converting minutes to seconds + # before generating the time array if time_unit is 's'). +