wenjun99 commited on
Commit
c4a6de4
·
verified ·
1 Parent(s): 1d13d0e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +177 -0
app.py CHANGED
@@ -717,3 +717,180 @@ with tab5:
717
  st.dataframe(source_volume_df)
718
  st.download_button("Download Source Volumes", source_volume_df.to_csv(index=False), "source_total_volumes.csv", key="download_volume_any")
719
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
717
  st.dataframe(source_volume_df)
718
  st.download_button("Download Source Volumes", source_volume_df.to_csv(index=False), "source_total_volumes.csv", key="download_volume_any")
719
 
720
+
721
+
722
+ import streamlit as st
723
+ import pandas as pd
724
+
725
+ # === App Title ===
726
+ with tab5:
727
+ st.header("Robot Script Generator")
728
+
729
+ # === Voyager ASCII 6-bit conversion table ===
730
+ voyager_table = {
731
+ i: ch for i, ch in enumerate([
732
+ ' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
733
+ 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
734
+ 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2',
735
+ '3', '4', '5', '6', '7', '8', '9', '.', ',', '(',
736
+ ')', '+', '-', '*', '/', '=', '$', '!', ':', '%',
737
+ '"', '#', '@', "'", '?', '&'
738
+ ])
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):
746
+ chunk = bits[i:i+6]
747
+ if len(chunk) < 6:
748
+ chunk += [0] * (6 - len(chunk))
749
+ val = sum(b << (5 - j) for j, b in enumerate(chunk))
750
+ chars.append(voyager_table.get(val, '?'))
751
+ return ''.join(chars)
752
+
753
+ # === Well mapping ===
754
+ def get_well_position(sample_index):
755
+ row_letter = chr(65 + (sample_index - 1) // 12)
756
+ col_number = ((sample_index - 1) % 12) + 1
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 = {}
762
+ adjusted_sources = []
763
+ for entry in robot_script:
764
+ src = entry['Source']
765
+ vol = entry['Volume']
766
+ source_volumes[src] = source_volumes.get(src, 0) + vol
767
+ if source_volumes[src] > volume_limit:
768
+ row_letter = src[0]
769
+ col_number = src[1:]
770
+ new_row_letter = chr(ord(row_letter) + 4)
771
+ new_src = f"{new_row_letter}{col_number}"
772
+ entry['Source'] = new_src
773
+ source_volumes[new_src] = source_volumes.get(new_src, 0) + vol
774
+ source_volumes[src] -= vol
775
+ adjusted_sources.append(entry)
776
+ return adjusted_sources, source_volumes
777
+
778
+ # === Fixed D-source transfers ===
779
+ def generate_fixed_d_source_instructions_to_all_samples(n_samples, fixed_volume=16, volume_limit=170):
780
+ d_source_volumes = {}
781
+ d_source_script = []
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):
810
+ row, col = rows[i // 12], (i % 12) + 1
811
+ wells.append(f"{row}{col}")
812
+ return wells
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
+
820
+ st.subheader("Optional Metadata")
821
+ barcode_id_input = st.text_input("Barcode ID (optional)", value="")
822
+ labware_source_input = st.text_input("Labware Source ID", value="1")
823
+ labware_dest_input = st.text_input("Labware Destination ID", value="1")
824
+ name_input = st.text_input("Name field (optional)", value="")
825
+ volume_limit_input = st.number_input("Maximum Volume per Source Well (µL)", value=150, min_value=10, step=10)
826
+
827
+ # === Load Data ===
828
+ if binary_file:
829
+ df_binary = pd.read_csv(binary_file, header=None)
830
+ df_binary.columns = [str(i+1) for i in range(df_binary.shape[1])]
831
+ else:
832
+ st.info("No file uploaded — manually enter binary data below.")
833
+ df_binary = st.data_editor(
834
+ pd.DataFrame(columns=[str(i) for i in range(1, 33)]),
835
+ num_rows="dynamic", key="manual_input"
836
+ )
837
+
838
+ if not df_binary.empty:
839
+ st.subheader("Binary Matrix")
840
+ st.dataframe(df_binary.style.applymap(lambda v: "background-color: lightgreen" if v == 1 else "background-color: lightcoral"))
841
+ st.download_button("⬇️ Download Binary CSV", df_binary.to_csv(index=False), "binary_matrix.csv")
842
+
843
+ # Decode to string
844
+ decoded = binary_labels_to_string(df_binary.values.flatten().astype(int).tolist())
845
+ st.subheader("Decoded String Output")
846
+ st.code(decoded)
847
+ st.download_button("⬇️ Download Decoded String", decoded, "decoded_string.txt")
848
+
849
+ # === Generate Robot Script ===
850
+ st.divider()
851
+ st.subheader("Generated Robot Script")
852
+
853
+ df_robot = df_binary.copy()
854
+ df_robot.insert(0, 'Sample', range(1, len(df_robot) + 1))
855
+ df_robot['# donors'] = df_robot.iloc[:, 1:].astype(int).sum(axis=1)
856
+ df_robot['volume donors (µL)'] = 64 / df_robot['# donors']
857
+
858
+ robot_script = []
859
+ source_wells = generate_source_wells(df_robot.shape[1] - 1)
860
+
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(int(sample['Sample']))
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({'Source': source, 'Destination': dest, 'Volume': vol, 'Tool': tool})
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)
880
+ robot_script_df.insert(0, 'Barcode ID', barcode_id_input)
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', 'Source',
885
+ 'Labware_Destination', 'Destination', 'Volume', 'Tool', 'Name']]
886
+
887
+ st.dataframe(robot_script_df)
888
+ st.download_button("⬇️ Download Robot Script", robot_script_df.to_csv(index=False), "robot_script.csv")
889
+
890
+ # === Source Volume Summary ===
891
+ st.divider()
892
+ st.subheader("Total Volume Used Per Source")
893
+ combined_volumes = {**source_volumes, **d_volumes}
894
+ volume_df = pd.DataFrame(list(combined_volumes.items()), columns=['Source', 'Total Volume (µL)'])
895
+ st.dataframe(volume_df)
896
+ st.download_button("⬇️ Download Volume Summary", volume_df.to_csv(index=False), "source_volumes.csv")