robert 2.1.1__tar.gz → 2.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {robert-2.1.1 → robert-2.1.2}/PKG-INFO +2 -2
- {robert-2.1.1 → robert-2.1.2}/robert/api.py +13 -11
- {robert-2.1.1 → robert-2.1.2}/robert/aqme.py +3 -2
- {robert-2.1.1 → robert-2.1.2}/robert/argument_parser.py +2 -1
- {robert-2.1.1 → robert-2.1.2}/robert/curate.py +3 -0
- {robert-2.1.1 → robert-2.1.2}/robert/generate.py +8 -3
- {robert-2.1.1 → robert-2.1.2}/robert/generate_utils.py +4 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/easyrob_launcher.py +37 -2
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/main/window.py +99 -46
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tabs/aqme.py +119 -21
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/utils/aqme_utils.py +79 -9
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/utils/utils_gui.py +15 -9
- {robert-2.1.1 → robert-2.1.2}/robert/predict.py +12 -0
- {robert-2.1.1 → robert-2.1.2}/robert/predict_utils.py +40 -32
- {robert-2.1.1 → robert-2.1.2}/robert/utils.py +221 -104
- {robert-2.1.1 → robert-2.1.2}/robert.egg-info/PKG-INFO +2 -2
- {robert-2.1.1 → robert-2.1.2}/setup.cfg +1 -1
- {robert-2.1.1 → robert-2.1.2}/setup.py +1 -1
- {robert-2.1.1 → robert-2.1.2}/tests/test_1curate.py +22 -1
- {robert-2.1.1 → robert-2.1.2}/tests/test_7api.py +65 -0
- {robert-2.1.1 → robert-2.1.2}/tests/test_easyrob.py +1 -1
- {robert-2.1.1 → robert-2.1.2}/LICENSE +0 -0
- {robert-2.1.1 → robert-2.1.2}/README.md +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/__init__.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/__main__.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/evaluate.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/__init__.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/easyrob.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/main/__init__.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tabs/__init__.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tabs/advanced_options.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tabs/images.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tabs/molssi.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tabs/predictions.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tabs/results.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/chemdraw.md +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/csv.md +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/descriptors.md +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/overview.md +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/predictions.md +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_1.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_10.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_11.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_12.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_13.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_2.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_3.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_4.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_5.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_6.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_7.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_8.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/chemdraw/chemdraw_9.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_1.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_1_1.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_1_2.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_1_3.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_1_4.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_1_5.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_2.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_3.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_4.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_5.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_6.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_7.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_8.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/csv/csv_9.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/descriptors/descriptors_1.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/descriptors/descriptors_2.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/descriptors/descriptors_3.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/descriptors/descriptors_4.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/descriptors/descriptors_5.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/descriptors/descriptors_6.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_1.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_2.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_3.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_4.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_5.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_6.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_7.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_1_8.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_2.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_3.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_4.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_5.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_5_1.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_5_2.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/overview/overview_5_3.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/predictions/predictions_1.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/predictions/predictions_1_1.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/predictions/predictions_2.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/predictions/predictions_3.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/predictions/predictions_4.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/predictions/predictions_5.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/tutorials/tutorial_images/predictions/predictions_6.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/utils/__init__.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/utils/molssi_utils.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/utils/predictions_utils.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/gui_easyrob/version.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/icons/Robert_icon.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/icons/Robert_logo.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/icons/Robert_logo_transparent.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/icons/documentation_icon.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/icons/info_icon.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/icons/pdf_icon.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/icons/play_icon.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/icons/stop_icon.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/icons/youtube_icon.ico +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/model_params/ADAB_params.yaml +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/model_params/GB_params.yaml +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/model_params/GP_params.yaml +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/model_params/MVL_params.yaml +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/model_params/NN_params.yaml +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/model_params/RF_params.yaml +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/model_params/VR_params.yaml +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/Robert_icon.ico +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/Robert_logo.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/Robert_logo1.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/abbrev.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/adv_anal.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/features.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/generate.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/outliers.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/pred.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/repro.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_0.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_1.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_10.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_2.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_3.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_4.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_5.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_6.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_7.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_8.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_9.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_w_1_0.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_w_1_1.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_w_2_0.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_w_2_1.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_w_2_2.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_w_3_0.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_w_3_1.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_w_3_2.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/score_w_3_3.jpg +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/transpa.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report/y_distrib.png +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/report_utils.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/robert.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert/verify.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert.egg-info/SOURCES.txt +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert.egg-info/dependency_links.txt +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert.egg-info/entry_points.txt +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert.egg-info/requires.txt +0 -0
- {robert-2.1.1 → robert-2.1.2}/robert.egg-info/top_level.txt +0 -0
- {robert-2.1.1 → robert-2.1.2}/tests/test_2generate.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/tests/test_3verify.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/tests/test_4predict.py +0 -0
- {robert-2.1.1 → robert-2.1.2}/tests/test_5aqme_n_full.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: robert
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Refiner and Optimizer of a Bunch of Existing Regression Tools
|
|
5
5
|
Home-page: https://github.com/jvalegre/robert
|
|
6
|
-
Download-URL: https://github.com/jvalegre/robert/archive/refs/tags/2.1.
|
|
6
|
+
Download-URL: https://github.com/jvalegre/robert/archive/refs/tags/2.1.2.tar.gz
|
|
7
7
|
Author: Juan V. Alegre Requena, David Dalmau
|
|
8
8
|
Author-email: jv.alegre@csic.es
|
|
9
9
|
License: MIT
|
|
@@ -514,6 +514,11 @@ class RobertModel(BaseEstimator):
|
|
|
514
514
|
umode = "cv_sd"
|
|
515
515
|
else:
|
|
516
516
|
umode = False
|
|
517
|
+
if umode in ("conformal", "both") and self.problem_type != "reg":
|
|
518
|
+
raise ValueError(
|
|
519
|
+
"return_uncertainty='conformal' or 'both' is only supported "
|
|
520
|
+
"for problem_type='reg'."
|
|
521
|
+
)
|
|
517
522
|
|
|
518
523
|
descriptors = list(self.model_data_["X_descriptors"])
|
|
519
524
|
y_target = str(self.model_data_["y"])
|
|
@@ -559,6 +564,7 @@ class RobertModel(BaseEstimator):
|
|
|
559
564
|
base["csv_test"] = pred_name
|
|
560
565
|
base["params_dir"] = "GENERATE/Best_model"
|
|
561
566
|
base["names"] = self.names_col_
|
|
567
|
+
base["_api_predict"] = True
|
|
562
568
|
|
|
563
569
|
with _noninteractive_mpl(), _chdir(workdir):
|
|
564
570
|
predict_module(**base)
|
|
@@ -608,11 +614,6 @@ class RobertModel(BaseEstimator):
|
|
|
608
614
|
raise RuntimeError(f"Column {sd_col!r} missing in {csv_path}")
|
|
609
615
|
y_sd = ordered[sd_col].to_numpy(dtype=float)
|
|
610
616
|
if umode in ("conformal", "both"):
|
|
611
|
-
if self.problem_type != "reg":
|
|
612
|
-
raise ValueError(
|
|
613
|
-
"return_uncertainty='conformal' or 'both' is only supported "
|
|
614
|
-
"for problem_type='reg'."
|
|
615
|
-
)
|
|
616
617
|
if hw_col not in result_df.columns:
|
|
617
618
|
raise RuntimeError(f"Column {hw_col!r} missing in {csv_path}")
|
|
618
619
|
y_hw = ordered[hw_col].to_numpy(dtype=float)
|
|
@@ -637,9 +638,10 @@ class RobertModel(BaseEstimator):
|
|
|
637
638
|
y_hat = np.asarray(self.predict(X, return_std=False)).ravel()
|
|
638
639
|
if self.problem_type == "reg":
|
|
639
640
|
return float(r2_score(y_true, y_hat))
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
)
|
|
645
|
-
|
|
641
|
+
try:
|
|
642
|
+
y_true_score = np.round(y_true).astype(int)
|
|
643
|
+
y_hat_score = np.round(y_hat).astype(int)
|
|
644
|
+
except (TypeError, ValueError):
|
|
645
|
+
y_true_score = y_true.astype(str)
|
|
646
|
+
y_hat_score = y_hat.astype(str)
|
|
647
|
+
return float(accuracy_score(y_true_score, y_hat_score))
|
|
@@ -127,7 +127,8 @@ class aqme:
|
|
|
127
127
|
aqme_indv_name = f'AQME_indiv_{smi_suffix}'
|
|
128
128
|
|
|
129
129
|
# run AQME-QDESCP to generate descriptors
|
|
130
|
-
|
|
130
|
+
python_executable = sys.executable or 'python'
|
|
131
|
+
cmd_qdescp = [python_executable,'-u', '-m', 'aqme', '--qdescp', '--input', f'{aqme_indv_name}.csv', '--program', 'xtb', '--csv_name', f'{aqme_indv_name}.csv', '--nprocs', f'{self.args.nprocs}', '--robert']
|
|
131
132
|
_ = self.run_aqme(cmd_qdescp, self.args.qdescp_keywords)
|
|
132
133
|
|
|
133
134
|
if smi_suffix is not None:
|
|
@@ -268,4 +269,4 @@ def move_aqme():
|
|
|
268
269
|
shutil.rmtree(f'AQME/{file}')
|
|
269
270
|
else:
|
|
270
271
|
os.remove(f'AQME/{file}')
|
|
271
|
-
shutil.move(file, f'AQME/{file}')
|
|
272
|
+
shutil.move(file, f'AQME/{file}')
|
|
@@ -61,7 +61,8 @@ var_dict = {
|
|
|
61
61
|
"descp_lvl": "interpret",
|
|
62
62
|
"report_modules" : ['AQME','CURATE','GENERATE','VERIFY','PREDICT'],
|
|
63
63
|
"debug_report": False,
|
|
64
|
-
#
|
|
64
|
+
# Internal API/BO uncertainty options. Normal ROBERT workflows leave them unused.
|
|
65
|
+
"_api_predict": False,
|
|
65
66
|
"conformal_enable": True,
|
|
66
67
|
"conformal_calib_frac": 0.15,
|
|
67
68
|
"conformal_coverage": 0.9,
|
|
@@ -61,6 +61,7 @@ Parameters
|
|
|
61
61
|
|
|
62
62
|
import time
|
|
63
63
|
import os
|
|
64
|
+
import json
|
|
64
65
|
import pandas as pd
|
|
65
66
|
from robert.utils import (load_variables, finish_print, load_database, pearson_map,
|
|
66
67
|
check_clas_problem, categorical_transform, correlation_filter)
|
|
@@ -203,6 +204,8 @@ class curate:
|
|
|
203
204
|
options_df['csv_name'] = [csv_curate_name_general]
|
|
204
205
|
|
|
205
206
|
# Save class label mapping if it exists (for classification with string labels)
|
|
207
|
+
if hasattr(self.args, 'class_mapping_reverse'):
|
|
208
|
+
options_df['class_mapping_reverse'] = [json.dumps(self.args.class_mapping_reverse)]
|
|
206
209
|
if hasattr(self.args, 'class_0_label'):
|
|
207
210
|
options_df['class_0_label'] = [self.args.class_0_label]
|
|
208
211
|
options_df['class_1_label'] = [self.args.class_1_label]
|
|
@@ -124,8 +124,8 @@ class generate:
|
|
|
124
124
|
# load database, discard user-defined descriptors and perform data checks
|
|
125
125
|
csv_df, _, _ = load_database(self,self.args.csv_name,"generate")
|
|
126
126
|
|
|
127
|
-
#
|
|
128
|
-
if self.args.type.lower() == 'reg' and self.args.auto_type:
|
|
127
|
+
# adjust classification labels and auto-detect binary classification
|
|
128
|
+
if self.args.type.lower() == 'clas' or (self.args.type.lower() == 'reg' and self.args.auto_type):
|
|
129
129
|
self = check_clas_problem(self,csv_df)
|
|
130
130
|
|
|
131
131
|
# scan different ML models
|
|
@@ -171,7 +171,9 @@ class generate:
|
|
|
171
171
|
|
|
172
172
|
# load database, discard user-defined descriptors and perform data checks
|
|
173
173
|
csv_df, csv_X, csv_y = load_database(self,csv_to_load,"generate",print_info=False)
|
|
174
|
-
|
|
174
|
+
if self.args.type.lower() == 'clas':
|
|
175
|
+
self = check_clas_problem(self,csv_df)
|
|
176
|
+
csv_y = csv_df[self.args.y]
|
|
175
177
|
|
|
176
178
|
# standardizes and separates an external test set
|
|
177
179
|
Xy_data = prepare_sets(self,csv_df,csv_X,csv_y,None,self.args.names,None,None,None,BO_opt=True)
|
|
@@ -183,6 +185,9 @@ class generate:
|
|
|
183
185
|
if self.args.pfi_filter:
|
|
184
186
|
# load database, discard user-defined descriptors and perform data checks
|
|
185
187
|
csv_df, csv_X, csv_y = load_database(self,csv_to_load,"generate",print_info=False)
|
|
188
|
+
if self.args.type.lower() == 'clas':
|
|
189
|
+
self = check_clas_problem(self,csv_df)
|
|
190
|
+
csv_y = csv_df[self.args.y]
|
|
186
191
|
|
|
187
192
|
# standardizes and separates an external test set
|
|
188
193
|
Xy_data = prepare_sets(self,csv_df,csv_X,csv_y,None,self.args.names,None,None,None,BO_opt=True)
|
|
@@ -60,6 +60,8 @@ def BO_workflow(self, Xy_data, csv_df, ML_model):
|
|
|
60
60
|
bo_data_to_save['X_descriptors'] = json.dumps(bo_data_to_save['X_descriptors'])
|
|
61
61
|
|
|
62
62
|
# Save class label mapping if it exists (for classification with string labels)
|
|
63
|
+
if hasattr(self.args, 'class_mapping_reverse'):
|
|
64
|
+
bo_data_to_save['class_mapping_reverse'] = json.dumps(self.args.class_mapping_reverse)
|
|
63
65
|
if hasattr(self.args, 'class_0_label'):
|
|
64
66
|
bo_data_to_save['class_0_label'] = self.args.class_0_label
|
|
65
67
|
bo_data_to_save['class_1_label'] = self.args.class_1_label
|
|
@@ -152,6 +154,8 @@ def save_pfi_csv(self,csv_df,name_csv_hyperopt,PFI_dict,Xy_data_PFI,ML_model):
|
|
|
152
154
|
path_csv_PFI = self.args.destination.joinpath(f'{name_csv_hyperopt_PFI}_PFI')
|
|
153
155
|
|
|
154
156
|
# Save class label mapping if it exists (for classification with string labels)
|
|
157
|
+
if hasattr(self.args, 'class_mapping_reverse'):
|
|
158
|
+
PFI_dict['class_mapping_reverse'] = json.dumps(self.args.class_mapping_reverse)
|
|
155
159
|
if hasattr(self.args, 'class_0_label'):
|
|
156
160
|
PFI_dict['class_0_label'] = self.args.class_0_label
|
|
157
161
|
PFI_dict['class_1_label'] = self.args.class_1_label
|
|
@@ -37,19 +37,52 @@ configure_environment()
|
|
|
37
37
|
|
|
38
38
|
from PySide6.QtCore import Qt, QCoreApplication
|
|
39
39
|
from PySide6.QtWidgets import QApplication
|
|
40
|
-
from PySide6.QtGui import QPalette
|
|
40
|
+
from PySide6.QtGui import QIcon, QPalette
|
|
41
|
+
|
|
42
|
+
APP_NAME = "EasyRob"
|
|
43
|
+
APP_DISPLAY_NAME = "EasyRob"
|
|
44
|
+
APP_ORGANIZATION = "The Alegre Group"
|
|
45
|
+
APP_DESKTOP_FILE_NAME = "easyrob"
|
|
46
|
+
APP_USER_MODEL_ID = "com.thealegregroup.easyrob"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def configure_process_identity():
|
|
50
|
+
"""Configure process-level identity used by desktop shells."""
|
|
51
|
+
if sys.platform != "win32":
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
import ctypes
|
|
56
|
+
|
|
57
|
+
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
|
|
58
|
+
APP_USER_MODEL_ID
|
|
59
|
+
)
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
41
62
|
|
|
42
63
|
def main():
|
|
43
64
|
"""Main entry point for the EasyROB application."""
|
|
65
|
+
configure_process_identity()
|
|
44
66
|
QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_UseSoftwareOpenGL)
|
|
45
67
|
|
|
46
68
|
app = QApplication(sys.argv)
|
|
69
|
+
app.setApplicationName(APP_NAME)
|
|
70
|
+
app.setApplicationDisplayName(APP_DISPLAY_NAME)
|
|
71
|
+
app.setOrganizationName(APP_ORGANIZATION)
|
|
72
|
+
app.setDesktopFileName(APP_DESKTOP_FILE_NAME)
|
|
47
73
|
|
|
48
74
|
# Import AFTER QApplication to avoid lunching issues
|
|
49
75
|
try:
|
|
50
76
|
from easyrob import get_main_window_class
|
|
77
|
+
from utils.utils_gui import AssetLibrary
|
|
51
78
|
except ImportError:
|
|
52
79
|
from robert.gui_easyrob.easyrob import get_main_window_class
|
|
80
|
+
from robert.gui_easyrob.utils.utils_gui import AssetLibrary
|
|
81
|
+
|
|
82
|
+
with AssetLibrary.Robert_icon.get_path() as path_icon:
|
|
83
|
+
icon = QIcon(str(path_icon))
|
|
84
|
+
if not icon.isNull():
|
|
85
|
+
app.setWindowIcon(icon)
|
|
53
86
|
|
|
54
87
|
EasyROB = get_main_window_class()
|
|
55
88
|
|
|
@@ -60,9 +93,11 @@ def main():
|
|
|
60
93
|
app.setStyleSheet(f"QLabel {{ color: {text_color}; }}")
|
|
61
94
|
|
|
62
95
|
window = EasyROB()
|
|
96
|
+
if not app.windowIcon().isNull():
|
|
97
|
+
window.setWindowIcon(app.windowIcon())
|
|
63
98
|
window.show()
|
|
64
99
|
|
|
65
100
|
sys.exit(app.exec())
|
|
66
101
|
|
|
67
102
|
if __name__ == "__main__":
|
|
68
|
-
main()
|
|
103
|
+
main()
|
|
@@ -196,6 +196,21 @@ class EasyROB(QMainWindow):
|
|
|
196
196
|
|
|
197
197
|
QApplication.quit()
|
|
198
198
|
|
|
199
|
+
def _resolve_python_executable(self):
|
|
200
|
+
"""Return the Python interpreter that should be reused for child workflows."""
|
|
201
|
+
python_pointer = sys.executable or "python"
|
|
202
|
+
|
|
203
|
+
if getattr(sys, "frozen", False):
|
|
204
|
+
embedded_env = Path.cwd() / "_internal" / "robert_env"
|
|
205
|
+
if sys.platform == "win32":
|
|
206
|
+
python_pointer = embedded_env / "python.exe"
|
|
207
|
+
elif sys.platform == "darwin":
|
|
208
|
+
python_pointer = embedded_env / "bin" / "python3"
|
|
209
|
+
else:
|
|
210
|
+
python_pointer = embedded_env / "bin" / "python"
|
|
211
|
+
|
|
212
|
+
return str(python_pointer)
|
|
213
|
+
|
|
199
214
|
def move_to_selected(self):
|
|
200
215
|
"""Move selected items from available_list to selected_list."""
|
|
201
216
|
selected_items = self.available_list.selectedItems()
|
|
@@ -1461,16 +1476,20 @@ class EasyROB(QMainWindow):
|
|
|
1461
1476
|
if not hasattr(self, "tab_widget_aqme"):
|
|
1462
1477
|
return
|
|
1463
1478
|
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
self.
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
self.
|
|
1472
|
-
|
|
1473
|
-
|
|
1479
|
+
try:
|
|
1480
|
+
if self.csv_test_path:
|
|
1481
|
+
unified_smiles = self.tab_widget_aqme.build_unified_smiles_context(
|
|
1482
|
+
self.file_path,
|
|
1483
|
+
self.csv_test_path
|
|
1484
|
+
)
|
|
1485
|
+
else:
|
|
1486
|
+
unified_smiles = self.tab_widget_aqme.build_unified_smiles_context(
|
|
1487
|
+
self.file_path
|
|
1488
|
+
)
|
|
1489
|
+
except ValueError:
|
|
1490
|
+
# Missing SMILES is a valid non-AQME situation; keep the GUI quiet.
|
|
1491
|
+
unified_smiles = []
|
|
1492
|
+
|
|
1474
1493
|
self.tab_widget_aqme.unified_smiles = unified_smiles
|
|
1475
1494
|
|
|
1476
1495
|
def set_main_chemdraw_path(self, file_path):
|
|
@@ -1742,15 +1761,7 @@ class EasyROB(QMainWindow):
|
|
|
1742
1761
|
def _build_test_aqme_command(self, original_command=None, qdescp_keywords=None):
|
|
1743
1762
|
"""Builds an AQME command for generating descriptors for the test CSV."""
|
|
1744
1763
|
|
|
1745
|
-
python_pointer =
|
|
1746
|
-
if getattr(sys, "frozen", False):
|
|
1747
|
-
env = Path.cwd() / "_internal" / "robert_env"
|
|
1748
|
-
if sys.platform == "win32":
|
|
1749
|
-
python_pointer = env / "python.exe"
|
|
1750
|
-
elif sys.platform == "darwin":
|
|
1751
|
-
python_pointer = env / "bin" / "python3"
|
|
1752
|
-
else:
|
|
1753
|
-
python_pointer = env / "bin" / "python"
|
|
1764
|
+
python_pointer = self._resolve_python_executable()
|
|
1754
1765
|
|
|
1755
1766
|
test_csv = os.path.basename(self.csv_test_path)
|
|
1756
1767
|
|
|
@@ -1854,8 +1865,9 @@ class EasyROB(QMainWindow):
|
|
|
1854
1865
|
if not smarts or not selected_atoms:
|
|
1855
1866
|
return
|
|
1856
1867
|
|
|
1857
|
-
|
|
1858
|
-
|
|
1868
|
+
dat_dir = self._get_atom_mapping_dir(run_dir)
|
|
1869
|
+
os.makedirs(dat_dir, exist_ok=True)
|
|
1870
|
+
dat_path = os.path.join(dat_dir, filename)
|
|
1859
1871
|
|
|
1860
1872
|
try:
|
|
1861
1873
|
pattern_mol = Chem.MolFromSmarts(smarts)
|
|
@@ -1915,6 +1927,58 @@ class EasyROB(QMainWindow):
|
|
|
1915
1927
|
self.console_output.append(
|
|
1916
1928
|
f"<span style='color:orange;'>WARNING: Failed to write atom mapping dat: {e}</span>"
|
|
1917
1929
|
)
|
|
1930
|
+
|
|
1931
|
+
def _write_atom_mapping_image(
|
|
1932
|
+
self,
|
|
1933
|
+
smarts: str,
|
|
1934
|
+
selected_atoms: list,
|
|
1935
|
+
run_dir: str,
|
|
1936
|
+
filename: str = "AtomMapping_preview.png"
|
|
1937
|
+
):
|
|
1938
|
+
"""Write a numbered PNG preview for the current atom-mapping contract."""
|
|
1939
|
+
|
|
1940
|
+
if not smarts or not selected_atoms:
|
|
1941
|
+
return None
|
|
1942
|
+
|
|
1943
|
+
image_dir = self._get_atom_mapping_dir(run_dir)
|
|
1944
|
+
os.makedirs(image_dir, exist_ok=True)
|
|
1945
|
+
image_path = os.path.join(image_dir, filename)
|
|
1946
|
+
|
|
1947
|
+
try:
|
|
1948
|
+
saved_path = self.tab_widget_aqme.save_atom_mapping_image(
|
|
1949
|
+
image_path,
|
|
1950
|
+
smarts=smarts,
|
|
1951
|
+
selected_atoms=selected_atoms,
|
|
1952
|
+
)
|
|
1953
|
+
return saved_path
|
|
1954
|
+
except Exception as e:
|
|
1955
|
+
self.console_output.append(
|
|
1956
|
+
f"<span style='color:orange;'>WARNING: Failed to write atom mapping image: {e}</span>"
|
|
1957
|
+
)
|
|
1958
|
+
return None
|
|
1959
|
+
|
|
1960
|
+
def _get_atom_mapping_dir(self, run_dir: str) -> str:
|
|
1961
|
+
"""Return the dedicated folder used for atom-mapping artifacts."""
|
|
1962
|
+
|
|
1963
|
+
return os.path.join(run_dir, "AtomMapping")
|
|
1964
|
+
|
|
1965
|
+
def _resolve_atom_mapping_dat_path(
|
|
1966
|
+
self,
|
|
1967
|
+
run_dir: str,
|
|
1968
|
+
filename: str = "AtomMapping_data.dat"
|
|
1969
|
+
) -> str | None:
|
|
1970
|
+
"""Resolve the atom-mapping contract path, preferring the dedicated folder."""
|
|
1971
|
+
|
|
1972
|
+
candidate_paths = [
|
|
1973
|
+
os.path.join(self._get_atom_mapping_dir(run_dir), filename),
|
|
1974
|
+
os.path.join(run_dir, filename),
|
|
1975
|
+
]
|
|
1976
|
+
|
|
1977
|
+
for path in candidate_paths:
|
|
1978
|
+
if os.path.isfile(path):
|
|
1979
|
+
return path
|
|
1980
|
+
|
|
1981
|
+
return None
|
|
1918
1982
|
def _check_generate_folder(self, run_dir):
|
|
1919
1983
|
"""Checks if a GENERATE folder exists in the run directory."""
|
|
1920
1984
|
|
|
@@ -2137,6 +2201,11 @@ class EasyROB(QMainWindow):
|
|
|
2137
2201
|
selected_atoms=selected_atoms_for_robert,
|
|
2138
2202
|
run_dir=run_dir
|
|
2139
2203
|
)
|
|
2204
|
+
self._write_atom_mapping_image(
|
|
2205
|
+
smarts=smarts,
|
|
2206
|
+
selected_atoms=selected_atoms_for_robert,
|
|
2207
|
+
run_dir=run_dir
|
|
2208
|
+
)
|
|
2140
2209
|
|
|
2141
2210
|
# --------------------------------------------------
|
|
2142
2211
|
# Decide REAL input CSVs for ROBERT
|
|
@@ -2182,9 +2251,9 @@ class EasyROB(QMainWindow):
|
|
|
2182
2251
|
# -----------------------------------------------
|
|
2183
2252
|
# Step 1: Detect atomic mapping contract (.dat)
|
|
2184
2253
|
# -----------------------------------------------
|
|
2185
|
-
dat_path =
|
|
2254
|
+
dat_path = self._resolve_atom_mapping_dat_path(run_dir)
|
|
2186
2255
|
|
|
2187
|
-
if
|
|
2256
|
+
if dat_path:
|
|
2188
2257
|
|
|
2189
2258
|
self.console_output.append(
|
|
2190
2259
|
f"[INFO] Atomic mapping contract detected: {dat_path}"
|
|
@@ -2404,19 +2473,7 @@ class EasyROB(QMainWindow):
|
|
|
2404
2473
|
|
|
2405
2474
|
def build_robert_command(self, selected_file_path):
|
|
2406
2475
|
"""Builds the ROBERT command based on GUI selections."""
|
|
2407
|
-
python_pointer =
|
|
2408
|
-
|
|
2409
|
-
# --------------------------------------------------
|
|
2410
|
-
# Detect embedded Python (frozen app)
|
|
2411
|
-
# --------------------------------------------------
|
|
2412
|
-
if getattr(sys, "frozen", False):
|
|
2413
|
-
embedded_env = Path.cwd() / "_internal" / "robert_env"
|
|
2414
|
-
if sys.platform == "win32":
|
|
2415
|
-
python_pointer = embedded_env / "python.exe"
|
|
2416
|
-
elif sys.platform == "darwin":
|
|
2417
|
-
python_pointer = embedded_env / "bin" / "python3"
|
|
2418
|
-
else:
|
|
2419
|
-
python_pointer = embedded_env / "bin" / "python"
|
|
2476
|
+
python_pointer = self._resolve_python_executable()
|
|
2420
2477
|
|
|
2421
2478
|
wf = self.workflow_selector.currentText()
|
|
2422
2479
|
|
|
@@ -2857,16 +2914,7 @@ class EasyROB(QMainWindow):
|
|
|
2857
2914
|
def build_aqme_command(self, selected_file_path, selected_atoms_override=None):
|
|
2858
2915
|
"""Builds the AQME command based on the GUI selections."""
|
|
2859
2916
|
|
|
2860
|
-
python_pointer =
|
|
2861
|
-
|
|
2862
|
-
if getattr(sys, "frozen", False):
|
|
2863
|
-
embeded_env = Path.cwd() / "_internal" / "robert_env"
|
|
2864
|
-
if sys.platform == "win32":
|
|
2865
|
-
python_pointer = embeded_env / "python.exe"
|
|
2866
|
-
elif sys.platform == "darwin":
|
|
2867
|
-
python_pointer = embeded_env / "bin" / "python3"
|
|
2868
|
-
else:
|
|
2869
|
-
python_pointer = embeded_env / "bin" / "python"
|
|
2917
|
+
python_pointer = self._resolve_python_executable()
|
|
2870
2918
|
|
|
2871
2919
|
csv_name = os.path.basename(selected_file_path)
|
|
2872
2920
|
|
|
@@ -3001,6 +3049,11 @@ class EasyROB(QMainWindow):
|
|
|
3001
3049
|
selected_atoms=selected_atoms_for_aqme,
|
|
3002
3050
|
run_dir=run_dir
|
|
3003
3051
|
)
|
|
3052
|
+
self._write_atom_mapping_image(
|
|
3053
|
+
smarts=smarts,
|
|
3054
|
+
selected_atoms=selected_atoms_for_aqme,
|
|
3055
|
+
run_dir=run_dir
|
|
3056
|
+
)
|
|
3004
3057
|
|
|
3005
3058
|
# ------------------------------------------------
|
|
3006
3059
|
# Decide REAL input CSVs for AQME
|
|
@@ -316,12 +316,14 @@ class AQMETab(QWidget):
|
|
|
316
316
|
|
|
317
317
|
def _on_mcs_success(self, smarts):
|
|
318
318
|
"""Handle successful MCS detection."""
|
|
319
|
+
self.mcs_worker = None
|
|
319
320
|
self.smarts_targets.append(smarts)
|
|
320
321
|
self.mol_info_label.setText("🔬 Info here")
|
|
321
322
|
self.display_molecule()
|
|
322
323
|
|
|
323
324
|
def _on_mcs_error(self, message):
|
|
324
325
|
"""Handle MCS detection error."""
|
|
326
|
+
self.mcs_worker = None
|
|
325
327
|
self.set_mol_viewer_message(
|
|
326
328
|
message,
|
|
327
329
|
tooltip="SMARTS pattern detection failed."
|
|
@@ -330,6 +332,7 @@ class AQMETab(QWidget):
|
|
|
330
332
|
|
|
331
333
|
def _on_mcs_timeout(self):
|
|
332
334
|
"""Handle MCS detection timeout."""
|
|
335
|
+
self.mcs_worker = None
|
|
333
336
|
self.set_mol_viewer_message(
|
|
334
337
|
"⏱️ Timeout: MCS (Maximum Common Substructure) took too long and was aborted.",
|
|
335
338
|
tooltip="SMARTS pattern detection failed."
|
|
@@ -463,6 +466,21 @@ class AQMETab(QWidget):
|
|
|
463
466
|
# -------------------------------
|
|
464
467
|
# Launch MCS worker
|
|
465
468
|
# -------------------------------
|
|
469
|
+
existing_worker = getattr(self, "mcs_worker", None)
|
|
470
|
+
if existing_worker is not None:
|
|
471
|
+
try:
|
|
472
|
+
existing_worker.finished.disconnect(self._on_mcs_success)
|
|
473
|
+
except Exception:
|
|
474
|
+
pass
|
|
475
|
+
try:
|
|
476
|
+
existing_worker.error.disconnect(self._on_mcs_error)
|
|
477
|
+
except Exception:
|
|
478
|
+
pass
|
|
479
|
+
try:
|
|
480
|
+
existing_worker.timeout.disconnect(self._on_mcs_timeout)
|
|
481
|
+
except Exception:
|
|
482
|
+
pass
|
|
483
|
+
|
|
466
484
|
self.mcs_worker = MCSProcessWorker(
|
|
467
485
|
smiles_list,
|
|
468
486
|
timeout_ms=60000
|
|
@@ -554,30 +572,16 @@ class AQMETab(QWidget):
|
|
|
554
572
|
else:
|
|
555
573
|
highlight_atoms = set(self.selected_atoms)
|
|
556
574
|
|
|
557
|
-
|
|
558
|
-
{idx: (0.698, 0.4, 1.0) for idx in highlight_atoms}
|
|
559
|
-
if highlight_atoms else {}
|
|
560
|
-
)
|
|
561
|
-
|
|
562
|
-
drawer = rdMolDraw2D.MolDraw2DCairo(
|
|
563
|
-
self.molecule_image_width,
|
|
564
|
-
self.molecule_image_height
|
|
565
|
-
)
|
|
566
|
-
drawer.drawOptions().bondLineWidth = 1.5
|
|
567
|
-
drawer.DrawMolecule(
|
|
575
|
+
png_bytes, atom_coords = self._draw_pattern_molecule(
|
|
568
576
|
self.mol,
|
|
569
|
-
|
|
570
|
-
|
|
577
|
+
highlight_atoms=highlight_atoms,
|
|
578
|
+
selected_atoms=self.selected_atoms,
|
|
579
|
+
width=self.molecule_image_width,
|
|
580
|
+
height=self.molecule_image_height,
|
|
571
581
|
)
|
|
572
|
-
drawer.FinishDrawing()
|
|
573
|
-
|
|
574
|
-
png_bytes = drawer.GetDrawingText()
|
|
575
582
|
pixmap = QPixmap()
|
|
576
583
|
pixmap.loadFromData(png_bytes)
|
|
577
|
-
self.atom_coords =
|
|
578
|
-
drawer.GetDrawCoords(i)
|
|
579
|
-
for i in range(self.mol.GetNumAtoms())
|
|
580
|
-
]
|
|
584
|
+
self.atom_coords = atom_coords
|
|
581
585
|
|
|
582
586
|
if self.mol_viewer:
|
|
583
587
|
if pixmap.isNull():
|
|
@@ -618,6 +622,100 @@ class AQMETab(QWidget):
|
|
|
618
622
|
)
|
|
619
623
|
self.mol_info_label.setText("🔬 Info here")
|
|
620
624
|
|
|
625
|
+
def _prepare_pattern_molecule_for_drawing(self, pattern_mol, selected_atoms=None):
|
|
626
|
+
"""Return a copy of the SMARTS pattern with visible atom-order labels."""
|
|
627
|
+
|
|
628
|
+
mol_for_draw = Chem.Mol(pattern_mol)
|
|
629
|
+
selected_atoms = list(selected_atoms or [])
|
|
630
|
+
|
|
631
|
+
for atom in mol_for_draw.GetAtoms():
|
|
632
|
+
if atom.HasProp("atomNote"):
|
|
633
|
+
atom.ClearProp("atomNote")
|
|
634
|
+
|
|
635
|
+
for order, atom_idx in enumerate(selected_atoms, start=1):
|
|
636
|
+
if 0 <= atom_idx < mol_for_draw.GetNumAtoms():
|
|
637
|
+
mol_for_draw.GetAtomWithIdx(atom_idx).SetProp(
|
|
638
|
+
"atomNote",
|
|
639
|
+
str(order)
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
return mol_for_draw
|
|
643
|
+
|
|
644
|
+
def _draw_pattern_molecule(
|
|
645
|
+
self,
|
|
646
|
+
pattern_mol,
|
|
647
|
+
highlight_atoms,
|
|
648
|
+
selected_atoms,
|
|
649
|
+
width,
|
|
650
|
+
height,
|
|
651
|
+
):
|
|
652
|
+
"""Draw the SMARTS pattern with highlight and click-order numbering."""
|
|
653
|
+
|
|
654
|
+
highlight_atoms = set(highlight_atoms or [])
|
|
655
|
+
highlight_colors = (
|
|
656
|
+
{idx: (0.698, 0.4, 1.0) for idx in highlight_atoms}
|
|
657
|
+
if highlight_atoms else {}
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
draw_mol = self._prepare_pattern_molecule_for_drawing(
|
|
661
|
+
pattern_mol,
|
|
662
|
+
selected_atoms=selected_atoms,
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
drawer = rdMolDraw2D.MolDraw2DCairo(
|
|
666
|
+
max(1, int(width)),
|
|
667
|
+
max(1, int(height))
|
|
668
|
+
)
|
|
669
|
+
options = drawer.drawOptions()
|
|
670
|
+
options.bondLineWidth = 1.5
|
|
671
|
+
options.annotationFontScale = 1.0
|
|
672
|
+
drawer.DrawMolecule(
|
|
673
|
+
draw_mol,
|
|
674
|
+
highlightAtoms=list(highlight_atoms),
|
|
675
|
+
highlightAtomColors=highlight_colors
|
|
676
|
+
)
|
|
677
|
+
drawer.FinishDrawing()
|
|
678
|
+
|
|
679
|
+
png_bytes = drawer.GetDrawingText()
|
|
680
|
+
atom_coords = [
|
|
681
|
+
drawer.GetDrawCoords(i)
|
|
682
|
+
for i in range(draw_mol.GetNumAtoms())
|
|
683
|
+
]
|
|
684
|
+
return png_bytes, atom_coords
|
|
685
|
+
|
|
686
|
+
def save_atom_mapping_image(
|
|
687
|
+
self,
|
|
688
|
+
output_path,
|
|
689
|
+
smarts=None,
|
|
690
|
+
selected_atoms=None,
|
|
691
|
+
width=900,
|
|
692
|
+
height=700,
|
|
693
|
+
):
|
|
694
|
+
"""Save the numbered SMARTS render used for atom mapping as a PNG."""
|
|
695
|
+
|
|
696
|
+
smarts = smarts or (self.smarts_targets[0] if self.smarts_targets else None)
|
|
697
|
+
selected_atoms = list(self.selected_atoms if selected_atoms is None else selected_atoms)
|
|
698
|
+
|
|
699
|
+
if not smarts or not selected_atoms:
|
|
700
|
+
return None
|
|
701
|
+
|
|
702
|
+
pattern_mol = Chem.MolFromSmarts(smarts)
|
|
703
|
+
if pattern_mol is None:
|
|
704
|
+
raise ValueError("Invalid SMARTS pattern")
|
|
705
|
+
|
|
706
|
+
png_bytes, _ = self._draw_pattern_molecule(
|
|
707
|
+
pattern_mol,
|
|
708
|
+
highlight_atoms=set(selected_atoms),
|
|
709
|
+
selected_atoms=selected_atoms,
|
|
710
|
+
width=width,
|
|
711
|
+
height=height,
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
with open(output_path, "wb") as f:
|
|
715
|
+
f.write(png_bytes)
|
|
716
|
+
|
|
717
|
+
return output_path
|
|
718
|
+
|
|
621
719
|
def handle_atom_selection(self, atom_idx):
|
|
622
720
|
"""Handle the selection of an atom in the pattern."""
|
|
623
721
|
|
|
@@ -1102,4 +1200,4 @@ class AQMETab(QWidget):
|
|
|
1102
1200
|
save_button.clicked.connect(save_to_csv)
|
|
1103
1201
|
|
|
1104
1202
|
dialog.setLayout(layout)
|
|
1105
|
-
dialog.exec()
|
|
1203
|
+
dialog.exec()
|