SMILES2PEPTIDE / app.py
yinuozhang's picture
update more aa recog
84bbd6a
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()