decimer 2.7.1__tar.gz → 2.8.1__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.
- {decimer-2.7.1 → decimer-2.8.1}/DECIMER/Efficient_Net_encoder.py +0 -2
- decimer-2.8.1/DECIMER/__init__.py +38 -0
- decimer-2.8.1/DECIMER/config.py +148 -0
- decimer-2.8.1/DECIMER/decimer.py +313 -0
- decimer-2.7.1/DECIMER/config.py → decimer-2.8.1/DECIMER/pre_process.py +8 -189
- decimer-2.8.1/DECIMER/utils.py +143 -0
- decimer-2.8.1/PKG-INFO +249 -0
- decimer-2.8.1/README.md +208 -0
- decimer-2.8.1/decimer.egg-info/PKG-INFO +249 -0
- {decimer-2.7.1 → decimer-2.8.1}/decimer.egg-info/SOURCES.txt +5 -1
- {decimer-2.7.1 → decimer-2.8.1}/decimer.egg-info/requires.txt +1 -1
- decimer-2.8.1/pyproject.toml +3 -0
- {decimer-2.7.1 → decimer-2.8.1}/setup.py +7 -10
- decimer-2.8.1/tests/test_functions.py +30 -0
- decimer-2.8.1/tests/test_lazy_models.py +317 -0
- decimer-2.7.1/DECIMER/__init__.py +0 -29
- decimer-2.7.1/DECIMER/decimer.py +0 -172
- decimer-2.7.1/DECIMER/utils.py +0 -97
- decimer-2.7.1/PKG-INFO +0 -195
- decimer-2.7.1/README.md +0 -171
- decimer-2.7.1/decimer.egg-info/PKG-INFO +0 -195
- {decimer-2.7.1 → decimer-2.8.1}/DECIMER/DECIMER_EfficinetNetV2_Transfomer_Trainer.py +0 -0
- {decimer-2.7.1 → decimer-2.8.1}/DECIMER/Predictor_usingCheckpoints.py +0 -0
- {decimer-2.7.1 → decimer-2.8.1}/DECIMER/Repack_model.py +0 -0
- {decimer-2.7.1 → decimer-2.8.1}/DECIMER/Transformer_decoder.py +0 -0
- {decimer-2.7.1 → decimer-2.8.1}/DECIMER/Utils/Create_TFrecord_From_pickles.py +0 -0
- {decimer-2.7.1 → decimer-2.8.1}/DECIMER/Utils/Create_image_tokenizer.py +0 -0
- {decimer-2.7.1 → decimer-2.8.1}/DECIMER/efficientnetv2/effnetv2_configs.py +0 -0
- {decimer-2.7.1 → decimer-2.8.1}/DECIMER/efficientnetv2/effnetv2_model.py +0 -0
- {decimer-2.7.1 → decimer-2.8.1}/DECIMER/efficientnetv2/hparams.py +0 -0
- {decimer-2.7.1 → decimer-2.8.1}/DECIMER/efficientnetv2/utils.py +0 -0
- {decimer-2.7.1 → decimer-2.8.1}/LICENSE +0 -0
- {decimer-2.7.1 → decimer-2.8.1}/decimer.egg-info/dependency_links.txt +0 -0
- {decimer-2.7.1 → decimer-2.8.1}/decimer.egg-info/entry_points.txt +0 -0
- {decimer-2.7.1 → decimer-2.8.1}/decimer.egg-info/top_level.txt +0 -0
- {decimer-2.7.1 → decimer-2.8.1}/setup.cfg +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
DECIMER V2.7.2 Python Package
|
|
4
|
+
=============================
|
|
5
|
+
|
|
6
|
+
Deep lEarning for Chemical ImagE Recognition (DECIMER) project
|
|
7
|
+
was launched to address the Optical Chemical Structure Recognition (OCSR)
|
|
8
|
+
problem using deep learning based methods,
|
|
9
|
+
providing an automated open-source software solution.
|
|
10
|
+
|
|
11
|
+
Typical usage example::
|
|
12
|
+
|
|
13
|
+
from decimer import predict_SMILES
|
|
14
|
+
|
|
15
|
+
# Chemical depiction to SMILES translation
|
|
16
|
+
image_path = "path/to/imagefile"
|
|
17
|
+
SMILES = predict_SMILES(image_path)
|
|
18
|
+
print(SMILES)
|
|
19
|
+
|
|
20
|
+
Importing DECIMER does not download or load any model. The models are downloaded
|
|
21
|
+
(if they are missing) and loaded when predict_SMILES() is first called, or earlier
|
|
22
|
+
by calling ``load_models()``. They are stored in ``$PYSTOW_HOME/DECIMER-V2/``
|
|
23
|
+
(``~/.data/DECIMER-V2/`` by default); a directory that already holds them is used
|
|
24
|
+
without any network access.
|
|
25
|
+
|
|
26
|
+
For comments, bug reports, or feature ideas,
|
|
27
|
+
please raise an issue on the GitHub repository.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
__version__ = "2.8.1"
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"DECIMER",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
from .decimer import load_models
|
|
38
|
+
from .decimer import predict_SMILES
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import tensorflow as tf
|
|
2
|
+
|
|
3
|
+
import DECIMER.Efficient_Net_encoder as Efficient_Net_encoder
|
|
4
|
+
import DECIMER.Transformer_decoder as Transformer_decoder
|
|
5
|
+
|
|
6
|
+
TARGET_DTYPE = tf.float32
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Config:
|
|
10
|
+
"""Configuration class."""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
):
|
|
15
|
+
self.encoder_config = {}
|
|
16
|
+
self.transformer_config = {}
|
|
17
|
+
self.lr_config = {}
|
|
18
|
+
|
|
19
|
+
def initialize_encoder_config(
|
|
20
|
+
self,
|
|
21
|
+
image_embedding_dim,
|
|
22
|
+
preprocessing_fn,
|
|
23
|
+
backbone_fn,
|
|
24
|
+
image_shape,
|
|
25
|
+
do_permute=False,
|
|
26
|
+
pretrained_weights=None,
|
|
27
|
+
):
|
|
28
|
+
"""This functions initializes the Efficient-Net V2 encoder with user
|
|
29
|
+
defined configurations.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
image_embedding_dim (int): Embedding dimension of the input image
|
|
33
|
+
preprocessing_fn (method): Efficient Net preprocessing function for input image
|
|
34
|
+
backbone_fn (method): Calls Efficient-Net V2 as backbone for encoder
|
|
35
|
+
image_shape (int): Shape of the input image
|
|
36
|
+
do_permute (bool, optional): . Defaults to False.
|
|
37
|
+
pretrained_weights (keras weights, optional): Use pretrained efficient net weights or not. Defaults to None.
|
|
38
|
+
"""
|
|
39
|
+
self.encoder_config = dict(
|
|
40
|
+
image_embedding_dim=image_embedding_dim,
|
|
41
|
+
preprocessing_fn=preprocessing_fn,
|
|
42
|
+
backbone_fn=backbone_fn,
|
|
43
|
+
image_shape=image_shape,
|
|
44
|
+
do_permute=do_permute,
|
|
45
|
+
pretrained_weights=pretrained_weights,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def initialize_transformer_config(
|
|
49
|
+
self,
|
|
50
|
+
vocab_len,
|
|
51
|
+
max_len,
|
|
52
|
+
n_transformer_layers,
|
|
53
|
+
transformer_d_dff,
|
|
54
|
+
transformer_n_heads,
|
|
55
|
+
image_embedding_dim,
|
|
56
|
+
rate=0.1,
|
|
57
|
+
):
|
|
58
|
+
"""This functions initializes the Transformer model as decoder with
|
|
59
|
+
user defined configurations.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
vocab_len (int): Total number of words in the input vocabulary
|
|
63
|
+
max_len (int): Maximum length of the string found on the training dataset
|
|
64
|
+
n_transformer_layers (int): Number of layers present in the transformer model
|
|
65
|
+
transformer_d_dff (int): Transformer feed forward upwards projection size
|
|
66
|
+
transformer_n_heads (int): Number of heads present in the transformer model
|
|
67
|
+
image_embedding_dim (int): Total number of dimension the image gets embedded
|
|
68
|
+
dropout_rate (float, optional): Fraction of the input units to drop. Defaults to 0.1.
|
|
69
|
+
"""
|
|
70
|
+
self.transformer_config = dict(
|
|
71
|
+
num_layers=n_transformer_layers,
|
|
72
|
+
d_model=image_embedding_dim,
|
|
73
|
+
num_heads=transformer_n_heads,
|
|
74
|
+
dff=transformer_d_dff,
|
|
75
|
+
target_vocab_size=vocab_len,
|
|
76
|
+
max_len=max_len,
|
|
77
|
+
rate=0.1,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def initialize_lr_config(self, warm_steps, n_epochs):
|
|
81
|
+
"""This function sets the configuration to initialize learning rate.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
warm_steps (int): Number of steps The learning rate is increased
|
|
85
|
+
n_epochs (int): Number of epochs
|
|
86
|
+
"""
|
|
87
|
+
self.lr_config = dict(
|
|
88
|
+
warm_steps=warm_steps,
|
|
89
|
+
n_epochs=n_epochs,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class CustomSchedule(tf.keras.optimizers.schedules.LearningRateSchedule):
|
|
94
|
+
"""Custom schedule for learning rate used during training.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
tf (_type_): keras learning rate schedule
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(self, d_model, warmup_steps=4000):
|
|
101
|
+
super(CustomSchedule, self).__init__()
|
|
102
|
+
|
|
103
|
+
self.d_model = d_model
|
|
104
|
+
self.d_model = tf.cast(self.d_model, tf.float32)
|
|
105
|
+
|
|
106
|
+
self.warmup_steps = warmup_steps
|
|
107
|
+
|
|
108
|
+
def __call__(self, step):
|
|
109
|
+
arg1 = tf.math.rsqrt(step)
|
|
110
|
+
arg2 = step * (self.warmup_steps**-1.5)
|
|
111
|
+
|
|
112
|
+
return tf.math.rsqrt(self.d_model) * tf.math.minimum(arg1, arg2)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def prepare_models(encoder_config, transformer_config, replica_batch_size, verbose=0):
|
|
116
|
+
"""This function is used to initiate the Encoder and the Transformer with
|
|
117
|
+
appropriate configs set by the user. After initiating the models this
|
|
118
|
+
function returns the Encoder,Transformer and the optimizer.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
encoder_config ([type]): Encoder configuration set by user in the config class.
|
|
122
|
+
transformer_config ([type]): Transformer configuration set by user in the config class.
|
|
123
|
+
replica_batch_size ([type]): Per replica batch size set by user(during distributed training).
|
|
124
|
+
verbose (int, optional): Defaults to 0.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
[type]: Optimizer, Encoder model and the Transformer
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
# Instantiate an optimizer
|
|
131
|
+
optimizer = tf.keras.optimizers.Adam(learning_rate=0.00051)
|
|
132
|
+
|
|
133
|
+
# Instantiate the encoder model
|
|
134
|
+
|
|
135
|
+
encoder = Efficient_Net_encoder.Encoder(**encoder_config)
|
|
136
|
+
|
|
137
|
+
# Instantiate the decoder model
|
|
138
|
+
transformer = Transformer_decoder.Decoder(**transformer_config)
|
|
139
|
+
|
|
140
|
+
# Show the model architectures and plot the learning rate
|
|
141
|
+
if verbose:
|
|
142
|
+
print("\nEncoder model\n")
|
|
143
|
+
print(encoder.summary())
|
|
144
|
+
|
|
145
|
+
print("\nTransformer model\n")
|
|
146
|
+
print(transformer.summary())
|
|
147
|
+
|
|
148
|
+
return optimizer, encoder, transformer
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import pickle
|
|
4
|
+
import sys
|
|
5
|
+
import threading
|
|
6
|
+
from typing import List
|
|
7
|
+
from typing import Tuple
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pystow
|
|
11
|
+
import tensorflow as tf
|
|
12
|
+
|
|
13
|
+
import DECIMER.pre_process as pre_process
|
|
14
|
+
import DECIMER.utils as utils
|
|
15
|
+
|
|
16
|
+
# Silence tensorflow model loading warnings.
|
|
17
|
+
logging.getLogger("absl").setLevel("ERROR")
|
|
18
|
+
|
|
19
|
+
# Silence tensorflow errors - not recommended if your model is not working properly.
|
|
20
|
+
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ============================================================================
|
|
24
|
+
# Keras 2 to Keras 3 Compatibility Layer
|
|
25
|
+
# ============================================================================
|
|
26
|
+
class KerasCompatibilityUnpickler(pickle.Unpickler):
|
|
27
|
+
"""Custom unpickler to handle Keras 2.x tokenizers in Keras 3 environment.
|
|
28
|
+
|
|
29
|
+
This handles the module path changes between Keras 2 and Keras 3:
|
|
30
|
+
- keras.preprocessing.text -> tensorflow.keras.preprocessing.text
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def find_class(self, module, name):
|
|
34
|
+
# Redirect old Keras 2.x imports to TensorFlow Keras
|
|
35
|
+
if module.startswith("keras."):
|
|
36
|
+
module = module.replace("keras.", "tensorflow.keras.")
|
|
37
|
+
return super().find_class(module, name)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def load_tokenizer(tokenizer_path: str) -> object:
|
|
41
|
+
"""Load tokenizer with Keras 2/3 compatibility.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
tokenizer_path (str): Path to the pickled tokenizer file
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
tokenizer: Loaded tokenizer object
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
# Try standard pickle load first (for Keras 3 tokenizers)
|
|
51
|
+
with open(tokenizer_path, "rb") as f:
|
|
52
|
+
return pickle.load(f)
|
|
53
|
+
except ModuleNotFoundError as e:
|
|
54
|
+
if "keras.preprocessing" in str(e):
|
|
55
|
+
# Use compatibility unpickler for Keras 2.x tokenizers
|
|
56
|
+
with open(tokenizer_path, "rb") as f:
|
|
57
|
+
return KerasCompatibilityUnpickler(f).load()
|
|
58
|
+
else:
|
|
59
|
+
raise
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# Set the absolute path
|
|
63
|
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
64
|
+
|
|
65
|
+
# Set model to run on default GPU and allow memory to grow as much as needed.
|
|
66
|
+
# This allows us to run multiple instances of inference in the same GPU.
|
|
67
|
+
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
|
|
68
|
+
gpus = tf.config.experimental.list_physical_devices("GPU")
|
|
69
|
+
for gpu in gpus:
|
|
70
|
+
tf.config.experimental.set_memory_growth(gpu, True)
|
|
71
|
+
|
|
72
|
+
# The models are NOT downloaded or loaded when this module is imported. They are
|
|
73
|
+
# downloaded (if missing) and loaded the first time they are needed, either by the
|
|
74
|
+
# first predict_SMILES() call or by an explicit load_models(), and cached afterwards.
|
|
75
|
+
# They live in $PYSTOW_HOME/DECIMER-V2/ (PYSTOW_HOME defaults to ~/.data), read when
|
|
76
|
+
# the models are first needed. A directory that already holds them is used as it is,
|
|
77
|
+
# without any network access.
|
|
78
|
+
model_urls = {
|
|
79
|
+
"DECIMER": "https://zenodo.org/record/8300489/files/models.zip",
|
|
80
|
+
"DECIMER_HandDrawn": "https://zenodo.org/records/10781330/files/DECIMER_HandDrawn_model.zip",
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _get_default_path():
|
|
85
|
+
"""Directory that holds (or will hold) the downloaded models.
|
|
86
|
+
|
|
87
|
+
Looked up when the models are first needed, so that PYSTOW_HOME can still be set
|
|
88
|
+
after DECIMER was imported. Nothing is created here.
|
|
89
|
+
"""
|
|
90
|
+
return pystow.join("DECIMER-V2", ensure_exists=False)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_models(model_urls: dict):
|
|
94
|
+
"""Download and load models from the provided URLs.
|
|
95
|
+
|
|
96
|
+
This function downloads models from the provided URLs to a default location,
|
|
97
|
+
then loads tokenizers and TensorFlow saved models. It loads everything at once
|
|
98
|
+
and does not touch the cached models of predict_SMILES(); most callers want
|
|
99
|
+
load_models() instead.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
model_urls (dict): A dictionary containing model names as keys and their corresponding URLs as values.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
tuple: A tuple containing loaded tokenizer and TensorFlow saved models.
|
|
106
|
+
- tokenizer (object): Tokenizer for DECIMER model.
|
|
107
|
+
- DECIMER_V2 (tf.saved_model): TensorFlow saved model for DECIMER.
|
|
108
|
+
- DECIMER_Hand_drawn (tf.saved_model): TensorFlow saved model for DECIMER HandDrawn.
|
|
109
|
+
"""
|
|
110
|
+
# Download models to a default location
|
|
111
|
+
model_paths = utils.ensure_models(
|
|
112
|
+
default_path=_get_default_path(), model_urls=model_urls
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Load tokenizers
|
|
116
|
+
tokenizer_path = os.path.join(
|
|
117
|
+
model_paths["DECIMER"], "assets", "tokenizer_SMILES.pkl"
|
|
118
|
+
)
|
|
119
|
+
tokenizer = load_tokenizer(tokenizer_path)
|
|
120
|
+
|
|
121
|
+
# Load DECIMER models
|
|
122
|
+
DECIMER_V2 = tf.saved_model.load(model_paths["DECIMER"])
|
|
123
|
+
DECIMER_Hand_drawn = tf.saved_model.load(model_paths["DECIMER_HandDrawn"])
|
|
124
|
+
|
|
125
|
+
return tokenizer, DECIMER_V2, DECIMER_Hand_drawn
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _load_tokenizer():
|
|
129
|
+
"""Ensure the printed-structure model files are present and load its tokenizer.
|
|
130
|
+
|
|
131
|
+
The tokenizer of the printed-structure model decodes the output of both models.
|
|
132
|
+
"""
|
|
133
|
+
model_paths = utils.ensure_models(
|
|
134
|
+
default_path=_get_default_path(),
|
|
135
|
+
model_urls={"DECIMER": model_urls["DECIMER"]},
|
|
136
|
+
)
|
|
137
|
+
return load_tokenizer(
|
|
138
|
+
os.path.join(model_paths["DECIMER"], "assets", "tokenizer_SMILES.pkl")
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _load_saved_model(model_name: str):
|
|
143
|
+
"""Ensure the files of one model are present, then load its SavedModel."""
|
|
144
|
+
model_paths = utils.ensure_models(
|
|
145
|
+
default_path=_get_default_path(),
|
|
146
|
+
model_urls={model_name: model_urls[model_name]},
|
|
147
|
+
)
|
|
148
|
+
return tf.saved_model.load(model_paths[model_name])
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# Module-level names that are filled on first use. Once loaded, an object is stored
|
|
152
|
+
# in this module's globals under the same name that decimer 2.8.0 and older bound at
|
|
153
|
+
# import, so `from DECIMER.decimer import tokenizer, DECIMER_V2` keeps working.
|
|
154
|
+
_lazy_loaders = {
|
|
155
|
+
"tokenizer": _load_tokenizer,
|
|
156
|
+
"DECIMER_V2": lambda: _load_saved_model("DECIMER"),
|
|
157
|
+
"DECIMER_Hand_drawn": lambda: _load_saved_model("DECIMER_HandDrawn"),
|
|
158
|
+
}
|
|
159
|
+
_load_lock = threading.RLock()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _lazy(name: str):
|
|
163
|
+
"""Return tokenizer, DECIMER_V2 or DECIMER_Hand_drawn, loading it on first use.
|
|
164
|
+
|
|
165
|
+
Downloads the files first if they are missing. The lock makes concurrent first
|
|
166
|
+
calls download and load once. A failed download or load is not cached, so the
|
|
167
|
+
next call tries again.
|
|
168
|
+
"""
|
|
169
|
+
try:
|
|
170
|
+
return globals()[name]
|
|
171
|
+
except KeyError:
|
|
172
|
+
pass
|
|
173
|
+
with _load_lock:
|
|
174
|
+
if name not in globals():
|
|
175
|
+
globals()[name] = _lazy_loaders[name]()
|
|
176
|
+
return globals()[name]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def __getattr__(name: str):
|
|
180
|
+
"""Load tokenizer, DECIMER_V2 and DECIMER_Hand_drawn on first attribute access."""
|
|
181
|
+
if name == "default_path":
|
|
182
|
+
return _get_default_path()
|
|
183
|
+
if name in _lazy_loaders:
|
|
184
|
+
return _lazy(name)
|
|
185
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def load_models(printed: bool = True, hand_drawn: bool = False) -> None:
|
|
189
|
+
"""Download (if missing) and load the models now, instead of at the first prediction.
|
|
190
|
+
|
|
191
|
+
Call this once at start-up if the first predict_SMILES() call should not pay for
|
|
192
|
+
the download and the loading, for example in a server or when building an image.
|
|
193
|
+
Models that are already loaded are not loaded again. The tokenizer, which both
|
|
194
|
+
models need, is always loaded.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
printed (bool): Load the model for printed chemical structures (the default
|
|
198
|
+
model of predict_SMILES).
|
|
199
|
+
hand_drawn (bool): Also load the hand-drawn model. load_models(hand_drawn=True)
|
|
200
|
+
loads everything that decimer 2.8.0 and older loaded on import.
|
|
201
|
+
"""
|
|
202
|
+
with _load_lock:
|
|
203
|
+
_lazy("tokenizer")
|
|
204
|
+
if printed:
|
|
205
|
+
_lazy("DECIMER_V2")
|
|
206
|
+
if hand_drawn:
|
|
207
|
+
_lazy("DECIMER_Hand_drawn")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def detokenize_output(predicted_array: int) -> str:
|
|
211
|
+
"""This function takes the predicted tokens from the DECIMER model and
|
|
212
|
+
returns the decoded SMILES string.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
predicted_array (int): Predicted tokens from DECIMER
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
(str): SMILES representation of the molecule
|
|
219
|
+
"""
|
|
220
|
+
index_word = _lazy("tokenizer").index_word
|
|
221
|
+
outputs = [index_word[i] for i in predicted_array[0].numpy()]
|
|
222
|
+
prediction = (
|
|
223
|
+
"".join([str(elem) for elem in outputs])
|
|
224
|
+
.replace("<start>", "")
|
|
225
|
+
.replace("<end>", "")
|
|
226
|
+
)
|
|
227
|
+
return prediction
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def detokenize_output_add_confidence(
|
|
231
|
+
predicted_array: tf.Tensor,
|
|
232
|
+
confidence_array: tf.Tensor,
|
|
233
|
+
) -> List[Tuple[str, float]]:
|
|
234
|
+
"""This function takes the predicted array of tokens as well as the
|
|
235
|
+
confidence values returned by the Transformer Decoder and returns a list of
|
|
236
|
+
tuples that contain each token of the predicted SMILES string and the
|
|
237
|
+
confidence value.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
predicted_array (tf.Tensor): Transformer Decoder output array (predicted tokens)
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
str: SMILES string
|
|
244
|
+
"""
|
|
245
|
+
index_word = _lazy("tokenizer").index_word
|
|
246
|
+
prediction_with_confidence = [
|
|
247
|
+
(
|
|
248
|
+
index_word[predicted_array[0].numpy()[i]],
|
|
249
|
+
confidence_array[i].numpy(),
|
|
250
|
+
)
|
|
251
|
+
for i in range(len(confidence_array))
|
|
252
|
+
]
|
|
253
|
+
# remove start and end tokens
|
|
254
|
+
prediction_with_confidence_ = prediction_with_confidence[1:-1]
|
|
255
|
+
|
|
256
|
+
decoded_prediction_with_confidence = list(
|
|
257
|
+
[(utils.decoder(tok), conf) for tok, conf in prediction_with_confidence_]
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
return decoded_prediction_with_confidence
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def predict_SMILES(
|
|
264
|
+
image_input: [str, np.ndarray], confidence: bool = False, hand_drawn: bool = False
|
|
265
|
+
) -> str:
|
|
266
|
+
"""Predicts SMILES representation of a molecule depicted in the given image.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
image_input (str or np.ndarray): Path of chemical structure depiction image or a numpy array representing the image.
|
|
270
|
+
confidence (bool): Flag to indicate whether to return confidence values along with SMILES prediction.
|
|
271
|
+
hand_drawn (bool): Flag to indicate whether the molecule in the image is hand-drawn.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
str: SMILES representation of the molecule in the input image, optionally with confidence values.
|
|
275
|
+
"""
|
|
276
|
+
chemical_structure = pre_process.decode_image(image_input)
|
|
277
|
+
|
|
278
|
+
# The first call downloads the models that are missing and loads the ones it needs
|
|
279
|
+
# (the tokenizer, and the printed or the hand-drawn model); later calls reuse them.
|
|
280
|
+
_lazy("tokenizer")
|
|
281
|
+
model = _lazy("DECIMER_Hand_drawn" if hand_drawn else "DECIMER_V2")
|
|
282
|
+
predicted_tokens, confidence_values = model(tf.constant(chemical_structure))
|
|
283
|
+
|
|
284
|
+
predicted_SMILES = utils.decoder(detokenize_output(predicted_tokens))
|
|
285
|
+
|
|
286
|
+
if confidence:
|
|
287
|
+
predicted_SMILES_with_confidence = detokenize_output_add_confidence(
|
|
288
|
+
predicted_tokens, confidence_values
|
|
289
|
+
)
|
|
290
|
+
return predicted_SMILES, predicted_SMILES_with_confidence
|
|
291
|
+
|
|
292
|
+
return predicted_SMILES
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def main():
|
|
296
|
+
"""This function take the path of the image as user input and returns the
|
|
297
|
+
predicted SMILES as output in CLI.
|
|
298
|
+
|
|
299
|
+
Agrs:
|
|
300
|
+
str: image_path
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
str: predicted SMILES
|
|
304
|
+
"""
|
|
305
|
+
if len(sys.argv) != 2:
|
|
306
|
+
print("Usage: {} $image_path".format(sys.argv[0]))
|
|
307
|
+
else:
|
|
308
|
+
SMILES = predict_SMILES(sys.argv[1])
|
|
309
|
+
print(SMILES)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
if __name__ == "__main__":
|
|
313
|
+
main()
|