ImageToAutocad / app.py
ArrcttacsrjksX's picture
Update app.py
bcaa659 verified
import gradio as gr
import subprocess
import os
import tempfile
import datetime
from pathlib import Path
from huggingface_hub import upload_file
from typing import Optional, Tuple, List, Union
import logging
import shutil
import base64
from PIL import Image
import io
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class ImageToDxfConverter:
def __init__(self):
"""Initialize the converter with configuration."""
self.executable_path = Path("SimpleImageToDxfHavePass")
self.hf_token = os.getenv("HF_TOKEN")
self.repo_id = "ArrcttacsrjksX/ImageToAutocadData"
try:
os.chmod(self.executable_path, 0o755)
logger.info(f"Set executable permissions for {self.executable_path}")
except Exception as e:
logger.error(f"Failed to set executable permissions: {e}")
def _process_image(self, image_path: str, size_multiplier: float, black_white: bool) -> str:
"""Process the image with size multiplier and black/white conversion."""
try:
with Image.open(image_path) as img:
# Calculate new size
new_width = int(img.width * size_multiplier)
new_height = int(img.height * size_multiplier)
# Resize image
img = img.resize((new_width, new_height), Image.LANCZOS)
# Convert to black and white if option is selected
if black_white:
img = img.convert('L') # Convert to grayscale
img = img.point(lambda x: 0 if x < 128 else 255, '1') # Convert to binary
# Save processed image to temporary file
with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as temp_file:
img.save(temp_file.name, 'PNG')
return temp_file.name
except Exception as e:
logger.error(f"Failed to process image: {str(e)}")
return image_path
def _ensure_directory(self, path: Union[str, Path]) -> Path:
"""Ensure directory exists and return Path object."""
path = Path(path)
path.mkdir(parents=True, exist_ok=True)
return path
def _generate_output_paths(self, output_folder: Path, timestamp: str) -> dict:
"""Generate all required output paths."""
return {
'output_dxf': output_folder / f"{timestamp}_output.dxf",
'debug_png': output_folder / f"{timestamp}_debug.png",
'temp_dxf': output_folder / "_output.dxf",
'temp_debug': output_folder / "_debug.png"
}
def _save_base64_image(self, base64_data: str) -> str:
"""Save base64 image data to temporary file."""
try:
img_data = base64_data.split(',')[1]
img_bytes = base64.b64decode(img_data)
with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as temp_file:
temp_file.write(img_bytes)
return temp_file.name
except Exception as e:
logger.error(f"Failed to save base64 image: {str(e)}")
return None
def convert_image(self,
image_input: Union[str, None],
use_lines: bool = False,
size_multiplier: float = 1.0,
black_white: bool = False) -> Tuple[Optional[str], Optional[str], List[str]]:
"""Convert image to DXF format with size multiplier and black/white option."""
try:
if not image_input:
return None, None, []
# Handle base64 data
if isinstance(image_input, str) and image_input.startswith('data:image'):
temp_image_path = self._save_base64_image(image_input)
if not temp_image_path:
return None, None, []
image_path = temp_image_path
else:
image_path = image_input
# Process image with size multiplier and black/white conversion
processed_image_path = self._process_image(image_path, size_multiplier, black_white)
# Setup output directory
output_dir = self._ensure_directory("OutputPDF")
timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
paths = self._generate_output_paths(output_dir, timestamp)
# Prepare conversion command
command = [
f"./{self.executable_path}",
f"--imagePath={processed_image_path}",
f"--outputPath={paths['temp_dxf']}",
f"--debug-output={paths['temp_debug']}"
]
if use_lines:
command.append("--use-lines")
try:
result = subprocess.run(
command,
check=True,
capture_output=True,
text=True
)
logger.info(f"Conversion output: {result.stdout}")
except subprocess.CalledProcessError as e:
logger.error(f"Conversion failed: {e.stderr}")
return None, None, []
# Move temporary files to final locations
shutil.move(paths['temp_dxf'], paths['output_dxf'])
if use_lines and os.path.exists(paths['temp_debug']):
shutil.move(paths['temp_debug'], paths['debug_png'])
# Upload files to Hugging Face
uploaded_files = []
if self.hf_token:
try:
date_folder = timestamp
uploaded_input = upload_file(
path_or_fileobj=processed_image_path,
path_in_repo=f"datasets/{self.repo_id}/{date_folder}/{Path(processed_image_path).name}",
repo_id=self.repo_id,
token=self.hf_token
)
uploaded_files.append(uploaded_input)
uploaded_dxf = upload_file(
path_or_fileobj=str(paths['output_dxf']),
path_in_repo=f"datasets/{self.repo_id}/{date_folder}/{paths['output_dxf'].name}",
repo_id=self.repo_id,
token=self.hf_token
)
uploaded_files.append(uploaded_dxf)
if use_lines and os.path.exists(paths['debug_png']):
uploaded_debug = upload_file(
path_or_fileobj=str(paths['debug_png']),
path_in_repo=f"datasets/{self.repo_id}/{date_folder}/{paths['debug_png'].name}",
repo_id=self.repo_id,
token=self.hf_token
)
uploaded_files.append(uploaded_debug)
except Exception as e:
logger.error(f"Upload failed: {str(e)}")
# Clean up temporary files
if isinstance(image_input, str) and image_input.startswith('data:image'):
try:
os.unlink(image_path)
except Exception as e:
logger.error(f"Failed to clean up temporary file: {str(e)}")
if processed_image_path != image_path:
try:
os.unlink(processed_image_path)
except Exception as e:
logger.error(f"Failed to clean up processed image file: {str(e)}")
return (
str(paths['output_dxf']),
str(paths['debug_png']) if use_lines and os.path.exists(paths['debug_png']) else None,
uploaded_files
)
except Exception as e:
logger.error(f"Conversion failed: {str(e)}")
return None, None, []
def create_gradio_interface():
"""Create and configure the Gradio interface."""
converter = ImageToDxfConverter()
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# Image to DXF Converter
Convert your images to DXF format for CAD software.
Press Ctrl+V to paste image from clipboard.
""")
clipboard_image = gr.State()
with gr.Row():
with gr.Column(scale=2):
image_input = gr.Image(
type="filepath",
label="Input Image",
elem_id="image_input",
height=300
)
with gr.Column(scale=1):
size_multiplier = gr.Number(
label="Image Size Multiplier",
value=1.0,
minimum=0.1,
maximum=20.0,
step=0.1,
elem_id="size_multiplier"
)
use_lines_checkbox = gr.Checkbox(
label="Enable line detection",
value=False,
elem_id="use_lines"
)
black_white_checkbox = gr.Checkbox(
label="Convert to Black & White",
value=False,
elem_id="black_white"
)
convert_btn = gr.Button(
"Convert to DXF",
variant="primary",
elem_id="convert_btn"
)
with gr.Row():
with gr.Column():
dxf_output = gr.File(
label="DXF Output",
elem_id="dxf_output"
)
with gr.Column():
debug_output = gr.Image(
type="filepath",
label="Debug Preview",
elem_id="debug_output",
height=300
)
# JavaScript for paste handling
demo.load(js="""
function initPasteHandler() {
document.addEventListener('paste', function(e) {
e.preventDefault();
const items = e.clipboardData.items;
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.type.indexOf('image') !== -1) {
const file = item.getAsFile();
const reader = new FileReader();
reader.onload = function(event) {
fetch(event.target.result)
.then(res => res.blob())
.then(blob => {
const file = new File([blob], "pasted_image.png", { type: "image/png" });
const dt = new DataTransfer();
dt.items.add(file);
const fileInput = document.querySelector('#image_input input[type="file"]');
if (fileInput) {
fileInput.files = dt.files;
const event = new Event('change', { 'bubbles': true });
fileInput.dispatchEvent(event);
setTimeout(() => {
const convertBtn = document.querySelector('#convert_btn');
if (convertBtn) convertBtn.click();
}, 500);
}
});
};
reader.readAsDataURL(file);
break;
}
}
});
}
if (document.readyState === 'complete') {
initPasteHandler();
} else {
window.addEventListener('load', initPasteHandler);
}
console.log('Paste handler initialized');
""")
# Event handlers
convert_btn.click(
fn=converter.convert_image,
inputs=[image_input, use_lines_checkbox, size_multiplier, black_white_checkbox],
outputs=[dxf_output, debug_output],
)
return demo
def main():
"""Main entry point with proper error handling."""
try:
demo = create_gradio_interface()
demo.queue()
demo.launch(
server_name="0.0.0.0",
server_port=7860,
show_api=False,
share=False,
)
except Exception as e:
logger.critical(f"Application failed to start: {str(e)}")
raise
if __name__ == "__main__":
main()