Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -739,7 +739,7 @@ voyager_table = {
|
|
| 739 |
}
|
| 740 |
reverse_voyager_table = {v: k for k, v in voyager_table.items()}
|
| 741 |
|
| 742 |
-
# === Binary
|
| 743 |
def binary_labels_to_string(bits: list[int]) -> str:
|
| 744 |
chars = []
|
| 745 |
for i in range(0, len(bits), 6):
|
|
@@ -752,10 +752,16 @@ def binary_labels_to_string(bits: list[int]) -> str:
|
|
| 752 |
|
| 753 |
# === Well mapping ===
|
| 754 |
def get_well_position(sample_index):
|
| 755 |
-
|
| 756 |
-
|
|
|
|
| 757 |
return f"{row_letter}{col_number}"
|
| 758 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 759 |
# === Track and replace source if volume exceeded ===
|
| 760 |
def track_and_replace_source(source_list, robot_script, volume_limit=150):
|
| 761 |
source_volumes = {}
|
|
@@ -782,28 +788,24 @@ def generate_fixed_d_source_instructions_to_all_samples(n_samples, fixed_volume=
|
|
| 782 |
current_d_index = 1
|
| 783 |
for i in range(n_samples):
|
| 784 |
dest = get_well_position(i + 1)
|
|
|
|
| 785 |
current_d_well = f"D{current_d_index}"
|
| 786 |
d_source_volumes.setdefault(current_d_well, 0)
|
| 787 |
-
|
| 788 |
if d_source_volumes[current_d_well] + fixed_volume > volume_limit:
|
| 789 |
current_d_index += 1
|
| 790 |
current_d_well = f"D{current_d_index}"
|
| 791 |
d_source_volumes[current_d_well] = 0
|
| 792 |
-
|
| 793 |
d_source_volumes[current_d_well] += fixed_volume
|
| 794 |
-
|
| 795 |
-
# ✅ Updated: use TS_50 if volume >10 µL, else TS_10
|
| 796 |
tool = 'TS_50' if fixed_volume > 10 else 'TS_10'
|
| 797 |
d_source_script.append({
|
|
|
|
| 798 |
'Source': current_d_well,
|
| 799 |
'Destination': dest,
|
| 800 |
'Volume': fixed_volume,
|
| 801 |
'Tool': tool
|
| 802 |
})
|
| 803 |
-
|
| 804 |
return d_source_script, d_source_volumes
|
| 805 |
|
| 806 |
-
# === Source well generation ===
|
| 807 |
def generate_source_wells(n):
|
| 808 |
wells, rows = [], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
| 809 |
for i in range(n):
|
|
@@ -813,7 +815,6 @@ def generate_source_wells(n):
|
|
| 813 |
|
| 814 |
# === Main UI ===
|
| 815 |
st.header("Upload Binary Data (0/1)")
|
| 816 |
-
|
| 817 |
binary_file = st.file_uploader("Upload Binary CSV", type=["csv"])
|
| 818 |
st.divider()
|
| 819 |
|
|
@@ -831,7 +832,7 @@ if binary_file:
|
|
| 831 |
else:
|
| 832 |
st.info("No file uploaded — manually enter binary data below.")
|
| 833 |
df_binary = st.data_editor(
|
| 834 |
-
pd.DataFrame([str(i) for i in range(1,
|
| 835 |
num_rows="dynamic", key="manual_input"
|
| 836 |
)
|
| 837 |
|
|
@@ -861,19 +862,24 @@ if not df_binary.empty:
|
|
| 861 |
for i, col in enumerate(df_robot.columns[1:]):
|
| 862 |
for _, sample in df_robot.iterrows():
|
| 863 |
if int(sample[col]) == 1:
|
|
|
|
|
|
|
| 864 |
source = source_wells[i]
|
| 865 |
-
dest = get_well_position(
|
| 866 |
vol = round(sample['volume donors (µL)'], 2)
|
| 867 |
-
|
| 868 |
-
# ✅ Updated: use TS_50 for volumes >10 µL, TS_10 otherwise
|
| 869 |
tool = 'TS_50' if vol > 10 else 'TS_10'
|
| 870 |
-
robot_script.append({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 871 |
|
| 872 |
robot_script, source_volumes = track_and_replace_source(source_wells, robot_script, volume_limit=volume_limit_input)
|
| 873 |
d_script, d_volumes = generate_fixed_d_source_instructions_to_all_samples(
|
| 874 |
len(df_robot), fixed_volume=16, volume_limit=volume_limit_input
|
| 875 |
)
|
| 876 |
-
|
| 877 |
full_script = robot_script + d_script
|
| 878 |
|
| 879 |
robot_script_df = pd.DataFrame(full_script)
|
|
@@ -881,8 +887,9 @@ if not df_binary.empty:
|
|
| 881 |
robot_script_df.insert(1, 'Labware_Source', labware_source_input)
|
| 882 |
robot_script_df.insert(3, 'Labware_Destination', labware_dest_input)
|
| 883 |
robot_script_df['Name'] = name_input
|
| 884 |
-
robot_script_df = robot_script_df[['Barcode ID', 'Labware_Source', '
|
| 885 |
-
'
|
|
|
|
| 886 |
|
| 887 |
st.dataframe(robot_script_df)
|
| 888 |
st.download_button("⬇️ Download Robot Script", robot_script_df.to_csv(index=False), "robot_script.csv")
|
|
|
|
| 739 |
}
|
| 740 |
reverse_voyager_table = {v: k for k, v in voyager_table.items()}
|
| 741 |
|
| 742 |
+
# === Binary → String conversion ===
|
| 743 |
def binary_labels_to_string(bits: list[int]) -> str:
|
| 744 |
chars = []
|
| 745 |
for i in range(0, len(bits), 6):
|
|
|
|
| 752 |
|
| 753 |
# === Well mapping ===
|
| 754 |
def get_well_position(sample_index):
|
| 755 |
+
"""Convert sample index (1-based) into A1–H12 pattern within its plate"""
|
| 756 |
+
row_letter = chr(65 + ((sample_index - 1) % 96) // 12) # 8 rows (A–H)
|
| 757 |
+
col_number = ((sample_index - 1) % 12) + 1 # 12 columns
|
| 758 |
return f"{row_letter}{col_number}"
|
| 759 |
|
| 760 |
+
def get_plate_id(sample_index):
|
| 761 |
+
"""Return Plate number based on 96 samples per plate"""
|
| 762 |
+
plate_number = math.ceil(sample_index / 96)
|
| 763 |
+
return f"Plate {plate_number}"
|
| 764 |
+
|
| 765 |
# === Track and replace source if volume exceeded ===
|
| 766 |
def track_and_replace_source(source_list, robot_script, volume_limit=150):
|
| 767 |
source_volumes = {}
|
|
|
|
| 788 |
current_d_index = 1
|
| 789 |
for i in range(n_samples):
|
| 790 |
dest = get_well_position(i + 1)
|
| 791 |
+
plate = get_plate_id(i + 1)
|
| 792 |
current_d_well = f"D{current_d_index}"
|
| 793 |
d_source_volumes.setdefault(current_d_well, 0)
|
|
|
|
| 794 |
if d_source_volumes[current_d_well] + fixed_volume > volume_limit:
|
| 795 |
current_d_index += 1
|
| 796 |
current_d_well = f"D{current_d_index}"
|
| 797 |
d_source_volumes[current_d_well] = 0
|
|
|
|
| 798 |
d_source_volumes[current_d_well] += fixed_volume
|
|
|
|
|
|
|
| 799 |
tool = 'TS_50' if fixed_volume > 10 else 'TS_10'
|
| 800 |
d_source_script.append({
|
| 801 |
+
'Plate': plate,
|
| 802 |
'Source': current_d_well,
|
| 803 |
'Destination': dest,
|
| 804 |
'Volume': fixed_volume,
|
| 805 |
'Tool': tool
|
| 806 |
})
|
|
|
|
| 807 |
return d_source_script, d_source_volumes
|
| 808 |
|
|
|
|
| 809 |
def generate_source_wells(n):
|
| 810 |
wells, rows = [], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
| 811 |
for i in range(n):
|
|
|
|
| 815 |
|
| 816 |
# === Main UI ===
|
| 817 |
st.header("Upload Binary Data (0/1)")
|
|
|
|
| 818 |
binary_file = st.file_uploader("Upload Binary CSV", type=["csv"])
|
| 819 |
st.divider()
|
| 820 |
|
|
|
|
| 832 |
else:
|
| 833 |
st.info("No file uploaded — manually enter binary data below.")
|
| 834 |
df_binary = st.data_editor(
|
| 835 |
+
pd.DataFrame(columns=[str(i) for i in range(1, 33)]),
|
| 836 |
num_rows="dynamic", key="manual_input"
|
| 837 |
)
|
| 838 |
|
|
|
|
| 862 |
for i, col in enumerate(df_robot.columns[1:]):
|
| 863 |
for _, sample in df_robot.iterrows():
|
| 864 |
if int(sample[col]) == 1:
|
| 865 |
+
sample_id = int(sample['Sample'])
|
| 866 |
+
plate = get_plate_id(sample_id)
|
| 867 |
source = source_wells[i]
|
| 868 |
+
dest = get_well_position(sample_id)
|
| 869 |
vol = round(sample['volume donors (µL)'], 2)
|
|
|
|
|
|
|
| 870 |
tool = 'TS_50' if vol > 10 else 'TS_10'
|
| 871 |
+
robot_script.append({
|
| 872 |
+
'Plate': plate, # ✅ New Column
|
| 873 |
+
'Source': source,
|
| 874 |
+
'Destination': dest,
|
| 875 |
+
'Volume': vol,
|
| 876 |
+
'Tool': tool
|
| 877 |
+
})
|
| 878 |
|
| 879 |
robot_script, source_volumes = track_and_replace_source(source_wells, robot_script, volume_limit=volume_limit_input)
|
| 880 |
d_script, d_volumes = generate_fixed_d_source_instructions_to_all_samples(
|
| 881 |
len(df_robot), fixed_volume=16, volume_limit=volume_limit_input
|
| 882 |
)
|
|
|
|
| 883 |
full_script = robot_script + d_script
|
| 884 |
|
| 885 |
robot_script_df = pd.DataFrame(full_script)
|
|
|
|
| 887 |
robot_script_df.insert(1, 'Labware_Source', labware_source_input)
|
| 888 |
robot_script_df.insert(3, 'Labware_Destination', labware_dest_input)
|
| 889 |
robot_script_df['Name'] = name_input
|
| 890 |
+
robot_script_df = robot_script_df[['Barcode ID', 'Labware_Source', 'Plate',
|
| 891 |
+
'Source', 'Labware_Destination', 'Destination',
|
| 892 |
+
'Volume', 'Tool', 'Name']]
|
| 893 |
|
| 894 |
st.dataframe(robot_script_df)
|
| 895 |
st.download_button("⬇️ Download Robot Script", robot_script_df.to_csv(index=False), "robot_script.csv")
|