Spaces:
Runtime error
Runtime error
| """ | |
| DNA-Diffusion Gradio Application with Integrated 3D Viewer | |
| Combines Gradio interface with HTML-based 3D molecular visualization | |
| """ | |
| import gradio as gr | |
| import logging | |
| import json | |
| import os | |
| from typing import Dict, Any, Tuple, List | |
| import time | |
| import random | |
| import numpy as np | |
| from datetime import datetime | |
| import urllib.parse | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') | |
| logger = logging.getLogger(__name__) | |
| # Import spaces and handle ZeroGPU | |
| try: | |
| import spaces | |
| SPACES_AVAILABLE = True | |
| logger.info("Spaces module loaded successfully") | |
| except ImportError: | |
| SPACES_AVAILABLE = False | |
| logger.warning("Spaces module not available - running without GPU") | |
| # Create dummy decorator | |
| class spaces: | |
| def GPU(duration=60): | |
| def decorator(func): | |
| return func | |
| return decorator | |
| # DNA Model and genetic code table (same as before) | |
| CODON_TABLE = { | |
| 'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L', | |
| 'TCT': 'S', 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', | |
| 'TAT': 'Y', 'TAC': 'Y', 'TAA': '*', 'TAG': '*', | |
| 'TGT': 'C', 'TGC': 'C', 'TGA': '*', 'TGG': 'W', | |
| 'CTT': 'L', 'CTC': 'L', 'CTA': 'L', 'CTG': 'L', | |
| 'CCT': 'P', 'CCC': 'P', 'CCA': 'P', 'CCG': 'P', | |
| 'CAT': 'H', 'CAC': 'H', 'CAA': 'Q', 'CAG': 'Q', | |
| 'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R', | |
| 'ATT': 'I', 'ATC': 'I', 'ATA': 'I', 'ATG': 'M', | |
| 'ACT': 'T', 'ACC': 'T', 'ACA': 'T', 'ACG': 'T', | |
| 'AAT': 'N', 'AAC': 'N', 'AAA': 'K', 'AAG': 'K', | |
| 'AGT': 'S', 'AGC': 'S', 'AGA': 'R', 'AGG': 'R', | |
| 'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V', | |
| 'GCT': 'A', 'GCC': 'A', 'GCA': 'A', 'GCG': 'A', | |
| 'GAT': 'D', 'GAC': 'D', 'GAA': 'E', 'GAG': 'E', | |
| 'GGT': 'G', 'GGC': 'G', 'GGA': 'G', 'GGG': 'G' | |
| } | |
| class DNAModel: | |
| """Mock DNA generation model""" | |
| def generate(self, cell_type: str, num_sequences: int = 1, length: int = 200, guidance_scale: float = 1.0): | |
| sequences = [] | |
| for _ in range(num_sequences): | |
| sequence = ''.join(random.choice(['A', 'T', 'C', 'G']) for _ in range(length)) | |
| if cell_type == "K562": | |
| for i in range(0, length-8, 50): | |
| sequence = sequence[:i] + 'GCGCGCGC' + sequence[i+8:] | |
| elif cell_type == "GM12878": | |
| for i in range(10, length-8, 60): | |
| sequence = sequence[:i] + 'ATATATAT' + sequence[i+8:] | |
| elif cell_type == "HepG2": | |
| for i in range(20, length-12, 70): | |
| sequence = sequence[:i] + 'GCGATCGATCGC' + sequence[i+12:] | |
| sequences.append(sequence) | |
| return sequences[0] if num_sequences == 1 else sequences | |
| model = DNAModel() | |
| class DNADiffusionApp: | |
| def __init__(self): | |
| self.current_sequence = "" | |
| self.current_analysis = {} | |
| self.generation_count = 0 | |
| def generate_with_gpu(self, cell_type: str, guidance_scale: float = 1.0, use_enhanced: bool = True): | |
| logger.info(f"Generating sequence on GPU for cell type: {cell_type}") | |
| try: | |
| time.sleep(2) | |
| sequence = model.generate(cell_type, length=200, guidance_scale=guidance_scale) | |
| if use_enhanced: | |
| sequence = self.enhance_sequence(sequence, cell_type) | |
| self.generation_count += 1 | |
| logger.info(f"Successfully generated sequence #{self.generation_count}") | |
| return sequence | |
| except Exception as e: | |
| logger.error(f"GPU generation failed: {e}") | |
| raise | |
| def enhance_sequence(self, sequence: str, cell_type: str) -> str: | |
| enhancers = { | |
| "K562": "GGGACTTTCC", | |
| "GM12878": "TGACGTCA", | |
| "HepG2": "TGTTGGTGG" | |
| } | |
| if cell_type in enhancers: | |
| pos = len(sequence) // 4 | |
| enhancer = enhancers[cell_type] | |
| sequence = sequence[:pos] + enhancer + sequence[pos+len(enhancer):] | |
| return sequence | |
| def analyze_sequence(self, sequence: str) -> Dict[str, Any]: | |
| if not sequence: | |
| return {} | |
| gc_count = sequence.count('G') + sequence.count('C') | |
| gc_content = (gc_count / len(sequence)) * 100 | |
| if len(sequence) < 14: | |
| tm = 4 * (sequence.count('G') + sequence.count('C')) + 2 * (sequence.count('A') + sequence.count('T')) | |
| else: | |
| tm = 81.5 + 0.41 * gc_content - 675 / len(sequence) | |
| restriction_sites = {} | |
| enzymes = { | |
| 'EcoRI': 'GAATTC', | |
| 'BamHI': 'GGATCC', | |
| 'HindIII': 'AAGCTT', | |
| 'PstI': 'CTGCAG', | |
| 'XbaI': 'TCTAGA' | |
| } | |
| for enzyme, pattern in enzymes.items(): | |
| positions = [] | |
| for i in range(len(sequence) - len(pattern) + 1): | |
| if sequence[i:i+len(pattern)] == pattern: | |
| positions.append(i) | |
| if positions: | |
| restriction_sites[enzyme] = positions | |
| protein = self.translate_to_protein(sequence) | |
| return { | |
| 'length': len(sequence), | |
| 'gc_content': round(gc_content, 1), | |
| 'melting_temp': round(tm, 1), | |
| 'restriction_sites': restriction_sites, | |
| 'protein_length': len(protein), | |
| 'protein_sequence': protein[:50] + '...' if len(protein) > 50 else protein | |
| } | |
| def translate_to_protein(self, dna_sequence: str) -> str: | |
| protein = [] | |
| start_pos = dna_sequence.find('ATG') | |
| if start_pos == -1: | |
| start_pos = 0 | |
| for i in range(start_pos, len(dna_sequence) - 2, 3): | |
| codon = dna_sequence[i:i+3] | |
| if len(codon) == 3: | |
| amino_acid = CODON_TABLE.get(codon, 'X') | |
| if amino_acid == '*': | |
| break | |
| protein.append(amino_acid) | |
| return ''.join(protein) | |
| app = DNADiffusionApp() | |
| def load_html_file(filename): | |
| """Safely load and prepare HTML file for iframe embedding""" | |
| try: | |
| with open(filename, 'r', encoding='utf-8') as f: | |
| content = f.read() | |
| # Properly encode content for data URI | |
| encoded_content = urllib.parse.quote(content, safe='') | |
| return f'<iframe src="data:text/html;charset=utf-8,{encoded_content}" width="100%" height="800px" style="border: none; border-radius: 10px;"></iframe>' | |
| except FileNotFoundError: | |
| return f''' | |
| <div style=" | |
| padding: 60px 40px; | |
| text-align: center; | |
| background: linear-gradient(135deg, rgba(253, 121, 168, 0.1), rgba(162, 155, 254, 0.1)); | |
| border: 2px dashed #a29bfe; | |
| border-radius: 20px; | |
| margin: 20px; | |
| "> | |
| <h3 style="color: #5f3dc4; font-size: 1.5em; margin-bottom: 10px;"> | |
| 📁 File Not Found | |
| </h3> | |
| <p style="color: #6c5ce7; font-size: 1.1em;"> | |
| {filename} not found in the current directory. | |
| </p> | |
| <p style="color: #74b9ff; margin-top: 20px;"> | |
| Please place the file in the same directory as app.py | |
| </p> | |
| </div> | |
| ''' | |
| except Exception as e: | |
| return f''' | |
| <div style=" | |
| padding: 60px 40px; | |
| text-align: center; | |
| background: linear-gradient(135deg, rgba(253, 121, 168, 0.1), rgba(253, 203, 110, 0.1)); | |
| border: 2px dashed #fd79a8; | |
| border-radius: 20px; | |
| margin: 20px; | |
| "> | |
| <h3 style="color: #e17055; font-size: 1.5em; margin-bottom: 10px;"> | |
| ❌ Error Loading File | |
| </h3> | |
| <p style="color: #d63031; font-size: 1.1em;"> | |
| {str(e)} | |
| </p> | |
| </div> | |
| ''' | |
| # HTML for 3D Viewer | |
| HTML_3D_VIEWER = """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <style> | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| background: linear-gradient(135deg, #ffeaa7 0%, #fab1a0 25%, #a29bfe 50%, #74b9ff 75%, #81ecec 100%); | |
| font-family: Arial, sans-serif; | |
| color: #2d3436; | |
| height: 100vh; | |
| overflow: hidden; | |
| } | |
| #viewer-container { | |
| width: 100%; | |
| height: 100%; | |
| position: relative; | |
| } | |
| #canvas3d { | |
| width: 100%; | |
| height: 100%; | |
| } | |
| .controls-panel { | |
| position: absolute; | |
| top: 20px; | |
| right: 20px; | |
| background: rgba(255, 255, 255, 0.9); | |
| backdrop-filter: blur(10px); | |
| padding: 20px; | |
| border-radius: 15px; | |
| box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); | |
| max-width: 300px; | |
| } | |
| .controls-panel h3 { | |
| color: #5f3dc4; | |
| margin-top: 0; | |
| font-weight: 600; | |
| } | |
| .control-btn { | |
| background: linear-gradient(135deg, #74b9ff, #a29bfe); | |
| color: #fff; | |
| border: none; | |
| padding: 10px 18px; | |
| margin: 5px; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-weight: 600; | |
| box-shadow: 0 4px 6px rgba(162, 155, 254, 0.3); | |
| transition: all 0.3s ease; | |
| } | |
| .control-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 12px rgba(162, 155, 254, 0.4); | |
| } | |
| .info-display { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 20px; | |
| background: rgba(255, 255, 255, 0.9); | |
| backdrop-filter: blur(10px); | |
| padding: 15px 20px; | |
| border-radius: 12px; | |
| box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); | |
| } | |
| #sequence-display { | |
| font-family: monospace; | |
| color: #5f3dc4; | |
| word-break: break-all; | |
| margin-top: 10px; | |
| background: rgba(162, 155, 254, 0.1); | |
| padding: 10px; | |
| border-radius: 8px; | |
| border: 1px solid rgba(162, 155, 254, 0.3); | |
| } | |
| strong { | |
| color: #2d3436; | |
| font-weight: 600; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="viewer-container"> | |
| <canvas id="canvas3d"></canvas> | |
| <div class="controls-panel"> | |
| <h3>🎨 3D View Controls</h3> | |
| <button class="control-btn" onclick="setViewMode('cartoon')">Cartoon</button> | |
| <button class="control-btn" onclick="setViewMode('stick')">Stick</button> | |
| <button class="control-btn" onclick="setViewMode('sphere')">Sphere</button> | |
| <button class="control-btn" onclick="toggleRotation()">Toggle Rotation</button> | |
| <button class="control-btn" onclick="resetView()">Reset View</button> | |
| </div> | |
| <div class="info-display"> | |
| <strong>Current Sequence:</strong> | |
| <div id="sequence-display">No sequence loaded</div> | |
| </div> | |
| </div> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script> | |
| let scene, camera, renderer; | |
| let moleculeGroup; | |
| let currentSequence = ''; | |
| let autoRotate = true; | |
| let viewMode = 'cartoon'; | |
| function init() { | |
| scene = new THREE.Scene(); | |
| // Soft white background with subtle gradient | |
| scene.background = new THREE.Color(0xffffff); | |
| scene.fog = new THREE.Fog(0xffffff, 50, 200); | |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
| camera.position.z = 50; | |
| renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('canvas3d'), antialias: true, alpha: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.setClearColor(0xffffff, 0.5); | |
| const ambientLight = new THREE.AmbientLight(0xffffff, 0.8); | |
| scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6); | |
| directionalLight.position.set(50, 50, 50); | |
| directionalLight.castShadow = true; | |
| scene.add(directionalLight); | |
| const directionalLight2 = new THREE.DirectionalLight(0xa29bfe, 0.3); | |
| directionalLight2.position.set(-50, 50, -50); | |
| scene.add(directionalLight2); | |
| moleculeGroup = new THREE.Group(); | |
| scene.add(moleculeGroup); | |
| animate(); | |
| // Listen for sequence updates from parent | |
| window.addEventListener('message', function(e) { | |
| if (e.data.type === 'updateSequence') { | |
| updateSequence(e.data.sequence); | |
| } | |
| }); | |
| } | |
| function updateSequence(sequence) { | |
| currentSequence = sequence; | |
| document.getElementById('sequence-display').textContent = sequence; | |
| generateDNAStructure(sequence); | |
| } | |
| function generateDNAStructure(sequence) { | |
| moleculeGroup.clear(); | |
| const radius = 10; | |
| const rise = 3.4; | |
| const basesPerTurn = 10; | |
| const anglePerBase = (2 * Math.PI) / basesPerTurn; | |
| // Create double helix | |
| const curve1Points = []; | |
| const curve2Points = []; | |
| for (let i = 0; i < sequence.length; i++) { | |
| const angle = i * anglePerBase; | |
| const height = i * rise / basesPerTurn; | |
| curve1Points.push(new THREE.Vector3( | |
| radius * Math.cos(angle), | |
| height, | |
| radius * Math.sin(angle) | |
| )); | |
| curve2Points.push(new THREE.Vector3( | |
| radius * Math.cos(angle + Math.PI), | |
| height, | |
| radius * Math.sin(angle + Math.PI) | |
| )); | |
| } | |
| // Create backbone curves | |
| const curve1 = new THREE.CatmullRomCurve3(curve1Points); | |
| const curve2 = new THREE.CatmullRomCurve3(curve2Points); | |
| const tubeGeometry1 = new THREE.TubeGeometry(curve1, 100, 0.5, 8, false); | |
| const tubeGeometry2 = new THREE.TubeGeometry(curve2, 100, 0.5, 8, false); | |
| const material1 = new THREE.MeshPhongMaterial({ | |
| color: 0x74b9ff, | |
| shininess: 100, | |
| specular: 0xffffff | |
| }); | |
| const material2 = new THREE.MeshPhongMaterial({ | |
| color: 0xa29bfe, | |
| shininess: 100, | |
| specular: 0xffffff | |
| }); | |
| moleculeGroup.add(new THREE.Mesh(tubeGeometry1, material1)); | |
| moleculeGroup.add(new THREE.Mesh(tubeGeometry2, material2)); | |
| // Add base pairs | |
| for (let i = 0; i < Math.min(sequence.length, 50); i++) { | |
| const p1 = curve1Points[i]; | |
| const p2 = curve2Points[i]; | |
| const direction = new THREE.Vector3().subVectors(p2, p1); | |
| const distance = direction.length(); | |
| direction.normalize(); | |
| const geometry = new THREE.CylinderGeometry(0.3, 0.3, distance, 8); | |
| const material = new THREE.MeshPhongMaterial({ | |
| color: getBaseColor(sequence[i]), | |
| shininess: 100, | |
| specular: 0xffffff | |
| }); | |
| const cylinder = new THREE.Mesh(geometry, material); | |
| cylinder.position.copy(p1).add(direction.multiplyScalar(distance / 2)); | |
| cylinder.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction); | |
| moleculeGroup.add(cylinder); | |
| } | |
| // Center the molecule | |
| const box = new THREE.Box3().setFromObject(moleculeGroup); | |
| const center = box.getCenter(new THREE.Vector3()); | |
| moleculeGroup.position.sub(center); | |
| } | |
| function getBaseColor(base) { | |
| const colors = { | |
| 'A': 0xfd79a8, // Soft pink | |
| 'T': 0x81ecec, // Soft turquoise | |
| 'G': 0xfdcb6e, // Soft yellow | |
| 'C': 0xa29bfe // Soft purple | |
| }; | |
| return colors[base] || 0x74b9ff; | |
| } | |
| function setViewMode(mode) { | |
| viewMode = mode; | |
| if (currentSequence) { | |
| generateDNAStructure(currentSequence); | |
| } | |
| } | |
| function toggleRotation() { | |
| autoRotate = !autoRotate; | |
| } | |
| function resetView() { | |
| camera.position.set(0, 0, 50); | |
| moleculeGroup.rotation.set(0, 0, 0); | |
| } | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| if (autoRotate) { | |
| moleculeGroup.rotation.y += 0.01; | |
| } | |
| renderer.render(scene, camera); | |
| } | |
| // Mouse controls | |
| let isDragging = false; | |
| let previousMousePosition = { x: 0, y: 0 }; | |
| document.addEventListener('mousedown', function(e) { | |
| isDragging = true; | |
| }); | |
| document.addEventListener('mouseup', function(e) { | |
| isDragging = false; | |
| }); | |
| document.addEventListener('mousemove', function(e) { | |
| if (isDragging) { | |
| const deltaMove = { | |
| x: e.clientX - previousMousePosition.x, | |
| y: e.clientY - previousMousePosition.y | |
| }; | |
| moleculeGroup.rotation.y += deltaMove.x * 0.01; | |
| moleculeGroup.rotation.x += deltaMove.y * 0.01; | |
| } | |
| previousMousePosition = { | |
| x: e.clientX, | |
| y: e.clientY | |
| }; | |
| }); | |
| document.addEventListener('wheel', function(e) { | |
| camera.position.z += e.deltaY * 0.1; | |
| camera.position.z = Math.max(10, Math.min(200, camera.position.z)); | |
| }); | |
| window.addEventListener('resize', function() { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| // Initialize | |
| init(); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| def create_demo(): | |
| """Create the Gradio interface with integrated 3D viewer""" | |
| css = """ | |
| .gradio-container { | |
| font-family: 'Arial', sans-serif; | |
| background: linear-gradient(135deg, #ffeaa7 0%, #fab1a0 25%, #a29bfe 50%, #74b9ff 75%, #81ecec 100%); | |
| min-height: 100vh; | |
| } | |
| /* Alternative pastel gradients - uncomment to use */ | |
| /* Soft pink to purple: background: linear-gradient(135deg, #ffeef8 0%, #f8e1ff 25%, #e1e8ff 50%, #d4f1ff 75%, #c8f7f7 100%); */ | |
| /* Peach to lavender: background: linear-gradient(135deg, #fff5e6 0%, #ffe6f2 25%, #f0e6ff 50%, #e6f0ff 75%, #e6fff9 100%); */ | |
| /* Warm sunset: background: linear-gradient(135deg, #fff9e6 0%, #ffede6 25%, #ffe6ee 50%, #f2e6ff 75%, #e6f3ff 100%); */ | |
| .sequence-box { | |
| font-family: 'Courier New', monospace; | |
| background-color: rgba(255, 255, 255, 0.8); | |
| color: #2d3436; | |
| padding: 15px; | |
| border-radius: 8px; | |
| border: 2px solid #74b9ff; | |
| backdrop-filter: blur(10px); | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| iframe { | |
| border: none; | |
| border-radius: 10px; | |
| box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); | |
| background: rgba(255, 255, 255, 0.9); | |
| } | |
| .gr-button-primary { | |
| background: linear-gradient(135deg, #74b9ff, #a29bfe) !important; | |
| border: none !important; | |
| color: #fff !important; | |
| font-weight: 600 !important; | |
| box-shadow: 0 4px 6px rgba(162, 155, 254, 0.3) !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .gr-button-primary:hover { | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 6px 12px rgba(162, 155, 254, 0.4) !important; | |
| } | |
| .gr-button-secondary { | |
| background: linear-gradient(135deg, #fd79a8, #fdcb6e) !important; | |
| border: none !important; | |
| color: #fff !important; | |
| font-weight: 600 !important; | |
| box-shadow: 0 4px 6px rgba(253, 121, 168, 0.3) !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .gr-button-secondary:hover { | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 6px 12px rgba(253, 121, 168, 0.4) !important; | |
| } | |
| /* Card styling */ | |
| .gr-box { | |
| background: rgba(255, 255, 255, 0.7) !important; | |
| backdrop-filter: blur(10px) !important; | |
| border: 1px solid rgba(255, 255, 255, 0.8) !important; | |
| } | |
| /* Tab styling */ | |
| .gr-button.gr-button-sm { | |
| background: rgba(255, 255, 255, 0.6) !important; | |
| color: #2d3436 !important; | |
| border: 1px solid rgba(255, 255, 255, 0.8) !important; | |
| } | |
| .gr-button.gr-button-sm.selected { | |
| background: linear-gradient(135deg, #74b9ff, #a29bfe) !important; | |
| color: #fff !important; | |
| border: none !important; | |
| } | |
| /* Input fields */ | |
| .gr-input, .gr-dropdown { | |
| background: rgba(255, 255, 255, 0.8) !important; | |
| border: 2px solid #dfe6e9 !important; | |
| color: #2d3436 !important; | |
| } | |
| .gr-input:focus, .gr-dropdown:focus { | |
| border-color: #74b9ff !important; | |
| box-shadow: 0 0 0 3px rgba(116, 185, 255, 0.2) !important; | |
| } | |
| /* Labels and text */ | |
| label, .gr-label { | |
| color: #2d3436 !important; | |
| font-weight: 600 !important; | |
| } | |
| /* Status text */ | |
| #status { | |
| background: rgba(255, 255, 255, 0.8) !important; | |
| color: #2d3436 !important; | |
| } | |
| """ | |
| with gr.Blocks(css=css, title="DNA-Diffusion Suite", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown( | |
| """ | |
| <div style="text-align: center; padding: 20px;"> | |
| <h1 style=" | |
| font-size: 3em; | |
| background: linear-gradient(135deg, #5f3dc4, #74b9ff, #fd79a8); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| margin-bottom: 10px; | |
| "> | |
| 🧬 DNA-Diffusion Suite | |
| </h1> | |
| <p style=" | |
| font-size: 1.2em; | |
| color: #5f3dc4; | |
| font-weight: 500; | |
| "> | |
| AI-Powered Molecular Engineering with Beautiful Visualizations | |
| </p> | |
| <div style="margin-top: 15px;"> | |
| <span style=" | |
| background: linear-gradient(135deg, #74b9ff, #a29bfe); | |
| color: white; | |
| padding: 6px 16px; | |
| border-radius: 20px; | |
| font-size: 0.9em; | |
| font-weight: 600; | |
| display: inline-block; | |
| box-shadow: 0 4px 6px rgba(162, 155, 254, 0.3); | |
| "> | |
| ✨ GPU Accelerated | |
| </span> | |
| </div> | |
| <div style="margin-top: 20px;"> | |
| <a href="https://discord.gg/openfreeai" target="_blank" style="text-decoration: none;"> | |
| <div style=" | |
| display: inline-block; | |
| background: linear-gradient(135deg, #800080, #0000ff); | |
| color: white; | |
| padding: 8px 20px; | |
| border-radius: 8px; | |
| font-weight: bold; | |
| font-size: 14px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 255, 0.3); | |
| "> | |
| <span style="margin-right: 8px;">💬</span> | |
| Discord | Openfree AI | |
| </div> | |
| </a> | |
| </div> | |
| </div> | |
| """ | |
| ) | |
| with gr.Tabs(): | |
| # Tab 1: Sequence Generation | |
| with gr.TabItem("🌸 Generate Sequence"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| cell_type = gr.Dropdown( | |
| choices=["K562", "GM12878", "HepG2"], | |
| value="K562", | |
| label="Cell Type", | |
| info="Select the cell type for sequence generation" | |
| ) | |
| guidance_scale = gr.Slider( | |
| minimum=1.0, | |
| maximum=10.0, | |
| value=1.0, | |
| step=0.5, | |
| label="Guidance Scale", | |
| info="Higher values create more cell-type specific patterns" | |
| ) | |
| enhanced_mode = gr.Checkbox( | |
| value=True, | |
| label="Enhanced Mode", | |
| info="Add cell-type specific enhancer sequences" | |
| ) | |
| generate_btn = gr.Button( | |
| "✨ Generate Sequence", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| with gr.Column(scale=2): | |
| sequence_output = gr.Textbox( | |
| label="Generated DNA Sequence", | |
| lines=5, | |
| max_lines=10, | |
| elem_classes="sequence-box" | |
| ) | |
| analysis_output = gr.JSON( | |
| label="Sequence Analysis" | |
| ) | |
| # Tab 2: 3D Visualization | |
| with gr.TabItem("🔮 3D Structure"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### 🧬 3D DNA Structure Visualization") | |
| gr.Markdown("The 3D viewer shows the double helix structure of your generated DNA sequence.") | |
| # HTML component for 3D viewer | |
| encoded_viewer = urllib.parse.quote(HTML_3D_VIEWER, safe='') | |
| viewer_html = gr.HTML( | |
| value=f'<iframe src="data:text/html;charset=utf-8,{encoded_viewer}" width="100%" height="600px"></iframe>', | |
| label="3D Molecular Viewer" | |
| ) | |
| # Button to update 3D view | |
| update_3d_btn = gr.Button( | |
| "🔄 Update 3D View with Current Sequence", | |
| variant="secondary" | |
| ) | |
| # Tab 3: DNA Casino (if external HTML exists) | |
| with gr.TabItem("🎰 DNA Casino"): | |
| gr.Markdown("### 🎲 DNA Slot Machine Game") | |
| gr.Markdown("Experience DNA generation as a fun casino game!") | |
| # Load external HTML file using helper function | |
| casino_html = gr.HTML( | |
| value=load_html_file('dna-slot-machine.html'), | |
| label="DNA Casino Game" | |
| ) | |
| # Button to capture sequence from casino | |
| with gr.Row(): | |
| casino_sequence = gr.Textbox( | |
| label="Casino Generated Sequence", | |
| placeholder="Sequence from casino will appear here", | |
| interactive=False | |
| ) | |
| analyze_casino_btn = gr.Button("🔬 Analyze Casino Sequence", variant="secondary") | |
| # Tab 4: Batch Generation | |
| with gr.TabItem("📦 Batch Generation"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| batch_cell_types = gr.CheckboxGroup( | |
| choices=["K562", "GM12878", "HepG2"], | |
| value=["K562", "GM12878"], | |
| label="Cell Types for Batch" | |
| ) | |
| batch_count = gr.Slider( | |
| minimum=1, | |
| maximum=10, | |
| value=5, | |
| step=1, | |
| label="Number of Sequences" | |
| ) | |
| batch_generate_btn = gr.Button( | |
| "✨ Generate Batch", | |
| variant="primary" | |
| ) | |
| with gr.Column(): | |
| batch_output = gr.Dataframe( | |
| headers=["ID", "Cell Type", "Length", "GC%", "Tm(°C)"], | |
| label="Batch Results" | |
| ) | |
| status_text = gr.Textbox( | |
| label="Status", | |
| value="🌸 Ready to generate sequences...", | |
| interactive=False | |
| ) | |
| # Event handlers | |
| def generate_single(cell_type, guidance_scale, enhanced): | |
| try: | |
| status_text.value = "🔄 Generating sequence on GPU..." | |
| sequence = app.generate_with_gpu(cell_type, guidance_scale, enhanced) | |
| analysis = app.analyze_sequence(sequence) | |
| status_text.value = f"✨ Successfully generated sequence for {cell_type}" | |
| return sequence, analysis, status_text.value | |
| except Exception as e: | |
| error_msg = f"⚠️ Error: {str(e)}" | |
| logger.error(error_msg) | |
| return "", {}, error_msg | |
| def update_3d_viewer(sequence): | |
| if not sequence: | |
| return viewer_html.value | |
| # Create HTML with embedded sequence data | |
| html_with_sequence = HTML_3D_VIEWER.replace( | |
| "window.addEventListener('message'", | |
| f"updateSequence('{sequence}');\n window.addEventListener('message'" | |
| ) | |
| # Properly encode content for data URI | |
| encoded_content = urllib.parse.quote(html_with_sequence, safe='') | |
| return f'<iframe src="data:text/html;charset=utf-8,{encoded_content}" width="100%" height="600px"></iframe>' | |
| def generate_batch(cell_types, count): | |
| if not cell_types: | |
| return None, "⚠️ Please select at least one cell type" | |
| try: | |
| status_text.value = "🔄 Generating batch on GPU..." | |
| results = [] | |
| for i in range(count): | |
| cell_type = cell_types[i % len(cell_types)] | |
| sequence = app.generate_with_gpu(cell_type) | |
| analysis = app.analyze_sequence(sequence) | |
| results.append([ | |
| f'SEQ_{i+1:03d}', | |
| cell_type, | |
| analysis['length'], | |
| analysis['gc_content'], | |
| analysis['melting_temp'] | |
| ]) | |
| status_text.value = f"✨ Generated {len(results)} sequences" | |
| return results, status_text.value | |
| except Exception as e: | |
| error_msg = f"⚠️ Error: {str(e)}" | |
| logger.error(error_msg) | |
| return None, error_msg | |
| # Connect event handlers | |
| generate_btn.click( | |
| fn=generate_single, | |
| inputs=[cell_type, guidance_scale, enhanced_mode], | |
| outputs=[sequence_output, analysis_output, status_text] | |
| ) | |
| update_3d_btn.click( | |
| fn=update_3d_viewer, | |
| inputs=[sequence_output], | |
| outputs=[viewer_html] | |
| ) | |
| # Auto-update 3D viewer when sequence is generated | |
| sequence_output.change( | |
| fn=update_3d_viewer, | |
| inputs=[sequence_output], | |
| outputs=[viewer_html] | |
| ) | |
| batch_generate_btn.click( | |
| fn=generate_batch, | |
| inputs=[batch_cell_types, batch_count], | |
| outputs=[batch_output, status_text] | |
| ) | |
| # Casino sequence analysis | |
| def analyze_casino_sequence(seq): | |
| if not seq: | |
| return {}, "⚠️ No sequence to analyze" | |
| analysis = app.analyze_sequence(seq) | |
| return analysis, "✨ Casino sequence analyzed" | |
| try: | |
| analyze_casino_btn.click( | |
| fn=analyze_casino_sequence, | |
| inputs=[casino_sequence], | |
| outputs=[analysis_output, status_text] | |
| ) | |
| except NameError: | |
| # Casino tab components may not be defined if HTML file is missing | |
| pass | |
| return demo | |
| if __name__ == "__main__": | |
| logger.info("=" * 50) | |
| logger.info("DNA-Diffusion App with 3D Viewer Starting...") | |
| logger.info(f"GPU Available: {SPACES_AVAILABLE}") | |
| logger.info(f"Environment: {'Hugging Face Spaces' if os.getenv('SPACE_ID') else 'Local'}") | |
| logger.info("=" * 50) | |
| demo = create_demo() | |
| if os.getenv("SPACE_ID"): | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| show_error=True | |
| ) | |
| else: | |
| demo.launch( | |
| share=True, | |
| show_error=True, | |
| debug=True | |
| ) |