Hsqcsu commited on
Commit
88f4bb6
·
verified ·
1 Parent(s): f9fff4f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -35
app.py CHANGED
@@ -25,11 +25,16 @@ TOKENIZER_PATH = r"tokenizer-smiles-roberta-1e_new"
25
  EXAMPLE_DIR = "example_files"
26
  RETRIEVAL_WEIGHTS_DIR = r"model_weights"
27
  PROCESSED_DATA_DIR = r"processed_data"
 
28
 
 
29
  ADMIN_KEY = os.environ.get("ADMIN_KEY", "123456")
30
- HF_TOKEN = os.environ.get("HF_TOKEN")
31
  PRIVATE_DATASET_REPO = "Hsqcsu/Derivative_PS_data"
32
 
 
 
 
33
  # ==================== Platt Calibration 拟合参数 ====================
34
  PLATT_CALIBRATION_PARAMS = {
35
  "general": {
@@ -84,18 +89,20 @@ def download_private_file(filename):
84
  repo_id=PRIVATE_DATASET_REPO,
85
  filename=filename,
86
  repo_type="dataset",
87
- token=HF_TOKEN,
88
  local_dir=PROCESSED_DATA_DIR
89
  )
90
  return path
91
  except Exception as e:
92
- print(f"Download failed {filename}: {e}")
93
- return local_path
94
  return local_path
95
 
96
 
97
  def load_gz_features(filename, device):
98
  path = download_private_file(filename)
 
 
99
  with gzip.open(path, 'rb') as f:
100
  features = torch.load(f, weights_only=False)
101
  return features.to(device)
@@ -103,6 +110,8 @@ def load_gz_features(filename, device):
103
 
104
  def load_smiles_list(filename):
105
  path = download_private_file(filename)
 
 
106
  with open(path, 'r', encoding='utf-8') as f:
107
  return [line.strip() for line in f if line.strip()]
108
 
@@ -117,10 +126,10 @@ def load_model_instance(model_instance, weight_filename, device):
117
  return model_instance
118
 
119
 
120
- # ==================== 1. 初始化双分支检索模型 ====================
121
- print("Initializing Models and Feature Libraries...")
122
 
123
- # 通用分支 (NIST / Custom 库)
124
  ir_model_general = load_model_instance(IRModel(), "overall_best_top1_ir.pth", DEVICE)
125
  smiles_model_general = load_model_instance(
126
  SmilesModel(roberta_model_path=None, roberta_tokenizer_path=TOKENIZER_PATH, smiles_maxlen=300, feature_dim=768),
@@ -128,7 +137,7 @@ smiles_model_general = load_model_instance(
128
  DEVICE
129
  )
130
 
131
- # 专属 PS 分支 (PS Existing / PS Derivative 库)
132
  ir_model_ps = load_model_instance(IRModel(), "overall_best_nps_recall10_ir.pth", DEVICE)
133
  smiles_model_ps = load_model_instance(
134
  SmilesModel(roberta_model_path=None, roberta_tokenizer_path=TOKENIZER_PATH, smiles_maxlen=300, feature_dim=768),
@@ -136,24 +145,46 @@ smiles_model_ps = load_model_instance(
136
  DEVICE
137
  )
138
 
139
- # 载入分子特征库
140
  library_nist_features = load_gz_features("library_NIST_smiles_features_fp16.gz", DEVICE)
141
  general_smiles_nist = load_smiles_list("NIST_smiles_can.txt")
142
 
143
- library_derivative_ps_features = load_gz_features("library_Derivative_PS_smiles_features_fp16.gz", DEVICE)
144
- ps_smiles_derivative = load_smiles_list("smiles_Derivative_PS.txt")
145
-
146
  library_existed_ps_features = load_gz_features("library_Existing_PS_smiles_features_fp16.gz", DEVICE)
147
  ps_smiles_existed = load_smiles_list("smiles_Existing_PS.txt")
148
 
149
- ALL_LIBS = {
150
  "NIST": (library_nist_features, general_smiles_nist),
151
- "PS Existing Library": (library_existed_ps_features, ps_smiles_existed),
152
- "PS Derivative Library": (library_derivative_ps_features, ps_smiles_derivative)
153
  }
154
 
155
 
156
- # ==================== 2. 置信度计算与分子编码 ====================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  def calculate_calibrated_confidence(cosine_sim: float, smiles_model_instance, model_type: str = "general") -> float:
158
  with torch.no_grad():
159
  t = torch.exp(smiles_model_instance.t_prime).cpu().item()
@@ -190,14 +221,10 @@ def process_custom_smiles(smiles_str, smiles_model_instance):
190
  return feats, valid_smiles, [original_map[s] for s in valid_smiles]
191
 
192
 
193
- # ==================== 3. 检索主业务流程 ====================
194
  def run_unified_retrieval(ir_file, spectrum_type, library_name, custom_smiles_str, access_key, progress=gr.Progress()):
195
- if library_name == "PS Derivative Library":
196
- if access_key != ADMIN_KEY:
197
- raise gr.Error("⛔ Access Denied: Incorrect Access Key for PS Derivative Library.")
198
-
199
  if ir_file is None:
200
- raise gr.Error("Please upload an IR spectrum file.")
201
 
202
  # 动态切换专属权重
203
  is_ps_lib = library_name in ["PS Existing Library", "PS Derivative Library"]
@@ -214,7 +241,7 @@ def run_unified_retrieval(ir_file, spectrum_type, library_name, custom_smiles_st
214
  w_raw, i_raw = df.iloc[:, 0].values, df.iloc[:, 1].values
215
  ir_data = preprocess_csv_spectra_higer_500(w_raw, i_raw)
216
  except Exception as e:
217
- raise gr.Error(f"Error processing CSV: {str(e)}")
218
 
219
  elif file_path.lower().endswith('.jdx'):
220
  try:
@@ -222,24 +249,28 @@ def run_unified_retrieval(ir_file, spectrum_type, library_name, custom_smiles_st
222
  w_raw, i_raw = np.array(data['x']), np.array(data['y'])
223
  ir_data = preprocess_jdx_spectra_higer_500(w_raw, i_raw)
224
  except Exception as e:
225
- raise gr.Error(f"Error processing JDX: {str(e)}")
226
 
227
  if ir_data is None:
228
- raise gr.Error("Unsupported file format or invalid data points. Please check your spectrum file.")
229
 
230
- # 光谱编码与检索
231
- ir_tensor = torch.tensor(ir_data, dtype=torch.float32).unsqueeze(0).to(DEVICE)
232
- with torch.no_grad():
233
- ir_feat = current_ir_model(ir_tensor).to(torch.float32)
234
-
235
- if library_name == "Custom Library":
236
  lib_features, lib_smiles, display_smiles = process_custom_smiles(custom_smiles_str, current_smiles_model)
237
  if lib_features is None:
238
- raise gr.Error("No valid Custom SMILES provided.")
239
  else:
240
- lib_features, lib_smiles = ALL_LIBS[library_name]
241
  display_smiles = lib_smiles
242
 
 
 
 
 
 
243
  sims = ir_feat @ lib_features.to(DEVICE).to(torch.float32).t()
244
  scores, indices = sims.topk(min(10, lib_features.shape[0]), dim=1)
245
  res_smiles = [display_smiles[idx.item()] for idx in indices[0]]
@@ -270,7 +301,6 @@ def run_unified_retrieval(ir_file, spectrum_type, library_name, custom_smiles_st
270
  grid_filename = "search_results_grid.png"
271
  grid_img_path = generate_mol_grid_custom(res_smiles, legends, grid_filename)
272
 
273
- # 归一化绘图数据 (4000 -> 500)
274
  wavenumbers_plot = np.linspace(4000, 500, 3500)
275
  plot_df = pd.DataFrame({'wavenumber': wavenumbers_plot, 'intensity': ir_data})
276
 
@@ -288,7 +318,7 @@ def run_unified_retrieval(ir_file, spectrum_type, library_name, custom_smiles_st
288
  )
289
 
290
 
291
- # ==================== 4. 界面前端构建 ====================
292
  CSS = """
293
  * { font-family: 'Times New Roman', Times, serif !important; }
294
  body { background-color: #ffffff !important; color: #333333 !important; }
 
25
  EXAMPLE_DIR = "example_files"
26
  RETRIEVAL_WEIGHTS_DIR = r"model_weights"
27
  PROCESSED_DATA_DIR = r"processed_data"
28
+ os.makedirs(PROCESSED_DATA_DIR, exist_ok=True)
29
 
30
+ # 密钥与私有仓库配置 (仅服务端可见)
31
  ADMIN_KEY = os.environ.get("ADMIN_KEY", "123456")
32
+ HF_TOKEN = os.environ.get("HF_TOKEN", None)
33
  PRIVATE_DATASET_REPO = "Hsqcsu/Derivative_PS_data"
34
 
35
+ # 衍生库内存缓存 (启动时保持为 None,只有密码正确时才按需加载)
36
+ DERIVATIVE_PS_CACHE = None
37
+
38
  # ==================== Platt Calibration 拟合参数 ====================
39
  PLATT_CALIBRATION_PARAMS = {
40
  "general": {
 
89
  repo_id=PRIVATE_DATASET_REPO,
90
  filename=filename,
91
  repo_type="dataset",
92
+ token=HF_TOKEN if HF_TOKEN else True,
93
  local_dir=PROCESSED_DATA_DIR
94
  )
95
  return path
96
  except Exception as e:
97
+ print(f"[Auth Error] Failed to download {filename}: {e}")
98
+ return None
99
  return local_path
100
 
101
 
102
  def load_gz_features(filename, device):
103
  path = download_private_file(filename)
104
+ if path is None or not os.path.exists(path):
105
+ return None
106
  with gzip.open(path, 'rb') as f:
107
  features = torch.load(f, weights_only=False)
108
  return features.to(device)
 
110
 
111
  def load_smiles_list(filename):
112
  path = download_private_file(filename)
113
+ if path is None or not os.path.exists(path):
114
+ return []
115
  with open(path, 'r', encoding='utf-8') as f:
116
  return [line.strip() for line in f if line.strip()]
117
 
 
126
  return model_instance
127
 
128
 
129
+ # ==================== 1. 初始化模型与基础库 (启动时不加载衍生库) ====================
130
+ print("Initializing Models and Base Feature Libraries...")
131
 
132
+ # 通用分支模型
133
  ir_model_general = load_model_instance(IRModel(), "overall_best_top1_ir.pth", DEVICE)
134
  smiles_model_general = load_model_instance(
135
  SmilesModel(roberta_model_path=None, roberta_tokenizer_path=TOKENIZER_PATH, smiles_maxlen=300, feature_dim=768),
 
137
  DEVICE
138
  )
139
 
140
+ # 专属 PS 分支模型
141
  ir_model_ps = load_model_instance(IRModel(), "overall_best_nps_recall10_ir.pth", DEVICE)
142
  smiles_model_ps = load_model_instance(
143
  SmilesModel(roberta_model_path=None, roberta_tokenizer_path=TOKENIZER_PATH, smiles_maxlen=300, feature_dim=768),
 
145
  DEVICE
146
  )
147
 
148
+ # 仅加载公开库
149
  library_nist_features = load_gz_features("library_NIST_smiles_features_fp16.gz", DEVICE)
150
  general_smiles_nist = load_smiles_list("NIST_smiles_can.txt")
151
 
 
 
 
152
  library_existed_ps_features = load_gz_features("library_Existing_PS_smiles_features_fp16.gz", DEVICE)
153
  ps_smiles_existed = load_smiles_list("smiles_Existing_PS.txt")
154
 
155
+ STATIC_LIBS = {
156
  "NIST": (library_nist_features, general_smiles_nist),
157
+ "PS Existing Library": (library_existed_ps_features, ps_smiles_existed)
 
158
  }
159
 
160
 
161
+ # ==================== 2. 衍生库动态按需鉴权加载 ====================
162
+ def get_authorized_derivative_ps_library(access_key):
163
+ """
164
+ 只有输入正确密钥时,才动态向私有仓库请求并加载衍生库数据
165
+ """
166
+ global DERIVATIVE_PS_CACHE
167
+
168
+ if not access_key or access_key.strip() != ADMIN_KEY:
169
+ raise gr.Error("⛔ 访问被拒绝:请输入正确的 PS 衍生库访问密钥 (Access Key)。")
170
+
171
+ # 若已授权且已加载过,直接返回内存缓存
172
+ if DERIVATIVE_PS_CACHE is not None:
173
+ return DERIVATIVE_PS_CACHE
174
+
175
+ # 首次输入正确密码时触发安全下载与加载
176
+ print("--> 密钥验证成功,正在动态加载 PS 衍生库特征...")
177
+ feat = load_gz_features("library_Derivative_PS_smiles_features_fp16.gz", DEVICE)
178
+ smiles = load_smiles_list("smiles_Derivative_PS.txt")
179
+
180
+ if feat is None or len(smiles) == 0:
181
+ raise gr.Error("⛔ 衍生库加载失败:请检查 Hugging Face Space Settings 中是否已配置有效的 HF_TOKEN。")
182
+
183
+ DERIVATIVE_PS_CACHE = (feat, smiles)
184
+ return DERIVATIVE_PS_CACHE
185
+
186
+
187
+ # ==================== 3. 置信度计算与分子编码 ====================
188
  def calculate_calibrated_confidence(cosine_sim: float, smiles_model_instance, model_type: str = "general") -> float:
189
  with torch.no_grad():
190
  t = torch.exp(smiles_model_instance.t_prime).cpu().item()
 
221
  return feats, valid_smiles, [original_map[s] for s in valid_smiles]
222
 
223
 
224
+ # ==================== 4. 检索主业务流程 ====================
225
  def run_unified_retrieval(ir_file, spectrum_type, library_name, custom_smiles_str, access_key, progress=gr.Progress()):
 
 
 
 
226
  if ir_file is None:
227
+ raise gr.Error("请上传待检索的红外光谱文件。")
228
 
229
  # 动态切换专属权重
230
  is_ps_lib = library_name in ["PS Existing Library", "PS Derivative Library"]
 
241
  w_raw, i_raw = df.iloc[:, 0].values, df.iloc[:, 1].values
242
  ir_data = preprocess_csv_spectra_higer_500(w_raw, i_raw)
243
  except Exception as e:
244
+ raise gr.Error(f"CSV 解析处理失败: {str(e)}")
245
 
246
  elif file_path.lower().endswith('.jdx'):
247
  try:
 
249
  w_raw, i_raw = np.array(data['x']), np.array(data['y'])
250
  ir_data = preprocess_jdx_spectra_higer_500(w_raw, i_raw)
251
  except Exception as e:
252
+ raise gr.Error(f"JDX 解析处理失败: {str(e)}")
253
 
254
  if ir_data is None:
255
+ raise gr.Error("文件格式不支持或光谱数据点无效,请检查文件。")
256
 
257
+ # 获取匹配目标库 (针对衍生库执行强密码鉴权)
258
+ if library_name == "PS Derivative Library":
259
+ lib_features, lib_smiles = get_authorized_derivative_ps_library(access_key)
260
+ display_smiles = lib_smiles
261
+ elif library_name == "Custom Library":
 
262
  lib_features, lib_smiles, display_smiles = process_custom_smiles(custom_smiles_str, current_smiles_model)
263
  if lib_features is None:
264
+ raise gr.Error("未提供有效的自定义 SMILES 列表。")
265
  else:
266
+ lib_features, lib_smiles = STATIC_LIBS[library_name]
267
  display_smiles = lib_smiles
268
 
269
+ # 光谱编码与向量检索
270
+ ir_tensor = torch.tensor(ir_data, dtype=torch.float32).unsqueeze(0).to(DEVICE)
271
+ with torch.no_grad():
272
+ ir_feat = current_ir_model(ir_tensor).to(torch.float32)
273
+
274
  sims = ir_feat @ lib_features.to(DEVICE).to(torch.float32).t()
275
  scores, indices = sims.topk(min(10, lib_features.shape[0]), dim=1)
276
  res_smiles = [display_smiles[idx.item()] for idx in indices[0]]
 
301
  grid_filename = "search_results_grid.png"
302
  grid_img_path = generate_mol_grid_custom(res_smiles, legends, grid_filename)
303
 
 
304
  wavenumbers_plot = np.linspace(4000, 500, 3500)
305
  plot_df = pd.DataFrame({'wavenumber': wavenumbers_plot, 'intensity': ir_data})
306
 
 
318
  )
319
 
320
 
321
+ # ==================== 5. 前端界面 ====================
322
  CSS = """
323
  * { font-family: 'Times New Roman', Times, serif !important; }
324
  body { background-color: #ffffff !important; color: #333333 !important; }