Spaces:
Paused
Paused
# Authenticate with Hugging Face | |
from huggingface_hub import login | |
import os | |
# Log in to Hugging Face using the provided token | |
hf_token = os.getenv("HF_TOKEN") | |
login(hf_token) | |
# Required imports | |
import gradio as gr | |
import spaces | |
from transformers import Qwen2VLForConditionalGeneration, AutoProcessor, TextIteratorStreamer | |
from qwen_vl_utils import process_vision_info | |
import torch | |
from PIL import Image | |
import os | |
import uuid | |
import io | |
from threading import Thread | |
from reportlab.lib.pagesizes import A4 | |
from reportlab.lib.styles import getSampleStyleSheet | |
from reportlab.lib import colors | |
from reportlab.platypus import SimpleDocTemplate, Image as RLImage, Paragraph, Spacer | |
from reportlab.pdfbase import pdfmetrics | |
from reportlab.pdfbase.ttfonts import TTFont | |
import docx | |
from docx.enum.text import WD_ALIGN_PARAGRAPH | |
from reportlab.lib.units import inch | |
# Define model options | |
MODEL_OPTIONS = { | |
"ChemQwen-1": "prithivMLmods/ChemQwen-vL", | |
"ChemQwen-2": "prithivMLmods/ChemQwen2-vL", | |
} | |
# Preload models and processors into CUDA | |
models = {} | |
processors = {} | |
for name, model_id in MODEL_OPTIONS.items(): | |
print(f"Loading {name}...") | |
models[name] = Qwen2VLForConditionalGeneration.from_pretrained( | |
model_id, | |
trust_remote_code=True, | |
torch_dtype=torch.float16 | |
).to("cuda").eval() | |
processors[name] = AutoProcessor.from_pretrained(model_id, trust_remote_code=True) | |
image_extensions = Image.registered_extensions() | |
def identify_and_save_blob(blob_path): | |
"""Identifies if the blob is an image and saves it.""" | |
try: | |
with open(blob_path, 'rb') as file: | |
blob_content = file.read() | |
try: | |
Image.open(io.BytesIO(blob_content)).verify() | |
extension = ".png" | |
media_type = "image" | |
except (IOError, SyntaxError): | |
raise ValueError("Unsupported media type. Please upload a valid image.") | |
filename = f"temp_{uuid.uuid4()}_media{extension}" | |
with open(filename, "wb") as f: | |
f.write(blob_content) | |
return filename, media_type | |
except FileNotFoundError: | |
raise ValueError(f"The file {blob_path} was not found.") | |
except Exception as e: | |
raise ValueError(f"An error occurred while processing the file: {e}") | |
def qwen_inference(model_name, media_input, text_input=None): | |
"""Handles inference for the selected model.""" | |
model = models[model_name] | |
processor = processors[model_name] | |
if isinstance(media_input, str): | |
media_path = media_input | |
if media_path.endswith(tuple([i for i in image_extensions.keys()])): | |
media_type = "image" | |
else: | |
try: | |
media_path, media_type = identify_and_save_blob(media_input) | |
except Exception as e: | |
raise ValueError("Unsupported media type. Please upload a valid image.") | |
messages = [ | |
{ | |
"role": "user", | |
"content": [ | |
{ | |
"type": media_type, | |
media_type: media_path | |
}, | |
{"type": "text", "text": text_input}, | |
], | |
} | |
] | |
text = processor.apply_chat_template( | |
messages, tokenize=False, add_generation_prompt=True | |
) | |
image_inputs, _ = process_vision_info(messages) | |
inputs = processor( | |
text=[text], | |
images=image_inputs, | |
padding=True, | |
return_tensors="pt", | |
).to("cuda") | |
streamer = TextIteratorStreamer( | |
processor.tokenizer, skip_prompt=True, skip_special_tokens=True | |
) | |
generation_kwargs = dict(inputs, streamer=streamer, max_new_tokens=1024) | |
thread = Thread(target=model.generate, kwargs=generation_kwargs) | |
thread.start() | |
buffer = "" | |
for new_text in streamer: | |
buffer += new_text | |
# Remove <|im_end|> or similar tokens from the output | |
buffer = buffer.replace("<|im_end|>", "") | |
yield buffer | |
def format_plain_text(output_text): | |
"""Formats the output text as plain text without LaTeX delimiters.""" | |
plain_text = output_text.replace("\\(", "").replace("\\)", "").replace("\\[", "").replace("\\]", "") | |
return plain_text | |
def generate_document(media_path, output_text, file_format, font_size, line_spacing, alignment, image_size): | |
"""Generates a document with the input image and plain text output.""" | |
plain_text = format_plain_text(output_text) | |
if file_format == "pdf": | |
return generate_pdf(media_path, plain_text, font_size, line_spacing, alignment, image_size) | |
elif file_format == "docx": | |
return generate_docx(media_path, plain_text, font_size, line_spacing, alignment, image_size) | |
def generate_pdf(media_path, plain_text, font_size, line_spacing, alignment, image_size): | |
"""Generates a PDF document.""" | |
filename = f"output_{uuid.uuid4()}.pdf" | |
doc = SimpleDocTemplate( | |
filename, | |
pagesize=A4, | |
rightMargin=inch, | |
leftMargin=inch, | |
topMargin=inch, | |
bottomMargin=inch | |
) | |
styles = getSampleStyleSheet() | |
styles["Normal"].fontSize = int(font_size) | |
styles["Normal"].leading = int(font_size) * line_spacing | |
styles["Normal"].alignment = { | |
"Left": 0, | |
"Center": 1, | |
"Right": 2, | |
"Justified": 4 | |
}[alignment] | |
story = [] | |
image_sizes = { | |
"Small": (200, 200), | |
"Medium": (400, 400), | |
"Large": (600, 600) | |
} | |
img = RLImage(media_path, width=image_sizes[image_size][0], height=image_sizes[image_size][1]) | |
story.append(img) | |
story.append(Spacer(1, 12)) | |
text = Paragraph(plain_text, styles["Normal"]) | |
story.append(text) | |
doc.build(story) | |
return filename | |
def generate_docx(media_path, plain_text, font_size, line_spacing, alignment, image_size): | |
"""Generates a DOCX document.""" | |
filename = f"output_{uuid.uuid4()}.docx" | |
doc = docx.Document() | |
# Convert image to PNG format before adding to document | |
try: | |
img = Image.open(media_path) | |
temp_image_path = f"temp_{uuid.uuid4()}.png" | |
img.save(temp_image_path, "PNG") | |
image_sizes = { | |
"Small": docx.shared.Inches(2), | |
"Medium": docx.shared.Inches(4), | |
"Large": docx.shared.Inches(6) | |
} | |
doc.add_picture(temp_image_path, width=image_sizes[image_size]) | |
# Clean up temporary image file | |
os.remove(temp_image_path) | |
except Exception as e: | |
print(f"Error processing image: {e}") | |
# Continue without image if there's an error | |
doc.add_paragraph() | |
paragraph = doc.add_paragraph() | |
paragraph.paragraph_format.line_spacing = line_spacing | |
paragraph.paragraph_format.alignment = { | |
"Left": WD_ALIGN_PARAGRAPH.LEFT, | |
"Center": WD_ALIGN_PARAGRAPH.CENTER, | |
"Right": WD_ALIGN_PARAGRAPH.RIGHT, | |
"Justified": WD_ALIGN_PARAGRAPH.JUSTIFY | |
}[alignment] | |
run = paragraph.add_run(plain_text) | |
run.font.size = docx.shared.Pt(int(font_size)) | |
doc.save(filename) | |
return filename | |
# CSS styling | |
css = """ | |
#output { | |
height: 500px; | |
overflow: auto; | |
border: 1px solid #ccc; | |
} | |
.container { | |
background: linear-gradient(145deg, #f0f0f0, #ffffff); | |
border-radius: 20px; | |
box-shadow: 20px 20px 60px #bebebe, -20px -20px 60px #ffffff; | |
padding: 2rem; | |
margin: 1rem; | |
} | |
.title { | |
text-align: center; | |
font-size: 2.5rem; | |
color: #2d3436; | |
text-shadow: 2px 2px 4px rgba(0,0,0,0.2); | |
margin-bottom: 2rem; | |
} | |
.submit-btn { | |
background: linear-gradient(145deg, #ff4757, #ff6b81) !important; | |
color: white !important; | |
border: none !important; | |
border-radius: 10px !important; | |
padding: 0.8rem 1.5rem !important; | |
font-weight: bold !important; | |
transform: translateY(0); | |
transition: all 0.3s ease !important; | |
box-shadow: 0 4px 15px rgba(255, 71, 87, 0.3) !important; | |
} | |
.submit-btn:hover { | |
transform: translateY(-2px) !important; | |
box-shadow: 0 6px 20px rgba(255, 71, 87, 0.4) !important; | |
} | |
.download-btn { | |
background: linear-gradient(145deg, #00b894, #00cec9) !important; | |
color: white !important; | |
border: none !important; | |
border-radius: 10px !important; | |
padding: 0.8rem 1.5rem !important; | |
font-weight: bold !important; | |
transform: translateY(0); | |
transition: all 0.3s ease !important; | |
box-shadow: 0 4px 15px rgba(0, 184, 148, 0.3) !important; | |
} | |
.download-btn:hover { | |
transform: translateY(-2px) !important; | |
box-shadow: 0 6px 20px rgba(0, 184, 148, 0.4) !important; | |
} | |
.input-box { | |
border-radius: 10px !important; | |
border: 2px solid #dfe6e9 !important; | |
transition: all 0.3s ease !important; | |
} | |
.input-box:focus { | |
border-color: #00b894 !important; | |
box-shadow: 0 0 10px rgba(0, 184, 148, 0.2) !important; | |
} | |
""" | |
# Gradio app setup | |
with gr.Blocks(css=css) as demo: | |
gr.Markdown("# π§ͺ ChemQwen Chemical Identifier AI π€", elem_classes="title") | |
with gr.Tab(label="πΌοΈ Image Analysis"): | |
with gr.Row(elem_classes="container"): | |
with gr.Column(): | |
model_choice = gr.Dropdown( | |
label="π Select Model", | |
choices=list(MODEL_OPTIONS.keys()), | |
value="ChemQwen-1", | |
elem_classes="input-box" | |
) | |
input_media = gr.File( | |
label="π€ Upload Image", | |
type="filepath", | |
elem_classes="input-box" | |
) | |
text_input = gr.Textbox( | |
label="β Your Question", | |
placeholder="Ask anything about the image...", | |
elem_classes="input-box" | |
) | |
submit_btn = gr.Button(value="π Analyze", elem_classes="submit-btn") | |
with gr.Column(): | |
output_text = gr.Textbox( | |
label="π AI Response", | |
lines=10, | |
elem_classes="input-box" | |
) | |
plain_text_output = gr.Textbox( | |
label="π Standardized Text", | |
lines=10, | |
elem_classes="input-box" | |
) | |
with gr.Row(elem_classes="container"): | |
with gr.Column(): | |
gr.Markdown("### π Document Settings") | |
line_spacing = gr.Dropdown( | |
choices=[0.5, 1.0, 1.15, 1.5, 2.0, 2.5, 3.0], | |
value=1.5, | |
label="βοΈ Line Spacing", | |
elem_classes="input-box" | |
) | |
font_size = gr.Dropdown( | |
choices=["8", "10", "12", "14", "16", "18", "20", "22", "24"], | |
value="18", | |
label="π Font Size", | |
elem_classes="input-box" | |
) | |
alignment = gr.Dropdown( | |
choices=["Left", "Center", "Right", "Justified"], | |
value="Justified", | |
label="β‘ Text Alignment", | |
elem_classes="input-box" | |
) | |
image_size = gr.Dropdown( | |
choices=["Small", "Medium", "Large"], | |
value="Medium", | |
label="πΌοΈ Image Size", | |
elem_classes="input-box" | |
) | |
file_format = gr.Radio( | |
["pdf", "docx"], | |
label="π File Format", | |
value="pdf", | |
elem_classes="input-box" | |
) | |
get_document_btn = gr.Button( | |
value="πΎ Generate Document", | |
elem_classes="download-btn" | |
) | |
submit_btn.click( | |
qwen_inference, | |
[model_choice, input_media, text_input], | |
output_text, | |
).then( | |
format_plain_text, | |
output_text, | |
plain_text_output | |
) | |
get_document_btn.click( | |
generate_document, | |
[input_media, output_text, file_format, font_size, line_spacing, alignment, image_size], | |
gr.File(label="π₯ Download Document") | |
) | |
gr.Markdown(""" | |
### π Features | |
- π¬ Advanced Chemical Structure Analysis | |
- π Multiple Model Support | |
- π« Real-time Processing | |
- π Custom Document Generation | |
### π‘ Tips | |
- πΈ Upload clear images for better results | |
- βοΈ Be specific with your questions | |
- π Use the document generator for professional reports | |
""") | |
demo.launch(debug=True) | |