Merlintxu commited on
Commit
206183d
·
1 Parent(s): 4d8dbf7

new file: configs/frame_templates.yaml

Browse files

new file: image_processor/analyzer.py
new file: image_processor/framer.py

configs/frame_templates.yaml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ styles:
2
+ baroque:
3
+ prompt: "Ornate gold leaf frame with floral motifs, intricate carvings, classical Baroque style"
4
+ colors: ["#FFD700", "#FFFFFF", "#704214"]
5
+ mask_size: 760
6
+ elements: ["curves", "scrollwork", "acanthus leaves"]
7
+
8
+ minimalista:
9
+ prompt: "Slim matte black metal frame with clean lines, modern minimalist design"
10
+ colors: ["#000000", "#E0E0E0"]
11
+ mask_size: 900
12
+ elements: ["straight edges", "sharp corners", "flat profile"]
13
+
14
+ abstracto:
15
+ prompt: "Geometric asymmetric frame with bold color blocks, contemporary art style"
16
+ colors: ["#FF0000", "#00FF00", "#0000FF"]
17
+ mask_size: 800
18
+ elements: ["triangles", "circles", "irregular shapes"]
image_processor/analyzer.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import pipeline
2
+ import torch
3
+ from PIL import Image
4
+ import numpy as np
5
+ import logging
6
+
7
+ class ImageAnalyzer:
8
+ def __init__(self, device="cuda" if torch.cuda.is_available() else "cpu"):
9
+ self.device = device
10
+ self.logger = logging.getLogger(__name__)
11
+ self.models = self._load_models()
12
+
13
+ def _load_models(self):
14
+ try:
15
+ return {
16
+ 'captioning': pipeline(
17
+ "image-to-text",
18
+ model="Salesforce/blip2-opt-2.7b",
19
+ device=self.device,
20
+ torch_dtype=torch.float16 if 'cuda' in self.device else torch.float32
21
+ ),
22
+ 'art_analysis': pipeline(
23
+ "text-generation",
24
+ model="ArtGAN/art-critique-generator",
25
+ device=self.device
26
+ ),
27
+ 'color_detector': pipeline(
28
+ "image-classification",
29
+ model="google/color-detector",
30
+ device=self.device
31
+ ),
32
+ 'style_classifier': pipeline(
33
+ "image-classification",
34
+ model="dima806/art_painting_style_detection",
35
+ device=self.device
36
+ )
37
+ }
38
+ except Exception as e:
39
+ self.logger.error(f"Error loading models: {str(e)}")
40
+ raise
41
+
42
+ def analyze_image(self, image):
43
+ try:
44
+ if isinstance(image, (str, bytes)):
45
+ image = Image.open(image)
46
+
47
+ results = {}
48
+
49
+ # Captioning
50
+ caption = self.models['captioning'](
51
+ image,
52
+ max_new_tokens=100,
53
+ generate_kwargs={"do_sample": False}
54
+ )
55
+ results.update(self._parse_caption(caption))
56
+
57
+ # Color detection
58
+ results['colors'] = self._get_colors(image)
59
+
60
+ # Style classification
61
+ style = self.models['style_classifier'](image)[0]
62
+ results['style'] = style['label']
63
+ results['style_confidence'] = style['score']
64
+
65
+ # Art analysis
66
+ art_prompt = f"Analyze this {results['style']} artwork: {results['description']}"
67
+ results['art_commentary'] = self.models['art_analysis'](
68
+ art_prompt,
69
+ max_new_tokens=200
70
+ )[0]['generated_text']
71
+
72
+ return results
73
+
74
+ except Exception as e:
75
+ self.logger.error(f"Analysis failed: {str(e)}")
76
+ return None
77
+
78
+ def _parse_caption(self, caption_output):
79
+ full_text = caption_output[0]['generated_text']
80
+ parts = full_text.split('.', 1)
81
+ return {
82
+ 'title': parts[0].strip(),
83
+ 'description': parts[1].strip() if len(parts) > 1 else full_text
84
+ }
85
+
86
+ def _get_colors(self, image):
87
+ colors = self.models['color_detector'](
88
+ image.resize((256, 256)),
89
+ top_k=5
90
+ )
91
+ return [{
92
+ 'hex': c['label'],
93
+ 'score': round(float(c['score']), 3)
94
+ } for c in colors]
image_processor/framer.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import openai
2
+ import time
3
+ import logging
4
+ from tenacity import retry, wait_exponential, stop_after_attempt
5
+ from PIL import Image
6
+ import requests
7
+ from io import BytesIO
8
+ import yaml
9
+
10
+ class FrameGenerator:
11
+ def __init__(self, api_key, config_path='configs/frame_templates.yaml'):
12
+ openai.api_key = api_key
13
+ self.logger = logging.getLogger(__name__)
14
+ self.templates = self._load_templates(config_path)
15
+ self.rate_limit = 5 # Llamadas por minuto
16
+ self.last_call = 0
17
+
18
+ def _load_templates(self, config_path):
19
+ try:
20
+ with open(config_path) as f:
21
+ return yaml.safe_load(f)['styles']
22
+ except Exception as e:
23
+ self.logger.error(f"Error loading templates: {str(e)}")
24
+ return {}
25
+
26
+ @retry(
27
+ wait=wait_exponential(multiplier=1, min=4, max=60),
28
+ stop=stop_after_attempt(3),
29
+ reraise=True
30
+ )
31
+ def generate_frame(self, image_url, metadata):
32
+ self._throttle_requests()
33
+ style = metadata.get('style', 'minimalista')
34
+
35
+ template = self.templates.get(style, self.templates['minimalista'])
36
+ prompt = self._build_prompt(template, metadata)
37
+
38
+ try:
39
+ response = openai.images.generate(
40
+ model="dall-e-3",
41
+ prompt=prompt,
42
+ size="1024x1024",
43
+ quality="hd",
44
+ n=1,
45
+ response_format="url"
46
+ )
47
+
48
+ return self._composite_frame(
49
+ image_url,
50
+ response.data[0].url,
51
+ template['mask_size']
52
+ )
53
+
54
+ except openai.RateLimitError:
55
+ self.logger.warning("Rate limit exceeded, retrying...")
56
+ time.sleep(60)
57
+ raise
58
+ except Exception as e:
59
+ self.logger.error(f"Generation failed: {str(e)}")
60
+ return None
61
+
62
+ def _build_prompt(self, template, metadata):
63
+ color_str = ", ".join(metadata['colors'][:3])
64
+ return (
65
+ f"High-quality frame for {metadata['style']} painting, "
66
+ f"main colors: {color_str}. {template['prompt']} "
67
+ "No text, no signatures, pure decorative frame."
68
+ )
69
+
70
+ def _throttle_requests(self):
71
+ elapsed = time.time() - self.last_call
72
+ if elapsed < 60 / self.rate_limit:
73
+ time.sleep(60 / self.rate_limit - elapsed)
74
+ self.last_call = time.time()
75
+
76
+ def _composite_frame(self, original_url, frame_url, mask_size=800):
77
+ try:
78
+ original = Image.open(requests.get(original_url, stream=True).raw)
79
+ frame = Image.open(requests.get(frame_url, stream=True).raw)
80
+
81
+ original = original.resize((mask_size, mask_size))
82
+ position = ((frame.width - original.width) // 2,
83
+ (frame.height - original.height) // 2)
84
+
85
+ composite = frame.copy()
86
+ composite.paste(original, position)
87
+
88
+ return composite
89
+
90
+ except Exception as e:
91
+ self.logger.error(f"Compositing failed: {str(e)}")
92
+ return None