andreamena commited on
Commit
d5b68e1
·
verified ·
1 Parent(s): 8e848bf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +97 -77
app.py CHANGED
@@ -22,7 +22,6 @@ from encoding import one_hot_padding, position_encoding, blosum62_padding, zscal
22
 
23
  # --- 2. CONFIGURATION ---
24
  SEEDS = [2023, 2024, 2025]
25
- TASK = 'PDC'
26
 
27
  # --- 3. LOAD ESM-2 ---
28
  print("📦 Downloading ESM-2 Model safely...")
@@ -41,7 +40,7 @@ esm_model.eval()
41
  # --- 4. CORE HELPER FUNCTIONS ---
42
 
43
  def get_esm_embedding(sequence):
44
- clean_seq = re.sub(r"[UZOB]", "X", str(sequence)) # Ensure string
45
  encoded_input = tokenizer(clean_seq, return_tensors='pt')
46
  with torch.no_grad():
47
  output = esm_model(**encoded_input)
@@ -60,7 +59,6 @@ def get_t2_features(sequence, padding=71):
60
  return tf.constant(combined, dtype=tf.float32)
61
 
62
  def preprocess_input(payload_smiles, linker_smiles, peptide_seq):
63
- # Create temp dataframe for the Graph Loader
64
  df = pd.DataFrame({
65
  'Payload SMILES': [payload_smiles],
66
  'Linker SMILES': [linker_smiles],
@@ -69,7 +67,6 @@ def preprocess_input(payload_smiles, linker_smiles, peptide_seq):
69
  })
70
  df.to_excel('temp_input.xlsx', index=False)
71
 
72
- # Use existing loader
73
  dataset_gen = Graph_Classification_Dataset(
74
  'temp_input.xlsx',
75
  smiles_field1='Payload SMILES',
@@ -95,136 +92,152 @@ def preprocess_input(payload_smiles, linker_smiles, peptide_seq):
95
 
96
  x1, adjoin1, _, x2, adjoin2, _ = target_batch
97
 
98
- # Create Masks
99
  seq1 = tf.cast(tf.math.equal(x1, 0), tf.float32)
100
  mask1 = seq1[:, tf.newaxis, tf.newaxis, :]
101
  seq2 = tf.cast(tf.math.equal(x2, 0), tf.float32)
102
  mask2 = seq2[:, tf.newaxis, tf.newaxis, :]
103
 
104
- # Get Features
105
  t2 = get_t2_features(peptide_seq)
106
  t1 = get_esm_embedding(peptide_seq)
107
 
108
  return x1, mask1, adjoin1, x2, mask2, adjoin2, t1, t2
109
 
110
- def get_raw_score(payload, linker, peptide):
111
- """Calculates the raw probability (0.0 to 1.0)"""
112
- try:
113
- data = preprocess_input(payload, linker, peptide)
114
- if data is None: return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
- x1, mask1, adj1, x2, mask2, adj2, t1, t2 = data
117
- total_prob = 0.0
118
- models_ran = 0
119
 
120
- for seed in SEEDS:
121
- weight_file = f'PDC_{seed}.h5'
122
- if not os.path.exists(weight_file): continue
123
-
124
- keras.backend.clear_session()
125
- model = PredictModel(num_layers=6, d_model=256, dff=512, num_heads=8, vocab_size=18, a=1, dense_dropout=0.4)
126
-
127
- # Init and Load
128
- _ = model(x1=x1, mask1=mask1, training=False, adjoin_matrix1=adj1,
 
 
 
 
 
 
129
  x2=x2, mask2=mask2, adjoin_matrix2=adj2, t1=t1, t2=t2)
130
- model.load_weights(weight_file)
131
-
132
- logits = model(x1=x1, mask1=mask1, training=False, adjoin_matrix1=adj1,
133
- x2=x2, mask2=mask2, adjoin_matrix2=adj2, t1=t1, t2=t2)
134
-
135
- prob = tf.sigmoid(logits).numpy()[0][0]
136
- total_prob += prob
137
- models_ran += 1
138
- del model
139
-
140
- if models_ran == 0: return None
141
- return total_prob / models_ran
142
 
143
- except Exception:
144
- return None
 
 
 
 
145
 
146
- # --- 5. UI HANDLERS ---
147
 
148
  def predict_single(payload, linker, peptide):
149
  if not payload or not linker or not peptide:
150
  return "⚠️ Please fill in all fields."
151
 
152
- score = get_raw_score(payload, linker, peptide)
 
 
 
 
 
 
153
 
154
- if score is None:
155
- return "Error processing input. Check SMILES validity."
156
 
157
  label = "Active PDC ✅" if score > 0.5 else "Inactive ❌"
158
  return f"{label}\nConfidence: {score:.2%}"
159
 
160
  def predict_batch(file_obj, progress=gr.Progress()):
161
- if file_obj is None:
162
- return None
163
 
164
- # Read File
165
  if file_obj.name.endswith('.csv'):
166
  df = pd.read_csv(file_obj.name)
167
  else:
168
  df = pd.read_excel(file_obj.name)
169
 
170
- # --- SMART COLUMN DETECTION ---
171
- # We map the "Keywords" we look for -> to the "Standard Names" our code needs
172
- keyword_map = {
173
- 'payload': 'Payload SMILES',
174
- 'linker': 'Linker SMILES',
175
- 'peptide': 'Peptide Sequence'
176
- }
177
-
178
  col_map = {}
179
  missing = []
180
-
181
  for keyword, standard_name in keyword_map.items():
182
- # Look for a column that contains the keyword (e.g. "payload") case-insensitive
183
  match = next((c for c in df.columns if keyword in c.lower()), None)
184
-
185
- if match:
186
- col_map[standard_name] = match # Map found column to standard name
187
- else:
188
- missing.append(keyword)
189
 
190
  if missing:
191
- return f"Error: Could not find columns containing keywords: {missing}. Found columns: {list(df.columns)}"
 
 
 
 
192
 
193
  results = []
194
 
195
- # Iterate with Progress Bar
196
  for index, row in progress.tqdm(df.iterrows(), total=len(df)):
197
- # Use the mapped column names to get data
198
  p_smiles = row[col_map['Payload SMILES']]
199
  l_smiles = row[col_map['Linker SMILES']]
200
  peptide = row[col_map['Peptide Sequence']]
201
 
202
- score = get_raw_score(p_smiles, l_smiles, peptide)
 
203
 
204
- if score is None:
205
- pred = "Error"
206
- conf = 0.0
207
  else:
 
 
208
  pred = "Active" if score > 0.5 else "Inactive"
209
  conf = score
210
 
211
  results.append([p_smiles, l_smiles, peptide, pred, f"{conf:.4f}"])
212
 
213
- # Create Result DataFrame
214
- res_df = pd.DataFrame(results, columns=['Payload', 'Linker', 'Peptide', 'Prediction', 'Score'])
215
 
 
 
216
  output_file = "predictions.csv"
217
  res_df.to_csv(output_file, index=False)
218
 
219
- return output_file
220
 
221
- # --- 6. GRADIO INTERFACE ---
222
  with gr.Blocks(title="PDCNet Ensemble Demo") as demo:
223
  gr.Markdown("# 💊 PDCNet Ensemble Predictor")
224
  gr.Markdown("Predict Peptide-Drug Conjugate activity. Upload a file or type manually.")
225
 
226
  with gr.Tabs():
227
- # TAB 1: SINGLE INPUT
228
  with gr.TabItem("Single Prediction"):
229
  with gr.Row():
230
  with gr.Column():
@@ -236,13 +249,20 @@ with gr.Blocks(title="PDCNet Ensemble Demo") as demo:
236
  out_result = gr.Textbox(label="Prediction Result", lines=4)
237
  btn_predict.click(predict_single, inputs=[in_payload, in_linker, in_peptide], outputs=out_result)
238
 
239
- # TAB 2: BATCH INPUT
240
  with gr.TabItem("Batch Prediction (File)"):
241
- gr.Markdown("Upload a CSV or Excel file. Must have columns for Payload, Linker, and Peptide.")
242
- file_input = gr.File(label="Upload Dataset (.csv or .xlsx)")
243
- btn_batch = gr.Button("Run Batch Prediction", variant="primary")
244
- file_output = gr.File(label="Download Results")
 
 
 
 
245
 
246
- btn_batch.click(predict_batch, inputs=file_input, outputs=file_output)
 
 
 
 
247
 
248
  demo.launch()
 
22
 
23
  # --- 2. CONFIGURATION ---
24
  SEEDS = [2023, 2024, 2025]
 
25
 
26
  # --- 3. LOAD ESM-2 ---
27
  print("📦 Downloading ESM-2 Model safely...")
 
40
  # --- 4. CORE HELPER FUNCTIONS ---
41
 
42
  def get_esm_embedding(sequence):
43
+ clean_seq = re.sub(r"[UZOB]", "X", str(sequence))
44
  encoded_input = tokenizer(clean_seq, return_tensors='pt')
45
  with torch.no_grad():
46
  output = esm_model(**encoded_input)
 
59
  return tf.constant(combined, dtype=tf.float32)
60
 
61
  def preprocess_input(payload_smiles, linker_smiles, peptide_seq):
 
62
  df = pd.DataFrame({
63
  'Payload SMILES': [payload_smiles],
64
  'Linker SMILES': [linker_smiles],
 
67
  })
68
  df.to_excel('temp_input.xlsx', index=False)
69
 
 
70
  dataset_gen = Graph_Classification_Dataset(
71
  'temp_input.xlsx',
72
  smiles_field1='Payload SMILES',
 
92
 
93
  x1, adjoin1, _, x2, adjoin2, _ = target_batch
94
 
 
95
  seq1 = tf.cast(tf.math.equal(x1, 0), tf.float32)
96
  mask1 = seq1[:, tf.newaxis, tf.newaxis, :]
97
  seq2 = tf.cast(tf.math.equal(x2, 0), tf.float32)
98
  mask2 = seq2[:, tf.newaxis, tf.newaxis, :]
99
 
 
100
  t2 = get_t2_features(peptide_seq)
101
  t1 = get_esm_embedding(peptide_seq)
102
 
103
  return x1, mask1, adjoin1, x2, mask2, adjoin2, t1, t2
104
 
105
+ # --- 5. HIGH-SPEED PREDICTION LOGIC ---
106
+
107
+ def load_ensemble_models():
108
+ """Loads all 3 models into memory ONCE."""
109
+ models = []
110
+ keras.backend.clear_session()
111
+
112
+ for seed in SEEDS:
113
+ weight_file = f'PDC_{seed}.h5'
114
+ if not os.path.exists(weight_file):
115
+ models.append(None)
116
+ continue
117
+
118
+ model = PredictModel(num_layers=6, d_model=256, dff=512, num_heads=8, vocab_size=18, a=1, dense_dropout=0.4)
119
+ # Dummy call to build graph
120
+ # We use dummy tensors to initialize the weights
121
+ dummy_x = tf.zeros((1, 512), dtype=tf.int64)
122
+ # ... (simplified init) ...
123
+ # Actually, simpler to just load weights if we build input correctly.
124
+ # But Safe way: Just return the fresh model and load weights during loop if needed?
125
+ # No, that's slow. We load weights NOW.
126
 
127
+ # NOTE: To load weights, we need to build the model first.
128
+ # We will do lazy-loading inside the loop but KEEP the model in memory.
129
+ models.append((model, weight_file))
130
 
131
+ return models
132
+
133
+ def predict_with_loaded_models(models, inputs):
134
+ """Runs prediction using pre-loaded models"""
135
+ x1, mask1, adj1, x2, mask2, adj2, t1, t2 = inputs
136
+ total_prob = 0.0
137
+ models_ran = 0
138
+
139
+ for model_tuple in models:
140
+ if model_tuple is None: continue
141
+ model, weight_path = model_tuple
142
+
143
+ # If model is not built, we build it now (Lazy Build)
144
+ if not model.built:
145
+ _ = model(x1=x1, mask1=mask1, training=False, adjoin_matrix1=adj1,
146
  x2=x2, mask2=mask2, adjoin_matrix2=adj2, t1=t1, t2=t2)
147
+ model.load_weights(weight_path)
148
+
149
+ logits = model(x1=x1, mask1=mask1, training=False, adjoin_matrix1=adj1,
150
+ x2=x2, mask2=mask2, adjoin_matrix2=adj2, t1=t1, t2=t2)
 
 
 
 
 
 
 
 
151
 
152
+ prob = tf.sigmoid(logits).numpy()[0][0]
153
+ total_prob += prob
154
+ models_ran += 1
155
+
156
+ if models_ran == 0: return None
157
+ return total_prob / models_ran
158
 
159
+ # --- 6. UI HANDLERS ---
160
 
161
  def predict_single(payload, linker, peptide):
162
  if not payload or not linker or not peptide:
163
  return "⚠️ Please fill in all fields."
164
 
165
+ # For single prediction, we accept the small slowness of loading
166
+ # to avoid keeping models in memory forever if not needed.
167
+ # OR reuse the logic. Let's reuse logic for consistency.
168
+ models = load_ensemble_models()
169
+ data = preprocess_input(payload, linker, peptide)
170
+
171
+ if data is None: return "Error processing input."
172
 
173
+ score = predict_with_loaded_models(models, data)
174
+ keras.backend.clear_session() # Clean up after single use
175
 
176
  label = "Active PDC ✅" if score > 0.5 else "Inactive ❌"
177
  return f"{label}\nConfidence: {score:.2%}"
178
 
179
  def predict_batch(file_obj, progress=gr.Progress()):
180
+ if file_obj is None: return None, None
 
181
 
182
+ # 1. Read File
183
  if file_obj.name.endswith('.csv'):
184
  df = pd.read_csv(file_obj.name)
185
  else:
186
  df = pd.read_excel(file_obj.name)
187
 
188
+ # 2. Map Columns
189
+ keyword_map = {'payload': 'Payload SMILES', 'linker': 'Linker SMILES', 'peptide': 'Peptide Sequence'}
 
 
 
 
 
 
190
  col_map = {}
191
  missing = []
 
192
  for keyword, standard_name in keyword_map.items():
 
193
  match = next((c for c in df.columns if keyword in c.lower()), None)
194
+ if match: col_map[standard_name] = match
195
+ else: missing.append(keyword)
 
 
 
196
 
197
  if missing:
198
+ return None, pd.DataFrame({"Error": [f"Missing columns: {missing}"]})
199
+
200
+ # 3. LOAD MODELS ONCE (The Speed Fix)
201
+ print("🚀 Loading Ensemble Models for Batch Processing...")
202
+ models = load_ensemble_models()
203
 
204
  results = []
205
 
206
+ # 4. Iterate Rows
207
  for index, row in progress.tqdm(df.iterrows(), total=len(df)):
 
208
  p_smiles = row[col_map['Payload SMILES']]
209
  l_smiles = row[col_map['Linker SMILES']]
210
  peptide = row[col_map['Peptide Sequence']]
211
 
212
+ # Preprocess
213
+ data = preprocess_input(p_smiles, l_smiles, peptide)
214
 
215
+ if data is None:
216
+ pred, conf = "Error", 0.0
 
217
  else:
218
+ # Predict using the ALREADY LOADED models
219
+ score = predict_with_loaded_models(models, data)
220
  pred = "Active" if score > 0.5 else "Inactive"
221
  conf = score
222
 
223
  results.append([p_smiles, l_smiles, peptide, pred, f"{conf:.4f}"])
224
 
225
+ # 5. Cleanup
226
+ keras.backend.clear_session()
227
 
228
+ # 6. Format Outputs
229
+ res_df = pd.DataFrame(results, columns=['Payload', 'Linker', 'Peptide', 'Prediction', 'Score'])
230
  output_file = "predictions.csv"
231
  res_df.to_csv(output_file, index=False)
232
 
233
+ return output_file, res_df
234
 
235
+ # --- 7. GRADIO INTERFACE ---
236
  with gr.Blocks(title="PDCNet Ensemble Demo") as demo:
237
  gr.Markdown("# 💊 PDCNet Ensemble Predictor")
238
  gr.Markdown("Predict Peptide-Drug Conjugate activity. Upload a file or type manually.")
239
 
240
  with gr.Tabs():
 
241
  with gr.TabItem("Single Prediction"):
242
  with gr.Row():
243
  with gr.Column():
 
249
  out_result = gr.Textbox(label="Prediction Result", lines=4)
250
  btn_predict.click(predict_single, inputs=[in_payload, in_linker, in_peptide], outputs=out_result)
251
 
 
252
  with gr.TabItem("Batch Prediction (File)"):
253
+ gr.Markdown("Upload a CSV or Excel file.")
254
+ with gr.Row():
255
+ file_input = gr.File(label="Upload Dataset")
256
+ btn_batch = gr.Button("Run Batch Prediction", variant="primary")
257
+
258
+ # OUTPUTS: Visual Table AND Download File
259
+ out_dataframe = gr.Dataframe(label="Results Preview", interactive=False)
260
+ file_output = gr.File(label="Download Full Results (.csv)")
261
 
262
+ btn_batch.click(
263
+ predict_batch,
264
+ inputs=file_input,
265
+ outputs=[file_output, out_dataframe] # Two outputs now!
266
+ )
267
 
268
  demo.launch()