import gradio as gr import re import pandas as pd from io import StringIO import rdkit from rdkit import Chem from rdkit.Chem import AllChem, Draw import numpy as np from PIL import Image, ImageDraw, ImageFont import matplotlib.pyplot as plt import matplotlib.patches as patches from io import BytesIO import re from rdkit import Chem class PeptideAnalyzer: def __init__(self): self.bond_patterns = [ (r'OC\(=O\)', 'ester'), # Ester bond (r'N\(C\)C\(=O\)', 'n_methyl'), # N-methylated peptide bond (r'N[12]C\(=O\)', 'proline'), # Proline peptide bond (r'NC\(=O\)', 'peptide'), # Standard peptide bond (r'C\(=O\)N\(C\)', 'n_methyl_reverse'), # Reverse N-methylated (r'C\(=O\)N[12]?', 'peptide_reverse') # Reverse peptide bond ] def is_peptide(self, smiles): """Check if the SMILES represents a peptide structure""" mol = Chem.MolFromSmiles(smiles) if mol is None: return False # Look for peptide bonds: NC(=O) pattern peptide_bond_pattern = Chem.MolFromSmarts('[NH][C](=O)') if mol.HasSubstructMatch(peptide_bond_pattern): return True # Look for N-methylated peptide bonds: N(C)C(=O) pattern n_methyl_pattern = Chem.MolFromSmarts('[N;H0;$(NC)](C)[C](=O)') if mol.HasSubstructMatch(n_methyl_pattern): return True return False def is_cyclic(self, smiles): """ Determine if SMILES represents a cyclic peptide by checking head-tail connection. Returns: (is_cyclic, peptide_cycles, aromatic_cycles) """ # First find aromatic rings aromatic_cycles = [] for match in re.finditer(r'c[12]ccccc[12]', smiles): number = match.group(0)[1] if number not in aromatic_cycles: aromatic_cycles.append(str(number)) # Find potential cycle numbers and their contexts cycle_closures = [] # Look for cycle starts and corresponding ends cycle_patterns = [ # Pattern pairs (start, end) (r'[^\d](\d)[A-Z@]', r'C\1=O$'), # Classic C=O ending (r'[^\d](\d)[A-Z@]', r'N\1C\(=O\)'), # N1C(=O) pattern (r'[^\d](\d)[A-Z@]', r'N\1C$'), # Simple N1C ending (r'[^\d](\d)C\(=O\)', r'N\1[A-Z]'), # Reverse connection (r'H(\d)', r'N\1C'), # H1...N1C pattern (r'[^\d](\d)(?:C|N|O)', r'(?:C|N)\1(?:\(|$)'), # Generic cycle closure ] for start_pat, end_pat in cycle_patterns: start_matches = re.finditer(start_pat, smiles) for start_match in start_matches: number = start_match.group(1) if number not in aromatic_cycles: # Skip aromatic ring numbers # Look for corresponding end pattern end_match = re.search(end_pat.replace('\\1', number), smiles) if end_match and end_match.start() > start_match.start(): cycle_closures.append(number) break # Remove duplicates and aromatic numbers peptide_cycles = list(set(cycle_closures) - set(aromatic_cycles)) is_cyclic = len(peptide_cycles) > 0 return is_cyclic, peptide_cycles, aromatic_cycles def split_on_bonds(self, smiles): """Split SMILES into segments with simplified Pro handling""" positions = [] used = set() # Find Gly pattern first gly_pattern = r'NCC\(=O\)' for match in re.finditer(gly_pattern, smiles): if not any(p in range(match.start(), match.end()) for p in used): positions.append({ 'start': match.start(), 'end': match.end(), 'type': 'gly', 'pattern': match.group() }) used.update(range(match.start(), match.end())) for pattern, bond_type in self.bond_patterns: for match in re.finditer(pattern, smiles): if not any(p in range(match.start(), match.end()) for p in used): positions.append({ 'start': match.start(), 'end': match.end(), 'type': bond_type, 'pattern': match.group() }) used.update(range(match.start(), match.end())) # Sort by position positions.sort(key=lambda x: x['start']) # Create segments segments = [] if positions: # First segment if positions[0]['start'] > 0: segments.append({ 'content': smiles[0:positions[0]['start']], 'bond_after': positions[0]['pattern'] }) # Process segments for i in range(len(positions)-1): current = positions[i] next_pos = positions[i+1] if current['type'] == 'gly': segments.append({ 'content': 'NCC(=O)', 'bond_before': positions[i-1]['pattern'] if i > 0 else None, 'bond_after': next_pos['pattern'] }) else: content = smiles[current['end']:next_pos['start']] if content: segments.append({ 'content': content, 'bond_before': current['pattern'], 'bond_after': next_pos['pattern'] }) # Last segment if positions[-1]['end'] < len(smiles): segments.append({ 'content': smiles[positions[-1]['end']:], 'bond_before': positions[-1]['pattern'] }) return segments def identify_residue(self, segment): """Identify residue with Pro reconstruction""" content = segment['content'] mods = self.get_modifications(segment) # Special handling for Pro: reconstruct the complete pattern if (segment.get('bond_after') == 'N2C(=O)' and 'CCC' in content) or \ ('CCCN2' in content and content.endswith('=O')): # End case # Reconstruct the complete Pro pattern if '[C@@H]2' in content or '[C@H]2' in content: return 'Pro', mods if ('C[C@H](CCCC)' in content or 'C[C@@H](CCCC)' in content) and 'CC(C)' not in content: return 'Nle', mods # Ornithine (Orn) - 3-carbon chain with NH2 if ('C[C@H](CCCN)' in content or 'C[C@@H](CCCN)' in content) and 'CC(C)' not in content: return 'Orn', mods # 2-Naphthylalanine (2Nal) - distinct from Phe pattern if ('Cc3cc2ccccc2c3' in content) and ('C[C@H]' in content or 'C[C@@H]' in content): return '2Nal', mods # Cyclohexylalanine (Cha) - already in your code but moved here for clarity if 'N2CCCCC2' in content or 'CCCCC2' in content: return 'Cha', mods # Aminobutyric acid (Abu) - 2-carbon chain if ('C[C@H](CC)' in content or 'C[C@@H](CC)' in content) and not any(p in content for p in ['CC(C)', 'CCCC', 'CCC(C)']): return 'Abu', mods # Pipecolic acid (Pip) - 6-membered ring like Pro if ('N3CCCCC3' in content or 'CCCCC3' in content) and ('C[C@H]' in content or 'C[C@@H]' in content): return 'Pip', mods # Cyclohexylglycine (Chg) - direct cyclohexyl without CH2 if ('C[C@H](C1CCCCC1)' in content or 'C[C@@H](C1CCCCC1)' in content): return 'Chg', mods # 4-Fluorophenylalanine (4F-Phe) if ('Cc2ccc(F)cc2' in content) and ('C[C@H]' in content or 'C[C@@H]' in content): return '4F-Phe', mods # Regular residue identification if ('NCC(=O)' in content) or (content == 'C'): # Middle case - between bonds if segment.get('bond_before') and segment.get('bond_after'): if ('C(=O)N' in segment['bond_before'] or 'C(=O)N(C)' in segment['bond_before']): return 'Gly', mods # Terminal case - at the end elif segment.get('bond_before') and segment.get('bond_before').startswith('C(=O)N'): return 'Gly', mods if 'CC(C)C[C@H]' in content or 'CC(C)C[C@@H]' in content: return 'Leu', mods if '[C@@H](CC(C)C)' in content or '[C@H](CC(C)C)' in content: return 'Leu', mods if ('C(C)C[C@H]' in content or 'C(C)C[C@@H]' in content) and 'CC(C)C' not in content: return 'Ile', mods if '[C@@H]([C@@H](C)O)' in content or '[C@H]([C@H](C)O)' in content: return 'Thr', mods if '[C@H](Cc2ccccc2)' in content or '[C@@H](Cc2ccccc2)' in content: return 'Phe', mods if '[C@H](C(C)C)' in content or '[C@@H](C(C)C)' in content: if not any(p in content for p in ['CC(C)C[C@H]', 'CC(C)C[C@@H]']): return 'Val', mods if '[C@H](COC(C)(C)C)' in content or '[C@@H](COC(C)(C)C)' in content: return 'O-tBu', mods if ('[C@H](C)' in content or '[C@@H](C)' in content): if not any(p in content for p in ['C(C)C', 'COC', 'CN(', 'C(C)O']): return 'Ala', mods # Tyrosine (Tyr) - 4-hydroxybenzyl side chain if ('Cc2ccc(O)cc2' in content) and ('C[C@H]' in content or 'C[C@@H]' in content): return 'Tyr', mods # Tryptophan (Trp) - Indole side chain if ('Cc2c[nH]c3ccccc23' in content) and ('C[C@H]' in content or 'C[C@@H]' in content): return 'Trp', mods # Serine (Ser) - Hydroxymethyl side chain if '[C@H](CO)' in content or '[C@@H](CO)' in content: if not ('C(C)O' in content or 'COC' in content): return 'Ser', mods # Threonine (Thr) - 1-hydroxyethyl side chain if '[C@@H]([C@@H](C)O)' in content or '[C@H]([C@H](C)O)' in content: return 'Thr', mods # Cysteine (Cys) - Thiol side chain if '[C@H](CS)' in content or '[C@@H](CS)' in content: return 'Cys', mods # Methionine (Met) - Methylthioethyl side chain if ('C[C@H](CCSC)' in content or 'C[C@@H](CCSC)' in content): return 'Met', mods # Asparagine (Asn) - Carbamoylmethyl side chain if ('CC(=O)N' in content) and ('C[C@H]' in content or 'C[C@@H]' in content): return 'Asn', mods # Glutamine (Gln) - Carbamoylethyl side chain if ('CCC(=O)N' in content) and ('C[C@H]' in content or 'C[C@@H]' in content): return 'Gln', mods # Aspartic acid (Asp) - Carboxymethyl side chain if ('CC(=O)O' in content) and ('C[C@H]' in content or 'C[C@@H]' in content): return 'Asp', mods # Glutamic acid (Glu) - Carboxyethyl side chain if ('CCC(=O)O' in content) and ('C[C@H]' in content or 'C[C@@H]' in content): return 'Glu', mods # Lysine (Lys) - 4-aminobutyl side chain if ('C[C@H](CCCCN)' in content or 'C[C@@H](CCCCN)' in content): return 'Lys', mods # Arginine (Arg) - 3-guanidinopropyl side chain if ('CCCNC(=N)N' in content) and ('C[C@H]' in content or 'C[C@@H]' in content): return 'Arg', mods # Histidine (His) - Imidazole side chain if ('Cc2cnc[nH]2' in content) and ('C[C@H]' in content or 'C[C@@H]' in content): return 'His', mods return None, mods def get_modifications(self, segment): """Get modifications based on bond types""" mods = [] if segment.get('bond_after'): if 'N(C)' in segment['bond_after'] or segment['bond_after'].startswith('C(=O)N(C)'): mods.append('N-Me') if 'OC(=O)' in segment['bond_after']: mods.append('O-linked') return mods def analyze_structure(self, smiles): """Main analysis function""" print("\nAnalyzing structure:", smiles) # Split into segments segments = self.split_on_bonds(smiles) print("\nSegment Analysis:") sequence = [] for i, segment in enumerate(segments): print(f"\nSegment {i}:") print(f"Content: {segment['content']}") print(f"Bond before: {segment.get('bond_before', 'None')}") print(f"Bond after: {segment.get('bond_after', 'None')}") residue, mods = self.identify_residue(segment) if residue: if mods: sequence.append(f"{residue}({','.join(mods)})") else: sequence.append(residue) print(f"Identified as: {residue}") print(f"Modifications: {mods}") else: print(f"Warning: Could not identify residue in segment: {segment['content']}") # Check if cyclic is_cyclic = 'N1' in smiles or 'N2' in smiles final_sequence = f"cyclo({'-'.join(sequence)})" if is_cyclic else '-'.join(sequence) print(f"\nFinal sequence: {final_sequence}") return final_sequence """ def annotate_cyclic_structure(mol, sequence): '''Create annotated 2D structure with clear, non-overlapping residue labels''' # Generate 2D coordinates # Generate 2D coordinates AllChem.Compute2DCoords(mol) # Create drawer with larger size for annotations drawer = Draw.rdMolDraw2D.MolDraw2DCairo(2000, 2000) # Even larger size # Get residue list and reverse it to match structural representation if sequence.startswith('cyclo('): residues = sequence[6:-1].split('-') else: residues = sequence.split('-') residues = list(reversed(residues)) # Reverse the sequence # Draw molecule first to get its bounds drawer.drawOptions().addAtomIndices = False drawer.DrawMolecule(mol) drawer.FinishDrawing() # Convert to PIL Image img = Image.open(BytesIO(drawer.GetDrawingText())) draw = ImageDraw.Draw(img) try: # Try to use DejaVuSans as it's commonly available on Linux systems font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 60) small_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 60) except OSError: try: # Fallback to Arial if available (common on Windows) font = ImageFont.truetype("arial.ttf", 60) small_font = ImageFont.truetype("arial.ttf", 60) except OSError: # If no TrueType fonts are available, fall back to default print("Warning: TrueType fonts not available, using default font") font = ImageFont.load_default() small_font = ImageFont.load_default() # Get molecule bounds conf = mol.GetConformer() positions = [] for i in range(mol.GetNumAtoms()): pos = conf.GetAtomPosition(i) positions.append((pos.x, pos.y)) x_coords = [p[0] for p in positions] y_coords = [p[1] for p in positions] min_x, max_x = min(x_coords), max(x_coords) min_y, max_y = min(y_coords), max(y_coords) # Calculate scaling factors scale = 150 # Increased scale factor center_x = 1000 # Image center center_y = 1000 # Add residue labels in a circular arrangement around the structure n_residues = len(residues) radius = 700 # Distance of labels from center # Start from the rightmost point (3 o'clock position) and go counterclockwise # Offset by -3 positions to align with structure offset = 0 # Adjust this value to match the structure alignment for i, residue in enumerate(residues): # Calculate position in a circle around the structure # Start from 0 (3 o'clock) and go counterclockwise angle = -(2 * np.pi * ((i + offset) % n_residues) / n_residues) # Calculate label position label_x = center_x + radius * np.cos(angle) label_y = center_y + radius * np.sin(angle) # Draw residue label text = f"{i+1}. {residue}" bbox = draw.textbbox((label_x, label_y), text, font=font) padding = 10 draw.rectangle([bbox[0]-padding, bbox[1]-padding, bbox[2]+padding, bbox[3]+padding], fill='white', outline='white') draw.text((label_x, label_y), text, font=font, fill='black', anchor="mm") # Add sequence at the top with white background seq_text = f"Sequence: {sequence}" bbox = draw.textbbox((center_x, 100), seq_text, font=small_font) padding = 10 draw.rectangle([bbox[0]-padding, bbox[1]-padding, bbox[2]+padding, bbox[3]+padding], fill='white', outline='white') draw.text((center_x, 100), seq_text, font=small_font, fill='black', anchor="mm") return img """ def annotate_cyclic_structure(mol, sequence): """Create structure visualization with just the sequence header""" # Generate 2D coordinates AllChem.Compute2DCoords(mol) # Create drawer with larger size for annotations drawer = Draw.rdMolDraw2D.MolDraw2DCairo(2000, 2000) # Draw molecule first drawer.drawOptions().addAtomIndices = False drawer.DrawMolecule(mol) drawer.FinishDrawing() # Convert to PIL Image img = Image.open(BytesIO(drawer.GetDrawingText())) draw = ImageDraw.Draw(img) try: small_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 60) except OSError: try: small_font = ImageFont.truetype("arial.ttf", 60) except OSError: print("Warning: TrueType fonts not available, using default font") small_font = ImageFont.load_default() # Add just the sequence header at the top seq_text = f"Sequence: {sequence}" bbox = draw.textbbox((1000, 100), seq_text, font=small_font) padding = 10 draw.rectangle([bbox[0]-padding, bbox[1]-padding, bbox[2]+padding, bbox[3]+padding], fill='white', outline='white') draw.text((1000, 100), seq_text, font=small_font, fill='black', anchor="mm") return img def create_enhanced_linear_viz(sequence, smiles): """Create an enhanced linear representation using PeptideAnalyzer""" analyzer = PeptideAnalyzer() # Create analyzer instance # Create figure with two subplots fig = plt.figure(figsize=(15, 10)) gs = fig.add_gridspec(2, 1, height_ratios=[1, 2]) ax_struct = fig.add_subplot(gs[0]) ax_detail = fig.add_subplot(gs[1]) # Parse sequence and get residues if sequence.startswith('cyclo('): residues = sequence[6:-1].split('-') else: residues = sequence.split('-') # Get segments using analyzer segments = analyzer.split_on_bonds(smiles) # Debug print print(f"Number of residues: {len(residues)}") print(f"Number of segments: {len(segments)}") # Top subplot - Basic structure ax_struct.set_xlim(0, 10) ax_struct.set_ylim(0, 2) num_residues = len(residues) spacing = 9.0 / (num_residues - 1) if num_residues > 1 else 9.0 # Draw basic structure y_pos = 1.5 for i in range(num_residues): x_pos = 0.5 + i * spacing # Draw amino acid box rect = patches.Rectangle((x_pos-0.3, y_pos-0.2), 0.6, 0.4, facecolor='lightblue', edgecolor='black') ax_struct.add_patch(rect) # Draw connecting bonds if not the last residue if i < num_residues - 1: segment = segments[i] if i < len(segments) else None if segment: # Determine bond type from segment info bond_type = 'ester' if 'O-linked' in segment.get('bond_after', '') else 'peptide' is_n_methylated = 'N-Me' in segment.get('bond_after', '') bond_color = 'red' if bond_type == 'ester' else 'black' linestyle = '--' if bond_type == 'ester' else '-' # Draw bond line ax_struct.plot([x_pos+0.3, x_pos+spacing-0.3], [y_pos, y_pos], color=bond_color, linestyle=linestyle, linewidth=2) # Add bond type label mid_x = x_pos + spacing/2 bond_label = f"{bond_type}" if is_n_methylated: bond_label += "\n(N-Me)" ax_struct.text(mid_x, y_pos+0.1, bond_label, ha='center', va='bottom', fontsize=10, color=bond_color) # Add residue label ax_struct.text(x_pos, y_pos-0.5, residues[i], ha='center', va='top', fontsize=14) # Bottom subplot - Detailed breakdown ax_detail.set_ylim(0, len(segments)+1) ax_detail.set_xlim(0, 1) # Create detailed breakdown segment_y = len(segments) # Start from top for i, segment in enumerate(segments): y = segment_y - i # Check if this is a bond or residue residue, mods = analyzer.identify_residue(segment) if residue: text = f"Residue {i+1}: {residue}" if mods: text += f" ({', '.join(mods)})" color = 'blue' else: # Must be a bond text = f"Bond {i}: " if 'O-linked' in segment.get('bond_after', ''): text += "ester" elif 'N-Me' in segment.get('bond_after', ''): text += "peptide (N-methylated)" else: text += "peptide" color = 'red' # Add segment analysis ax_detail.text(0.05, y, text, fontsize=12, color=color) ax_detail.text(0.5, y, f"SMILES: {segment.get('content', '')}", fontsize=10, color='gray') # If cyclic, add connection indicator if sequence.startswith('cyclo('): ax_struct.annotate('', xy=(9.5, y_pos), xytext=(0.5, y_pos), arrowprops=dict(arrowstyle='<->', color='red', lw=2)) ax_struct.text(5, y_pos+0.3, 'Cyclic Connection', ha='center', color='red', fontsize=14) # Add titles and adjust layout ax_struct.set_title("Peptide Structure Overview", pad=20) ax_detail.set_title("Segment Analysis Breakdown", pad=20) # Remove axes for ax in [ax_struct, ax_detail]: ax.set_xticks([]) ax.set_yticks([]) ax.axis('off') plt.tight_layout() return fig def process_input(smiles_input=None, file_obj=None, show_linear=False, show_segment_details=False): """Process input and create visualizations using PeptideAnalyzer""" analyzer = PeptideAnalyzer() # Handle direct SMILES input if smiles_input: smiles = smiles_input.strip() # First check if it's a peptide using analyzer's method if not analyzer.is_peptide(smiles): return "Error: Input SMILES does not appear to be a peptide structure.", None, None try: # Create molecule mol = Chem.MolFromSmiles(smiles) if mol is None: return "Error: Invalid SMILES notation.", None, None # Use analyzer to get sequence segments = analyzer.split_on_bonds(smiles) # Process segments and build sequence sequence_parts = [] output_text = "" # Only include segment analysis in output if requested if show_segment_details: output_text += "Segment Analysis:\n" for i, segment in enumerate(segments): output_text += f"\nSegment {i}:\n" output_text += f"Content: {segment['content']}\n" output_text += f"Bond before: {segment.get('bond_before', 'None')}\n" output_text += f"Bond after: {segment.get('bond_after', 'None')}\n" residue, mods = analyzer.identify_residue(segment) if residue: if mods: sequence_parts.append(f"{residue}({','.join(mods)})") else: sequence_parts.append(residue) output_text += f"Identified as: {residue}\n" output_text += f"Modifications: {mods}\n" else: output_text += f"Warning: Could not identify residue in segment: {segment['content']}\n" output_text += "\n" else: # Just build sequence without detailed analysis in output for segment in segments: residue, mods = analyzer.identify_residue(segment) if residue: if mods: sequence_parts.append(f"{residue}({','.join(mods)})") else: sequence_parts.append(residue) # Check if cyclic using analyzer's method is_cyclic, peptide_cycles, aromatic_cycles = analyzer.is_cyclic(smiles) sequence = f"cyclo({'-'.join(sequence_parts)})" if is_cyclic else '-'.join(sequence_parts) # Create cyclic structure visualization img_cyclic = annotate_cyclic_structure(mol, sequence) # Create linear representation if requested img_linear = None if show_linear: fig_linear = create_enhanced_linear_viz(sequence, smiles) buf = BytesIO() fig_linear.savefig(buf, format='png', bbox_inches='tight', dpi=300) buf.seek(0) img_linear = Image.open(buf) plt.close(fig_linear) # Add summary to output summary = "Summary:\n" summary += f"Sequence: {sequence}\n" summary += f"Is Cyclic: {'Yes' if is_cyclic else 'No'}\n" if is_cyclic: summary += f"Peptide Cycles: {', '.join(peptide_cycles)}\n" #summary += f"Aromatic Cycles: {', '.join(aromatic_cycles)}\n" return summary + output_text, img_cyclic, img_linear except Exception as e: return f"Error processing SMILES: {str(e)}", None, None # Handle file input if file_obj is not None: try: # Handle file content if hasattr(file_obj, 'name'): with open(file_obj.name, 'r') as f: content = f.read() else: content = file_obj.decode('utf-8') if isinstance(file_obj, bytes) else str(file_obj) output_text = "" for line in content.splitlines(): smiles = line.strip() if smiles: # Check if it's a peptide if not analyzer.is_peptide(smiles): output_text += f"Skipping non-peptide SMILES: {smiles}\n" continue # Process this SMILES segments = analyzer.split_on_bonds(smiles) sequence_parts = [] # Add segment details if requested if show_segment_details: output_text += f"\nSegment Analysis for SMILES: {smiles}\n" for i, segment in enumerate(segments): output_text += f"\nSegment {i}:\n" output_text += f"Content: {segment['content']}\n" output_text += f"Bond before: {segment.get('bond_before', 'None')}\n" output_text += f"Bond after: {segment.get('bond_after', 'None')}\n" residue, mods = analyzer.identify_residue(segment) if residue: if mods: sequence_parts.append(f"{residue}({','.join(mods)})") else: sequence_parts.append(residue) output_text += f"Identified as: {residue}\n" output_text += f"Modifications: {mods}\n" else: for segment in segments: residue, mods = analyzer.identify_residue(segment) if residue: if mods: sequence_parts.append(f"{residue}({','.join(mods)})") else: sequence_parts.append(residue) # Get cyclicity and create sequence is_cyclic, peptide_cycles, aromatic_cycles = analyzer.is_cyclic(smiles) sequence = f"cyclo({'-'.join(sequence_parts)})" if is_cyclic else '-'.join(sequence_parts) output_text += f"\nSummary for SMILES: {smiles}\n" output_text += f"Sequence: {sequence}\n" output_text += f"Is Cyclic: {'Yes' if is_cyclic else 'No'}\n" if is_cyclic: output_text += f"Peptide Cycles: {', '.join(peptide_cycles)}\n" #output_text += f"Aromatic Cycles: {', '.join(aromatic_cycles)}\n" output_text += "-" * 50 + "\n" return output_text, None, None except Exception as e: return f"Error processing file: {str(e)}", None, None return "No input provided.", None, None iface = gr.Interface( fn=process_input, inputs=[ gr.Textbox( label="Enter SMILES string", placeholder="Enter SMILES notation of peptide...", lines=2 ), gr.File( label="Or upload a text file with SMILES", file_types=[".txt"] ), gr.Checkbox( label="Show linear representation", value=False ), gr.Checkbox( label="Show segment details", value=False ) ], outputs=[ gr.Textbox( label="Analysis Results", lines=10 ), gr.Image( label="2D Structure with Annotations", type="pil" ), gr.Image( label="Linear Representation", type="pil" ) ], title="Peptide Structure Analyzer and Visualizer", description=""" Analyze and visualize peptide structures from SMILES notation: 1. Validates if the input is a peptide structure 2. Determines if the peptide is cyclic 3. Parses the amino acid sequence 4. Creates 2D structure visualization with residue annotations 5. Optional linear representation Input: Either enter a SMILES string directly or upload a text file containing SMILES strings Example SMILES strings (copy and paste): ``` CC(C)C[C@@H]1NC(=O)[C@@H](CC(C)C)N(C)C(=O)[C@@H](C)N(C)C(=O)[C@H](Cc2ccccc2)NC(=O)[C@H](CC(C)C)N(C)C(=O)[C@H]2CCCN2C1=O ``` ``` C(C)C[C@@H]1NC(=O)[C@@H]2CCCN2C(=O)[C@@H](CC(C)C)NC(=O)[C@@H](CC(C)C)N(C)C(=O)[C@H](C)NC(=O)[C@H](Cc2ccccc2)NC1=O ``` ``` CC(C)C[C@H]1C(=O)N(C)[C@@H](Cc2ccccc2)C(=O)NCC(=O)N[C@H](C(=O)N2CCCCC2)CC(=O)N(C)CC(=O)N[C@@H]([C@@H](C)O)C(=O)N(C)[C@@H](C)C(=O)N[C@@H](COC(C)(C)C)C(=O)N(C)[C@@H](Cc2ccccc2)C(=O)N1C ``` """, flagging_mode="never" ) # Launch the app if __name__ == "__main__": iface.launch()