Spaces:
Running
Running
import os | |
import json | |
import uuid | |
import gradio as gr | |
from gradio_moleculeview import moleculeview | |
import cellscape | |
def html_output(input_file): | |
with open(input_file, "r") as f: | |
svg = f.read().replace("<svg", "<svg id='svgElement'") | |
x = ( | |
"""<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>Document</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
#snackbar { | |
visibility: hidden; | |
color: #fff; | |
background-color: #333; | |
min-width: 250px; | |
margin-left: -125px; | |
border-radius: 2px; | |
padding: 16px; | |
text-align: center; | |
left: 50%; | |
bottom: 30px; | |
z-index: 1; | |
position: fixed; | |
} | |
/* This will be activated when the snackbar's class is 'show' which will be added through JS */ | |
#snackbar.show { | |
visibility: visible; | |
-webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; | |
animation: fadein 0.5s, fadeout 0.5s 2.5s; | |
} | |
/* Animations for fading in and out */ | |
@-webkit-keyframes fadein { | |
from {bottom: 0; opacity: 0;} | |
to {bottom: 30px; opacity: 1;} | |
} | |
@keyframes fadein { | |
from {bottom: 0; opacity: 0;} | |
to {bottom: 30px; opacity: 1;} | |
} | |
@-webkit-keyframes fadeout { | |
from {bottom: 30px; opacity: 1;} | |
to {bottom: 0; opacity: 0;} | |
} | |
@keyframes fadeout { | |
from {bottom: 30px; opacity: 1;} | |
to {bottom: 0; opacity: 0;} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="flex container mx-auto max-w-3xl"> | |
<div class="w-1/2"> | |
""" | |
+ svg | |
+ """ | |
</div> | |
<div class="w-1/2 space-y-4 justify-center items-center flex flex-col"> | |
<div class="w-3/4"> | |
<div | |
class="w-full flex space-x-2 items-center text-xl justify-center font-light mb-3" | |
> | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
class="w-4 h-4" | |
viewBox="0 0 16 16" | |
> | |
<path | |
fill="currentColor" | |
fill-rule="evenodd" | |
d="M6.091 5.194q.2-.322.305-.694h3.208c.259.916.98 1.637 1.896 1.896v3.208A2.76 2.76 0 0 0 9.604 11.5H6.396A2.76 2.76 0 0 0 4.5 9.604V6.396a2.76 2.76 0 0 0 1.591-1.202M13 6.396v3.208q.372.106.694.305a2.75 2.75 0 0 1 .897 3.785A2.751 2.751 0 0 1 9.603 13H6.397q-.106.372-.305.694A2.75 2.75 0 1 1 3 9.604V6.396A2.751 2.751 0 1 1 6.396 3h3.208A2.751 2.751 0 1 1 13 6.396M3.75 2.5a1.25 1.25 0 1 1 0 2.5a1.25 1.25 0 0 1 0-2.5M5 12.25a1.25 1.25 0 1 0-2.5 0a1.25 1.25 0 0 0 2.5 0M12.25 11a1.25 1.25 0 1 1 0 2.5a1.25 1.25 0 0 1 0-2.5m1.25-7.25a1.25 1.25 0 1 0-2.5 0a1.25 1.25 0 0 0 2.5 0" | |
clip-rule="evenodd" | |
/> | |
</svg> | |
<div>SVG</div> | |
</div> | |
<div class="w-full flex space-x-5"> | |
<button | |
id="copySvgBtn" | |
class="flex items-center justify-center space-x-2 w-full p-3 border border-gray-300 rounded bg-gray-100" | |
> | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
class="w-4 h-4" | |
viewBox="0 0 16 16" | |
> | |
<path | |
fill="currentColor" | |
fill-rule="evenodd" | |
d="M12 2.5H8A1.5 1.5 0 0 0 6.5 4v1H8a3 3 0 0 1 3 3v1.5h1A1.5 1.5 0 0 0 13.5 8V4A1.5 1.5 0 0 0 12 2.5M11 11h1a3 3 0 0 0 3-3V4a3 3 0 0 0-3-3H8a3 3 0 0 0-3 3v1H4a3 3 0 0 0-3 3v4a3 3 0 0 0 3 3h4a3 3 0 0 0 3-3zM4 6.5h4A1.5 1.5 0 0 1 9.5 8v4A1.5 1.5 0 0 1 8 13.5H4A1.5 1.5 0 0 1 2.5 12V8A1.5 1.5 0 0 1 4 6.5" | |
clip-rule="evenodd" | |
/> | |
</svg> | |
<div>Copy*</div> | |
</button> | |
<button | |
id="downloadSvgBtn" | |
class="w-full flex items-center justify-center space-x-2 p-3 border border-gray-300 rounded bg-gray-100" | |
> | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
class="h-4 w-4" | |
viewBox="0 0 16 16" | |
> | |
<path | |
fill="currentColor" | |
fill-rule="evenodd" | |
d="M6.5 6H4.029l3.499 4.276a.61.61 0 0 0 .944 0L11.971 6H9.5V2.75a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25zm1.498 7.5H1.75a.75.75 0 0 0 0 1.5h12.5a.75.75 0 0 0 0-1.5zm0-1.5a2.11 2.11 0 0 1-1.631-.774L2.833 6.907A1.474 1.474 0 0 1 3.973 4.5H5V2.75C5 1.784 5.784 1 6.75 1h2.5c.966 0 1.75.784 1.75 1.75V4.5h1.026a1.474 1.474 0 0 1 1.14 2.407l-3.533 4.319c-.4.49-1 .774-1.632.774H8" | |
clip-rule="evenodd" | |
/> | |
</svg> | |
<div>Download</div> | |
</button> | |
</div> | |
<div class="text-xs mt-3 text-center w-full"> | |
*Only works in in Adobe Illustrator. | |
</div> | |
<div | |
class="flex space-x-2 items-center text-xl justify-center font-light mb-3 mt-10" | |
> | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
class="w-4 h-4" | |
viewBox="0 0 16 16" | |
> | |
<path | |
fill="currentColor" | |
fill-rule="evenodd" | |
d="M11.5 3h-7A1.5 1.5 0 0 0 3 4.5v5.027l.962-.7a1.75 1.75 0 0 1 2.079.016l.928.696l2.368-2.03a1.75 1.75 0 0 1 2.325.043L13 8.787V4.5A1.5 1.5 0 0 0 11.5 3m3 7.498V4.5a3 3 0 0 0-3-3h-7a3 3 0 0 0-3 3v7a3 3 0 0 0 3 3h7a3 3 0 0 0 3-3zm-1.5.33l-2.355-2.174a.25.25 0 0 0-.332-.006L7.488 11.07l-.457.392l-.481-.361l-1.41-1.057a.25.25 0 0 0-.296-.002L3 11.381v.119A1.5 1.5 0 0 0 4.5 13h7a1.5 1.5 0 0 0 1.5-1.5zM7.5 6a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0" | |
clip-rule="evenodd" | |
/> | |
</svg> | |
<div>PNG</div> | |
</div> | |
<div class="flex space-x-5"> | |
<!-- Buttons for Download --> | |
<button | |
id="copyPngBtn" | |
class="w-full flex items-center justify-center space-x-2 p-3 border border-gray-300 rounded bg-gray-100" | |
> | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
class="w-4 h-4" | |
viewBox="0 0 16 16" | |
> | |
<path | |
fill="currentColor" | |
fill-rule="evenodd" | |
d="M12 2.5H8A1.5 1.5 0 0 0 6.5 4v1H8a3 3 0 0 1 3 3v1.5h1A1.5 1.5 0 0 0 13.5 8V4A1.5 1.5 0 0 0 12 2.5M11 11h1a3 3 0 0 0 3-3V4a3 3 0 0 0-3-3H8a3 3 0 0 0-3 3v1H4a3 3 0 0 0-3 3v4a3 3 0 0 0 3 3h4a3 3 0 0 0 3-3zM4 6.5h4A1.5 1.5 0 0 1 9.5 8v4A1.5 1.5 0 0 1 8 13.5H4A1.5 1.5 0 0 1 2.5 12V8A1.5 1.5 0 0 1 4 6.5" | |
clip-rule="evenodd" | |
/> | |
</svg> | |
<div>Copy</div> | |
</button> | |
<button | |
id="downloadPngBtn" | |
class="w-full flex items-center justify-center space-x-2 p-3 border border-gray-300 rounded bg-gray-100" | |
> | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
class="h-4 w-4" | |
viewBox="0 0 16 16" | |
> | |
<path | |
fill="currentColor" | |
fill-rule="evenodd" | |
d="M6.5 6H4.029l3.499 4.276a.61.61 0 0 0 .944 0L11.971 6H9.5V2.75a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25zm1.498 7.5H1.75a.75.75 0 0 0 0 1.5h12.5a.75.75 0 0 0 0-1.5zm0-1.5a2.11 2.11 0 0 1-1.631-.774L2.833 6.907A1.474 1.474 0 0 1 3.973 4.5H5V2.75C5 1.784 5.784 1 6.75 1h2.5c.966 0 1.75.784 1.75 1.75V4.5h1.026a1.474 1.474 0 0 1 1.14 2.407l-3.533 4.319c-.4.49-1 .774-1.632.774H8" | |
clip-rule="evenodd" | |
/> | |
</svg> | |
<div>Download</div> | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<span id="snackbar">Copied to clipboard</span> | |
<script type="text/javascript"> | |
function copySvgToClipboard() { | |
const svgElement = document.getElementById('svgElement'); | |
const svgData = new XMLSerializer().serializeToString(svgElement); | |
// Copy the serialized SVG text to the clipboard | |
navigator.clipboard.writeText(svgData).then(() => { | |
var sb = document.getElementById("snackbar"); | |
sb.className = "show"; | |
setTimeout(()=>{ sb.className = sb.className.replace("show", ""); }, 3000); | |
}).catch(err => { | |
console.error("Could not copy SVG text to clipboard: ", err); | |
}); | |
} | |
// Function to convert SVG to PNG and copy to clipboard | |
function copyPngToClipboard() { | |
const svgElement = document.getElementById('svgElement'); | |
const svgData = new XMLSerializer().serializeToString(svgElement); | |
const canvas = document.createElement('canvas'); | |
const ctx = canvas.getContext('2d'); | |
const img = new Image(); | |
img.onload = function () { | |
// Ensure canvas size matches the SVG | |
canvas.width = svgElement.clientWidth; | |
canvas.height = svgElement.clientHeight; | |
// Clear the canvas (transparent background) | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Draw the SVG image on the canvas | |
ctx.drawImage(img, 0, 0); | |
// Convert the canvas content to a PNG blob | |
canvas.toBlob(blob => { | |
const clipboardItem = [new ClipboardItem({ 'image/png': blob })]; | |
navigator.clipboard.write(clipboardItem).then(() => { | |
var sb = document.getElementById("snackbar"); | |
sb.className = "show"; | |
setTimeout(()=>{ sb.className = sb.className.replace("show", ""); }, 3000); | |
}).catch(err => { | |
console.error("Could not copy PNG to clipboard: ", err); | |
}); | |
}, 'image/png'); | |
}; | |
// Set the image source to the serialized SVG data | |
img.src = 'data:image/svg+xml;base64,' + btoa(svgData); | |
} | |
// Function to download SVG | |
function downloadSvg() { | |
const svgElement = document.getElementById('svgElement'); | |
const svgData = new XMLSerializer().serializeToString(svgElement); | |
const blob = new Blob([svgData], { type: 'image/svg+xml' }); | |
const url = URL.createObjectURL(blob); | |
const a = document.createElement('a'); | |
a.href = url; | |
a.download = 'image.svg'; | |
document.body.appendChild(a); | |
a.click(); | |
document.body.removeChild(a); | |
URL.revokeObjectURL(url); | |
} | |
// Function to download PNG | |
function downloadPng() { | |
const svgElement = document.getElementById('svgElement'); | |
const svgData = new XMLSerializer().serializeToString(svgElement); | |
const canvas = document.createElement('canvas'); | |
const ctx = canvas.getContext('2d'); | |
const img = new Image(); | |
img.onload = function () { | |
canvas.width = svgElement.clientWidth; | |
canvas.height = svgElement.clientHeight; | |
ctx.drawImage(img, 0, 0); | |
canvas.toBlob(blob => { | |
const url = URL.createObjectURL(blob); | |
const a = document.createElement('a'); | |
a.href = url; | |
a.download = 'image.png'; | |
document.body.appendChild(a); | |
a.click(); | |
document.body.removeChild(a); | |
URL.revokeObjectURL(url); | |
}, 'image/png'); | |
}; | |
img.src = 'data:image/svg+xml;base64,' + btoa(svgData); | |
} | |
// Button event listeners | |
document.getElementById('copySvgBtn').addEventListener('click', copySvgToClipboard); | |
document.getElementById('copyPngBtn').addEventListener('click', copyPngToClipboard); | |
document.getElementById('downloadSvgBtn').addEventListener('click', downloadSvg); | |
document.getElementById('downloadPngBtn').addEventListener('click', downloadPng); | |
</script> | |
</body></html>""" | |
) | |
x = x.replace('"', """) | |
x = x.replace("'", """) | |
return f"""<iframe style="width: 100%; height: 600px" name="result" allow="midi; geolocation; microphone; camera; | |
display-capture; encrypted-media;" sandbox="allow-modals allow-forms | |
allow-scripts allow-same-origin allow-popups | |
allow-top-navigation-by-user-activation allow-downloads" allowfullscreen="" | |
allowpaymentrequest="" frameborder="0" srcdoc='{x}'></iframe>""" | |
def predict(input_mol, style, contour_level, view_str, chains): | |
# write view to file | |
with open("view_matrix", "w") as f: | |
f.write(json.loads(view_str)) | |
chain_str = "" | |
chain_dict = json.loads(chains) | |
outputfile = os.path.basename(input_mol).replace(".pdb",".svg") | |
# sort keys in dict and add colors to chain_str | |
for chain in sorted(chain_dict.keys()): | |
chain_str += f" '{chain_dict[chain]}'" | |
if style == "Goodsell3D": | |
os.system(f"cellscape cartoon --pdb {input_mol.name} --outline residue --color_by chain --depth_shading --depth_lines --colors {chain_str} --depth flat --back_outline --view view_matrix --save {outputfile}") | |
elif style == "Contour": | |
os.system(f"cellscape cartoon --pdb {input_mol.name} --outline chain --color_by chain --depth_contour_interval {contour_level} --colors {chain_str} --depth contours --back_outline --view view_matrix --save {outputfile}") | |
else: | |
os.system(f"cellscape cartoon --pdb {input_mol.name} --outline chain --colors {chain_str} --depth flat --back_outline --view view_matrix --save {outputfile}") | |
#read content of file | |
os.system(f"inkscape {outputfile} --actions='select-all;path-simplify;export-plain-svg' --export-filename {outputfile.replace('.svg', '_opt.svg')}") | |
return html_output(outputfile.replace('.svg', '_opt.svg')) | |
def show_contour_level(style): | |
if style=="Contour": | |
return gr.Slider(minimum=1,maximum=50,step=1, value=10, label="Contour level", visible=True) | |
else: | |
return gr.Slider(minimum=1,maximum=50,step=1, value=10, label="Contour level", visible=False) | |
with gr.Blocks() as demo: | |
style = gr.Radio(value="Flat", choices=["Flat", "Contour", "Goodsell3D"], label="Style") | |
contour_level = gr.Slider(minimum=1,maximum=50,step=1, value=10, label="Contour level", visible=False) | |
style.change(show_contour_level, style, contour_level) | |
inp = moleculeview(label="Molecule3D") | |
view_str = gr.Textbox("viewMatrixResult", label="View Matrix", visible=False) | |
chains = gr.Textbox("chainsResult", label="Chains", visible=False) | |
hidden_style = gr.Textbox(visible=False) | |
timestamp = gr.Textbox(visible=False) | |
btn = gr.Button("Vectorize") | |
html = gr.HTML("") | |
btn.click(None, style, [view_str, chains, hidden_style, timestamp], js="(style) => [document.getElementById('viewMatrixResult').value, document.getElementById('chains').value, style, Date.now()]") # | |
timestamp.change(predict, [inp, style, contour_level, view_str, chains], [html]) | |
# on change of chains trigger, rendering | |
if __name__ == "__main__": | |
demo.launch() | |