Skip to content

Commit 74bc7df

Browse files
authored
Merge pull request #824 from UPO-Sevilla-Fco-Javier-Lobo-Cabrera/add-protein-mosaic-q
Add protein_mosaic_q: Mosaic Q structural descriptor and 3D visualisation
2 parents 06f29da + 006531b commit 74bc7df

4 files changed

Lines changed: 1002 additions & 0 deletions

File tree

tools/protein_mosaic_q/.shed.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
categories: [Proteomics]
2+
description: Calculate the Mosaic Q descriptor and visualise amino acid clustering in protein structures
3+
homepage_url: https://proteins-mosaic-q.org
4+
long_description: |
5+
Proteins Mosaic Q calculates the Q and Q_alt structural descriptors
6+
for protein structures from the PDB and renders an interactive 3D
7+
visualisation coloured by amino acid chemical family using 3Dmol.js.
8+
name: protein_mosaic_q
9+
owner: galaxyp
10+
remote_repository_url: https://github.com/galaxyproteomics/tools-galaxyp/tree/master/tools/protein_mosaic_q
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import argparse
2+
import shutil
3+
import tempfile
4+
import urllib.request
5+
from pathlib import Path
6+
7+
8+
try:
9+
from mosaicq import calculate_q, calculate_q_alt
10+
MOSAICQ_AVAILABLE = True
11+
except ImportError:
12+
MOSAICQ_AVAILABLE = False
13+
14+
# No .format() used — placeholders are replaced with .replace()
15+
# This avoids conflicts between Python's {key} syntax and JavaScript's {key: value}
16+
HTML_TEMPLATE = """<!DOCTYPE html>
17+
<html>
18+
<head>
19+
<meta charset="utf-8">
20+
<title>Proteins Mosaic Q. __PDB_ID__</title>
21+
<script src="https://3dmol.csb.pitt.edu/build/3Dmol-min.js"></script>
22+
<style>
23+
body { font-family: 'Segoe UI', sans-serif; margin: 20px; background: #fafafa; }
24+
h2 { color: #E05A00; }
25+
.metrics { background: white; border-radius: 8px; padding: 16px;
26+
display: inline-block; margin: 12px 0;
27+
box-shadow: 0 1px 4px rgba(0,0,0,0.1); }
28+
table { border-collapse: collapse; }
29+
td, th { padding: 8px 16px; border: 1px solid #ddd; }
30+
th { background: #f5f5f5; }
31+
.legend { display: flex; flex-wrap: wrap; gap: 10px;
32+
font-size: 12px; margin: 10px 0; }
33+
.dot { width: 12px; height: 12px; border-radius: 50%;
34+
display: inline-block; border: 1px solid #ccc; }
35+
#viewer { width: 600px; height: 500px; position: relative;
36+
border-radius: 8px; border: 1px solid #eee; }
37+
</style>
38+
</head>
39+
<body>
40+
<h2>&#x1F920; Proteins Mosaic Q __PDB_ID__</h2>
41+
42+
<div class="metrics">
43+
<table>
44+
<tr><th>Descriptor</th><th>Value</th></tr>
45+
<tr><td><b>Q</b></td><td>__Q__</td></tr>
46+
<tr><td><b>Q_alt</b></td><td>__Q_ALT__</td></tr>
47+
</table>
48+
</div>
49+
50+
<div class="legend">
51+
<span><span class="dot" style="background:#fff;"></span> Hydrophobic</span>
52+
<span><span class="dot" style="background:#4CAF7D;"></span> Polar</span>
53+
<span><span class="dot" style="background:#E8863A;"></span> Acidic</span>
54+
<span><span class="dot" style="background:#5B8DD9;"></span> Basic</span>
55+
<span><span class="dot" style="background:#4DD9D9;"></span> Special</span>
56+
</div>
57+
58+
<div id="viewer"></div>
59+
60+
<script>
61+
var pdbData = `__PDB_CONTENT__`;
62+
63+
var viewer = $3Dmol.createViewer(document.getElementById('viewer'), {
64+
backgroundColor: 'white'
65+
});
66+
67+
viewer.addModel(pdbData, 'pdb');
68+
69+
var colorGroups = [
70+
{residues: ['ALA','VAL','ILE','LEU','MET','PHE','TYR','TRP'], color: 'white'},
71+
{residues: ['SER','THR','ASN','GLN'], color: 'green'},
72+
{residues: ['ASP','GLU'], color: 'orange'},
73+
{residues: ['ARG','HIS','LYS'], color: 'blue'},
74+
{residues: ['CYS','SEC','GLY','PRO'], color: 'cyan'},
75+
];
76+
77+
colorGroups.forEach(function(group) {
78+
group.residues.forEach(function(res) {
79+
viewer.setStyle({resn: res, hetflag: false}, {sphere: {color: group.color}});
80+
});
81+
});
82+
83+
viewer.zoomTo({hetflag: false});
84+
viewer.render();
85+
</script>
86+
87+
<p style="font-size:0.85rem; color:#888; margin-top:20px;">
88+
<a href="https://proteins-mosaic-q.org" target="_blank">proteins-mosaic-q.org</a>
89+
</p>
90+
</body>
91+
</html>"""
92+
93+
94+
def main():
95+
parser = argparse.ArgumentParser()
96+
parser.add_argument("--pdb_id", required=False, default=None)
97+
parser.add_argument("--pdb_file", required=False, default=None)
98+
parser.add_argument("--output", required=True)
99+
args = parser.parse_args()
100+
101+
# Determine label and source
102+
if args.pdb_file:
103+
pdb_label = args.pdb_id.strip().upper() if args.pdb_id else "Uploaded structure"
104+
elif args.pdb_id:
105+
pdb_label = args.pdb_id.strip().upper()
106+
else:
107+
pdb_label = "UNKNOWN"
108+
109+
q, q_alt = "N/A", "N/A"
110+
pdb_content = ""
111+
try:
112+
if args.pdb_file:
113+
# Galaxy passes files with a UUID filename — copy to .pdb so mosaicq recognises it
114+
with tempfile.TemporaryDirectory() as tmpdir:
115+
tmp_pdb = Path(tmpdir) / "structure.pdb"
116+
shutil.copy(args.pdb_file, str(tmp_pdb))
117+
pdb_content = tmp_pdb.read_text()
118+
if MOSAICQ_AVAILABLE:
119+
q = f"{calculate_q(str(tmp_pdb)):.4f}"
120+
q_alt = f"{calculate_q_alt(str(tmp_pdb)):.4f}"
121+
elif args.pdb_id:
122+
# Download from RCSB
123+
pdb_id = args.pdb_id.strip().upper()
124+
with tempfile.TemporaryDirectory() as tmpdir:
125+
url = f"https://files.rcsb.org/download/{pdb_id}.pdb"
126+
filepath = Path(tmpdir) / f"{pdb_id}.pdb"
127+
try:
128+
urllib.request.urlretrieve(url, str(filepath))
129+
except Exception as download_err:
130+
raise RuntimeError(f"Could not download {pdb_id}: {download_err}")
131+
if filepath.exists():
132+
pdb_content = filepath.read_text()
133+
if MOSAICQ_AVAILABLE:
134+
q = f"{calculate_q(str(filepath)):.4f}"
135+
q_alt = f"{calculate_q_alt(str(filepath)):.4f}"
136+
else:
137+
raise RuntimeError("Please provide either a PDB ID or a PDB file.")
138+
except Exception as e:
139+
q, q_alt = f"Error: {e}", "N/A"
140+
141+
# Escape backticks and ${ for JS template literal
142+
pdb_content_js = pdb_content.replace('`', '\\`').replace('${', '\\${')
143+
144+
# Use .replace() for all substitutions — no .format() needed,
145+
# so JavaScript { } syntax requires no escaping whatsoever
146+
html = HTML_TEMPLATE
147+
html = html.replace("__PDB_ID__", pdb_label)
148+
html = html.replace("__Q__", str(q))
149+
html = html.replace("__Q_ALT__", str(q_alt))
150+
html = html.replace("__PDB_CONTENT__", pdb_content_js)
151+
152+
with open(args.output, "w", encoding="utf-8") as f:
153+
f.write(html)
154+
155+
156+
if __name__ == "__main__":
157+
main()
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<tool id="protein_mosaic_q" name="Proteins Mosaic Q" version="0.3.3+galaxy0" profile="24.1">
2+
<description>Calculate Mosaic Q descriptors and visualise amino acid clustering</description>
3+
<requirements>
4+
<requirement type="package" version="0.3.3">protein-mosaic-q</requirement>
5+
</requirements>
6+
<command detect_errors="exit_code"><![CDATA[
7+
python '$__tool_directory__/protein_mosaic_q.py'
8+
#if $input_type.source == "pdb_id"
9+
--pdb_id '$input_type.pdb_id'
10+
#else
11+
--pdb_file '$input_type.pdb_file'
12+
#end if
13+
--output '$output'
14+
]]></command>
15+
<inputs>
16+
<conditional name="input_type">
17+
<param name="source" type="select" label="Input source">
18+
<option value="pdb_id">PDB ID</option>
19+
<option value="pdb_file">PDB file</option>
20+
</param>
21+
<when value="pdb_id">
22+
<param name="pdb_id" type="text" label="PDB ID"
23+
help="4-character PDB identifier, e.g. 1CRN, 4HHB, 1TIM"
24+
value="1CRN">
25+
<validator type="regex" message="PDB IDs are 4 alphanumeric characters">^[A-Za-z0-9]{4}$</validator>
26+
</param>
27+
</when>
28+
<when value="pdb_file">
29+
<param name="pdb_file" type="data" format="pdb"
30+
label="PDB file"
31+
help="PDB file from Galaxy history. Useful for artificial or modified structures not in the PDB."/>
32+
</when>
33+
</conditional>
34+
</inputs>
35+
<outputs>
36+
<data name="output" format="html" label="Mosaic Q visualisation"/>
37+
</outputs>
38+
<tests>
39+
<test>
40+
<conditional name="input_type">
41+
<param name="source" value="pdb_id"/>
42+
<param name="pdb_id" value="1CRN"/>
43+
</conditional>
44+
<output name="output" file="1CRN_expected.html" compare="contains"
45+
lines_diff="10">
46+
<assert_contents>
47+
<has_text text="Proteins Mosaic Q"/>
48+
<has_text text="1CRN"/>
49+
<has_text text="3Dmol"/>
50+
</assert_contents>
51+
</output>
52+
</test>
53+
</tests>
54+
<creator>
55+
<organization name="Proteins Mosaic Q Project"
56+
url="https://proteins-mosaic-q.org"/>
57+
<person givenName="Francisco Javier"
58+
familyName="Lobo Cabrera"
59+
url="https://github.com/UPO-Sevilla-Fco-Javier-Lobo-Cabrera"/>
60+
</creator>
61+
<help><![CDATA[
62+
63+
**Proteins Mosaic Q**
64+
65+
Calculates the Mosaic Q and Q_alt structural descriptors for a protein from the
66+
Protein Data Bank and renders an interactive 3D visualisation coloured
67+
by amino acid chemical family.
68+
69+
-----
70+
71+
**Input**
72+
73+
Either a 4-character PDB ID (e.g. ``1CRN``, ``4HHB``, ``1TIM``) fetched
74+
automatically from the RCSB Protein Data Bank, or a PDB file already
75+
present in the Galaxy history (useful for artificial or modified structures
76+
not available in the PDB).
77+
78+
-----
79+
80+
**Output**
81+
82+
An HTML page showing:
83+
84+
- Interactive 3D structure coloured by chemical family (spacefill/VDW representation)
85+
- Q and Q_alt descriptor values
86+
- Colour legend
87+
88+
-----
89+
90+
**Chemical family colouring**
91+
92+
+-----------------+----------------------------------------------+
93+
| Colour | Residues |
94+
+=================+==============================================+
95+
| White | Hydrophobic: Ala, Val, Ile, Leu, Met, |
96+
| | Phe, Tyr, Trp |
97+
+-----------------+----------------------------------------------+
98+
| Green | Polar: Ser, Thr, Asn, Gln |
99+
+-----------------+----------------------------------------------+
100+
| Orange | Acidic: Asp, Glu |
101+
+-----------------+----------------------------------------------+
102+
| Blue | Basic: Arg, His, Lys |
103+
+-----------------+----------------------------------------------+
104+
| Cyan | Special: Cys, Sec, Gly, Pro |
105+
+-----------------+----------------------------------------------+
106+
107+
-----
108+
109+
**The Mosaic Q descriptor**
110+
111+
Big-data analysis of the >160,000 protein structures in the Protein Data
112+
Bank points to an apparently conserved structural trait: the presence of
113+
particular amino acid 3D clustering pattern, where along with the existence
114+
of the hydrophobic core, amino acids tend to cluster in space according to
115+
their chemical family in groups of approximately 8 residues,
116+
forming a characteristic mosaic-like pattern (**Mosaic Q**).
117+
118+
This clustering is captured by the **Q descriptor**, defined as a sum of
119+
inverse inter-residue distances within each chemical family. Q follows a
120+
precise empirical equation (R² = 0.979) that depends only on the total
121+
number of residues. **Q_alt** extends this to include special residues
122+
(Cys, Sec, Gly, Pro).
123+
124+
-----
125+
126+
**How to participate**
127+
128+
Your observation contributes to a growing community-based repository
129+
of evidence. After running the tool:
130+
131+
1. Inspect the rendered structure — do amino acids of the same chemical
132+
type cluster together in groups of approximately the same size and shape?
133+
2. Save a screenshot.
134+
3. Email your image to protmsq@qmosaic.org with the PDB ID and your name.
135+
Your image will appear in the repository and you will receive a
136+
certificate of contribution.
137+
138+
More information at https://proteins-mosaic-q.org
139+
140+
- Preprint: https://www.biorxiv.org/content/10.1101/500025v1.full
141+
- Updated manuscript: https://github.com/UPO-Sevilla-Fco-Javier-Lobo-Cabrera/clustering_trait_proteins/blob/main/Manuscript_and_Supplementary_Materials_v4.pdf
142+
143+
]]></help>
144+
<citations>
145+
<citation type="doi">10.1101/500025</citation>
146+
</citations>
147+
</tool>
148+

0 commit comments

Comments
 (0)