Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 42 additions & 21 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def get_nearest_neighbors(self, smile, ms):

id_top = np.argmax(np.array(scores))

return id_top
return id_top, max(scores)

def extract_model_info(directory):
models_info = []
Expand Down Expand Up @@ -193,6 +193,24 @@ def predict():
all_predictions = {}
all_ads = {}
model_info_list = []
data_dict = {
"Model": {},
"Input": {'SMILES': {}, 'Structure': {}},
"Nearest Neighbor": {'Structure': {}, 'Similarity': {}, 'Source': {}, 'Experimental value': {}},
"Output": {'Predicted pChEMBL Value': {}, 'Within Applicability Domain': {}, 'Download QPRF': {}}
}
tooltip_dict = {"Model": 'Unique identifier of the model that made the prediction',
"Structure": '2D depiction of molecule',
"SMILES": 'Line representation of molecule',
"Nearest Neighbor": 'Molecule from the model training set that is the most similar to input molecule',
"Similarity": 'Tanimoto similarity score based on same chemical descriptor as used for model',
"Source": 'Document(s) containing experimental data for nearest neighbor',
"Experimental value": 'Average of all reported experimental values',
"Predicted pChEMBL Value": 'Model prediction for input molecule. pChEMBL is defined as -log(response). More information available in QMRF & QPRF',
"Within Applicability Domain": 'AD is based on descriptors of training set. An input molecule is within AD if the distance to the training set is lower than a set threshold. More information available in QMRF & QPRF',
"Download QPRF": 'Automatically filled in report about the prediction'
}

for model_name in model_names:
logging.debug(f"Processing model: {model_name}")
model_path = os.path.join(MODELS_DIR, model_name, f"{model_name}_meta.json")
Expand Down Expand Up @@ -244,19 +262,9 @@ def predict():
table_data.append(row)

# Update headers
table_data_extensive = []
headers = ['Structure', 'SMILES']
tooltips = ['2D depiction of input molecule', 'Line representation of input molecule']
headers_extensive = ['Model', 'Structure', 'SMILES', 'Nearest Neighbor', 'Source', 'Predicted pChEMBL Value', 'Within Applicability Domain']
tooltips_extensive = [
'Unique identifier of the model that made the prediction',
'2D depiction of input molecule',
'Line representation of input molecule',
'2D depiction of nearest neighbor of input molecule in model training set. More information available in QPRF',
'Document(s) containing experimental data for nearest neighbor',
'Model prediction for input molecule. pChEMBL is defined as -log(response). More information available in QMRF & QPRF',
'AD is based on descriptors of training set. An input molecule is within AD if the distance to the training set is lower than a set threshold. More information available in QMRF & QPRF',
]

searcher = SimilaritySearcher()
for model_name in model_names:
accession = model_name.split("_")[0]
Expand Down Expand Up @@ -288,7 +296,7 @@ def predict():

for i, smile in enumerate(smiles_list):
image_data = smiles_to_image(smile)
id_top = searcher.get_nearest_neighbors(smile, ms)
id_top, score = searcher.get_nearest_neighbors(smile, ms)
nearest_neighbor = {}
nn_smiles = train_df.iloc[id_top]['SMILES']
nearest_neighbor["smiles"] = nn_smiles
Expand All @@ -298,20 +306,33 @@ def predict():
nearest_neighbor["predicted_value"] = model.predictMols([nn_smiles])[0][0]
nearest_neighbor["similarity"] = f"Nearest neighbor was found using {searcher.scorer.__name__} based on {searcher.descgen.__class__.__name__}"
image_data_nn = smiles_to_image(nn_smiles)
if getattr(model, 'applicabilityDomain', None):
row = [model_name] + [image_data] + [smile] + [image_data_nn] + [nn_smiles] + [doi_nn] + [all_predictions[model_name][i]] + [all_ads[model_name][i]]
else:
row = [model_name] + [image_data] + [smile] + [image_data_nn] + [nn_smiles] + [doi_nn] + [all_predictions[model_name][i]]

table_data_extensive.append(row)

render_qprf(smile, model, predictions[i], ad[i], nearest_neighbor)

data_dict["Model"].setdefault("value", []).append(model_name)
data_dict["Input"]["Structure"].setdefault("image", []).append(image_data)
data_dict["Input"]["SMILES"].setdefault("value", []).append(smile)
data_dict["Nearest Neighbor"]["Structure"].setdefault("image", []).append(image_data_nn)
data_dict["Nearest Neighbor"]["Similarity"].setdefault("value", []).append(f"{score:.2f}")
data_dict["Nearest Neighbor"]["Source"].setdefault("value", []).append(doi_nn)
data_dict["Nearest Neighbor"]["Experimental value"].setdefault("value", []).append(nearest_neighbor["value"])
data_dict["Output"]["Predicted pChEMBL Value"].setdefault("value", []).append(all_predictions[model_name][i])
data_dict["Output"]["Within Applicability Domain"].setdefault("value", []).append(all_ads[model_name][i])
data_dict["Output"]["Download QPRF"].setdefault("url", []).append("hi")
error_message = None
if invalid_smiles:
error_message = f"Invalid SMILES, could not be processed: {', '.join(invalid_smiles)}" # Mention invalid SMILES in error message

return render_template('index.html', models=available_models, headers=headers, tooltips=tooltips, data=table_data, headers_extensive=headers_extensive, tooltips_extensive = tooltips_extensive, data_extensive=table_data_extensive, smiles_input=smiles_input, model_names=model_names, file_name=file_name, error=error_message)
return render_template('index.html',
models=available_models,
data_dict=data_dict,
headers=headers,
tooltips=tooltips,
data=table_data,
tooltips_extensive = tooltip_dict,
smiles_input=smiles_input,
model_names=model_names,
file_name=file_name,
error=error_message)
except Exception:
logging.exception("An error occurred while processing the request.")
return render_template('index.html', models=available_models, error="An error occurred while processing the request.")
Expand Down
10 changes: 10 additions & 0 deletions static/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ header {
}


.prediction-container {
max-width: 1200px;
margin: 20px auto;
padding: 20px;
background-color: white;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
}

.table-container {
max-height: 400px;
overflow-y: auto;
Expand Down
116 changes: 68 additions & 48 deletions templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
<script src="https://cdn.datatables.net/buttons/1.7.1/js/buttons.print.min.js"></script>
<style>
img {
max-width: 150px;
max-height: 150px;
max-width: 140px;
max-height: 140px;
object-fit: contain;
}
table.dataTable td, table.dataTable th {
text-align: center;
text-align: left;
}
</style>
<script>
Expand All @@ -36,8 +36,10 @@
'pdfHtml5',
'print'
],
scrollX: true,
autoWidth: false, // Prevent automatic column width adjustment
"initComplete": function (settings, json) {
$("#predictionsTable").wrap("<div style='overflow:auto; width:100%;position:relative;'></div>");
},
autoWidth: true, // Prevent automatic column width adjustment
columnDefs: [
{ targets: [1], visible: false},
{ targets: '_all', visible: true, width: '150px' } // Adjust this width as needed
Expand All @@ -60,10 +62,12 @@
'pdfHtml5',
'print'
],
scrollX: true,
"initComplete": function (settings, json) {
$("#predictionsTable2").wrap("<div style='overflow:auto; width:100%;position:relative;'></div>");
},
autoWidth: false, // Prevent automatic column width adjustment
columnDefs: [
{ targets: '_all', visible: true, width: '150px' } // Adjust this width as needed
{ targets: '_all', visible: true } // Adjust this width as needed
]

});
Expand Down Expand Up @@ -107,7 +111,7 @@ <h1>QSPRpred - pChEMBL Value Prediction</h1>

<div class="tab-content">
<div id="thyroid" class="tab-pane fade in active">
<label>MIE models:</label><sup><img src="/static/img/question-circle.svg" alt="Question circle" width="12" height="12" fill="currentColor" title="QSAR models for different endpoints. Download QMRF for model specific information"> </sup>
<label>MIE models:</label><sup><img src="/static/img/question-circle.svg" alt="Question circle" width="12" height="12" title="QSAR models for different endpoints. Download QMRF for model specific information"> </sup>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 10px;">
{% for model in models if model.case_study == 'thyroid' %}
<div class="model-tile {% if model.name in model_names %}checked{% endif %}">
Expand Down Expand Up @@ -158,7 +162,7 @@ <h1>QSPRpred - pChEMBL Value Prediction</h1>
<label for="smiles">Enter SMILES strings (comma-separated):</label><br>
<input type="text" id="smiles" name="smiles" value="{{ smiles_input }}" placeholder="Example: Cc1c(Cc2cc(I)c(OCC(=O)O)c(I)c2)c2c(cccc2)o1,O=c1cnn(-c2cc(Cl)c(Oc3ccc(O)c(S(=O)(=O)N4CCc5ccccc54)c3)c(Cl)c2)c(=O)[nH]1"><br><br>

<label for="file">or Upload CSV file with SMILES (with a column named <q>SMILES</q>):</label><sup><img src="/static/img/question-circle.svg" alt="Question circle" width="12" height="12" fill="currentColor" title="Case sensitive"> </sup><br>
<label for="file">or Upload CSV file with SMILES (with a column named <q>SMILES</q>):</label><sup><img src="/static/img/question-circle.svg" alt="Question circle" width="12" height="12" title="Case sensitive"> </sup><br>
<input type="file" id="file" name="file" accept=".csv"><br><br>

<input type="submit" value="Run">
Expand All @@ -171,7 +175,7 @@ <h1>QSPRpred - pChEMBL Value Prediction</h1>
<header>
<h2>Predictions</h2>
</header>
<div class="container">
<div class="prediction-container">
<ul class="nav nav-tabs">
<li class="active">
<a data-toggle="tab" href="#Output">Simple Output</a>
Expand All @@ -187,9 +191,9 @@ <h2>Predictions</h2>
<table id="predictionsTable" class="display">
<thead>
<tr>
<th>Structure <sup><img src="/static/img/question-circle.svg" alt="Question circle" width="12" height="12" fill="currentColor" title="2D depiction of input molecule"> </sup></th>
<th>Structure <sup><img src="/static/img/question-circle.svg" alt="Question circle" width="12" height="12" title="2D depiction of input molecule"> </sup></th>
{% for header in headers[1:] %}
<th>{{ header }} <sup><img src="/static/img/question-circle.svg" alt="Question circle" width="12" height="12" fill="currentColor" title= "{{ tooltips[loop.index0+1] }}" > </sup></th>
<th>{{ header }} <sup><img src="/static/img/question-circle.svg" alt="Question circle" width="12" height="12" title= "{{ tooltips[loop.index0+1] }}" > </sup></th>
{% endfor %}
</tr>
</thead>
Expand All @@ -215,45 +219,61 @@ <h2>Predictions</h2>


<div id="COutput" class="tab-pane fade">
<table id="predictionsTable2" class="display">
<thead>
<tr>
{% for header in headers_extensive %}
<th> {{ header }} <sup><img src="/static/img/question-circle.svg" alt="Question circle" width="12" height="12" fill="currentColor" title= "{{ tooltips_extensive[loop.index0] }}" > </sup></th>
{% endfor %}
<th>Download QPRF</th>
</tr>
</thead>
<tbody>
{% for row in data_extensive %}
<tr>
<td>{{ row[0] }}</td>
<td>
{% if row[1] %}
<img src="{{ row[1] }}" alt="Molecule Structure">
<table id="predictionsTable2" class="display">
<thead>
<tr>
{% for key in data_dict.keys() %}
{% if 'value' in data_dict[key].keys() %}
<th rowspan="2" width="15%">{{ key }}</th>
{% elif key == 'Nearest Neighbor' %}
<th colspan="{{ data_dict[key].keys()|length }}" scope="colgroup">{{ key }} <sup><img src="/static/img/question-circle.svg" alt="Question circle" width="12" height="12" title= "{{ tooltips_extensive[key] }}" > </sup></th>

{% else %}
<p>Invalid SMILES</p>

<th colspan="{{ data_dict[key].keys()|length }}" scope="colgroup">{{ key }}</th>
{% endif %}
{% endfor %}
</tr>
<tr>
{% for key in data_dict.keys() %}
{% if 'value' not in data_dict[key].keys() %}
{% for header in data_dict[key].keys() %}
<th> {{ header }} <sup><img src="/static/img/question-circle.svg" alt="Question circle" width="12" height="12" title= "{{ tooltips_extensive[header] }}" > </sup></th>
{% endfor %}
{% endif %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for i in range(data_dict["Model"]["value"]|length) %}
<tr>
{% for key in data_dict.keys() %}
{% if 'value' in data_dict[key].keys() %}
<td>{{ data_dict[key]['value'][i] }}</td>
{% else %}
{% for header in data_dict[key].keys() %}
{% if 'image' in data_dict[key][header].keys() %}
<td>
{% if data_dict[key][header]['image'][i] %}
<img src="{{ data_dict[key][header]['image'][i] }}" alt="Molecule Structure">
{% else %}
<p>Invalid SMILES</p>
{% endif %}
</td>
{% elif 'url' in data_dict[key][header].keys() %}
<td><a href="{{ url_for('download_qprf', model=data_dict['Model']['value'][i] , smile=data_dict['Input']['SMILES']['value'][i]) }}">Click here to download</a> </td>

{% else %}
<td>{{ data_dict[key][header]['value'][i] }}</td>
{% endif %}
{% endfor %}
{% endif %}
</td>
<td>{{ row[2] }}</td>
<td>
<a href="{{ row[5] }}"> <img src="{{ row[3] }}" alt="Molecule Structure Nearest Neighbor 2"> </a>
</td>
<td>
<a href="{{row[5]}}" target="_blank">{{row[5]}}</a>
</td>


{% for cell in row[6:] %}
<td>{{ cell }}</td>
{% endfor %}
</tr>
{% endfor %}
<td>
<a href="{{ url_for('download_qprf', model=row[0], smile=row[2]) }}">Click here to download</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</tbody>
</table>
</div>
{% endif %}

{% if error %}
Expand Down