luisaflorezm commited on
Commit
5800881
·
verified ·
1 Parent(s): 1827f1e

Upload 2 files

Browse files
Files changed (2) hide show
  1. main_resize.py +287 -0
  2. resize_utils.py +55 -0
main_resize.py ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import cv2
4
+ from PIL import Image
5
+ import os
6
+ import shutil # Para manejar directorios y archivos
7
+
8
+ # Importa tu módulo resize_utils
9
+ # Asegúrate de que 'app' sea el paquete correcto o ajusta la importación
10
+ from app.resize_utils import resize_image_for_print
11
+
12
+ # --- Tema y CSS personalizados ---
13
+ theme = gr.themes.Default(
14
+ primary_hue=gr.themes.Color(
15
+ c50="#e0f2f7", c100="#b3e1ef", c200="#80cee7", c300="#4dbbde", c400="#26ace0",
16
+ c500="#034077", # Tu azul principal
17
+ c600="#023768", c700="#022e59", c800="#01254a", c900="#011b3b", c950="#000f22"
18
+ ),
19
+ secondary_hue=gr.themes.Color(
20
+ c50="#f9fafb", c100="#f4f6f8", c200="#e9ecef", c300="#dee2e6", c400="#ced4da",
21
+ c500="#adb5bd", c600="#889096", c700="#6c757d", c800="#495057", c900="#343a40",
22
+ c950="#212529"
23
+ ),
24
+ neutral_hue=gr.themes.Color(
25
+ c50="#f8f9fa", c100="#f1f3f5", c200="#e9ecef", c300="#dee2e6", c400="#ced4da",
26
+ c500="#adb5bd", c600="#889096", c700="#6c757d", c800="#495057", c900="#343a40",
27
+ c950="#212529"
28
+ )
29
+ ).set(
30
+ button_primary_background_fill="#034077",
31
+ button_primary_background_fill_hover="#023768",
32
+ button_primary_text_color="white",
33
+ checkbox_background_color_selected="#034077",
34
+ checkbox_border_color_selected="#034077",
35
+ slider_color="#034077",
36
+ )
37
+
38
+ css = """
39
+ body {
40
+ background-color: #f0f2f5;
41
+ }
42
+ .gradio-container {
43
+ max-width: 1200px;
44
+ margin: 30px auto;
45
+ border-radius: 15px;
46
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
47
+ background-color: white;
48
+ overflow: hidden;
49
+ }
50
+ h1 {
51
+ color: #034077;
52
+ text-align: center;
53
+ padding-top: 20px;
54
+ margin-bottom: 20px;
55
+ font-size: 2.5em;
56
+ font-weight: 700;
57
+ }
58
+ .gr-text-input, .gr-slider {
59
+ border-radius: 8px;
60
+ }
61
+ .gr-button.primary {
62
+ border-radius: 10px;
63
+ font-weight: bold;
64
+ padding: 10px 20px;
65
+ font-size: 1.1em;
66
+ transition: all 0.3s ease;
67
+ }
68
+ .gr-button.primary:hover {
69
+ transform: translateY(-2px);
70
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
71
+ }
72
+ .gr-checkbox-group label {
73
+ font-weight: 500;
74
+ }
75
+ .gr-accordion {
76
+ border-radius: 10px;
77
+ border: 1px solid #e0e0e0;
78
+ margin-bottom: 15px;
79
+ background-color: #ffffff;
80
+ }
81
+ .gr-accordion.open > .gr-accordion-header {
82
+ border-bottom: 1px solid #e0e0e0;
83
+ }
84
+ .gr-accordion-header {
85
+ background-color: #f8f8f8;
86
+ border-top-left-radius: 10px;
87
+ border-top-right-radius: 10px;
88
+ padding: 15px;
89
+ font-weight: bold;
90
+ color: #034077;
91
+ font-size: 1.1em;
92
+ }
93
+ .gr-accordion-content .gr-checkbox label,
94
+ .gr-accordion-content .gr-slider label,
95
+ .gr-accordion-content .gr-textbox label {
96
+ color: #333333;
97
+ font-weight: 500;
98
+ margin-bottom: 5px;
99
+ }
100
+ .gr-image {
101
+ border: 1px solid #e0e0e0;
102
+ border-radius: 10px;
103
+ background-color: #fdfdfd;
104
+ }
105
+ .output_image_label {
106
+ text-align: center;
107
+ font-weight: bold;
108
+ color: #034077;
109
+ margin-top: 10px;
110
+ font-size: 1.2em;
111
+ }
112
+ """
113
+
114
+ # Directorio temporal para guardar los resultados
115
+ OUTPUT_DIR = "resized_images_output"
116
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
117
+
118
+ def process_images_for_print(
119
+ image_files: list, # Ahora espera una lista de archivos
120
+ input_folder: str, # Ruta a una carpeta
121
+ target_dpi: int,
122
+ longest_side_cm: float
123
+ ) -> tuple: # Devolverá un listado de archivos para descarga y un mensaje
124
+ """
125
+ Procesa una o varias imágenes (o una carpeta) para redimensionar y guarda como TIFF.
126
+ """
127
+ processed_file_paths = []
128
+ output_messages = []
129
+
130
+ # Limpiar el directorio de salida antes de cada procesamiento
131
+ # Comentar esta línea si deseas que los archivos se acumulen entre ejecuciones.
132
+ # Pero para una interfaz de usuario limpia, es mejor borrarlos.
133
+ if os.path.exists(OUTPUT_DIR):
134
+ shutil.rmtree(OUTPUT_DIR)
135
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
136
+
137
+ images_to_process = []
138
+
139
+ # Manejar entrada de carpeta
140
+ if input_folder and os.path.isdir(input_folder):
141
+ output_messages.append(f"Procesando imágenes de la carpeta: {input_folder}")
142
+ for filename in os.listdir(input_folder):
143
+ file_path = os.path.join(input_folder, filename)
144
+ if os.path.isfile(file_path) and filename.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff', '.tif')):
145
+ images_to_process.append(file_path)
146
+
147
+ # Manejar entrada de múltiples archivos (si no se seleccionó carpeta o si también hay archivos individuales)
148
+ # Si se seleccionaron archivos individuales, se añaden a la lista.
149
+ # Gradio `gr.File(file_count="multiple")` devuelve una lista de objetos `tempfile.NamedTemporaryFile`
150
+ if image_files:
151
+ for img_obj in image_files:
152
+ if img_obj is not None:
153
+ images_to_process.append(img_obj.name) # Usar el atributo .name para obtener la ruta del archivo temporal
154
+
155
+ if not images_to_process:
156
+ raise gr.Error("Por favor, sube al menos una imagen o selecciona una carpeta con imágenes.")
157
+
158
+ for i, img_path in enumerate(images_to_process):
159
+ try:
160
+ # Obtener el nombre original del archivo desde la ruta
161
+ original_filename_ext = os.path.basename(img_path)
162
+ original_filename_no_ext = os.path.splitext(original_filename_ext)[0]
163
+
164
+ # Abrir la imagen con PIL
165
+ original_pil_img = Image.open(img_path)
166
+
167
+ # Convertir a BGR numpy array para resize_image_for_print
168
+ img_np_rgb = np.array(original_pil_img)
169
+ img_np_bgr = cv2.cvtColor(img_np_rgb, cv2.COLOR_RGB2BGR)
170
+
171
+ # Aplicar redimensionamiento
172
+ resized_np_bgr = resize_image_for_print(
173
+ img_np_bgr,
174
+ target_dpi=target_dpi,
175
+ target_long_side_cm=longest_side_cm
176
+ )
177
+
178
+ # Convertir de nuevo a PIL Image para guardar como TIFF con DPI
179
+ resized_pil_rgb = Image.fromarray(cv2.cvtColor(resized_np_bgr, cv2.COLOR_BGR2RGB))
180
+
181
+ # Guardar la imagen en formato TIFF con la información de DPI
182
+ output_filename = f"{original_filename_no_ext}_resized.tiff"
183
+ output_filepath = os.path.join(OUTPUT_DIR, output_filename)
184
+
185
+ # PIL guarda DPI en puntos por pulgada (dots per inch)
186
+ # Asegúrate de que los metadatos de DPI se establezcan correctamente al guardar
187
+ resized_pil_rgb.save(output_filepath, dpi=(target_dpi, target_dpi))
188
+ processed_file_paths.append(output_filepath)
189
+ output_messages.append(f"✅ Procesado: {original_filename_ext} -> {output_filename}")
190
+
191
+ except Exception as e:
192
+ output_messages.append(f"❌ Error al procesar {os.path.basename(img_path)}: {e}")
193
+ print(f"Error processing {img_path}: {e}")
194
+
195
+ if not processed_file_paths:
196
+ raise gr.Error("No se pudo procesar ninguna imagen. Revisa los archivos de entrada y los parámetros.")
197
+
198
+ # Gradio requiere que se devuelva una lista de rutas de archivo para gr.File(file_count="multiple")
199
+ # Y un mensaje para el Textbox
200
+ return processed_file_paths, "\n".join(output_messages)
201
+
202
+
203
+ with gr.Blocks(theme=theme, css=css, title="Redimensionador de Imágenes para Impresión") as demo:
204
+ gr.Markdown(
205
+ """
206
+ # 🖼️ **Redimensionador de Imágenes para Impresión**
207
+ Sube tus imágenes o una carpeta, define los parámetros de impresión y obtén tus imágenes redimensionadas en formato TIFF.
208
+ """
209
+ )
210
+ with gr.Row():
211
+ with gr.Column(scale=1):
212
+ with gr.Group(elem_classes="input-card"):
213
+ gr.Markdown("## ⬆️ **Cargar Imágenes**")
214
+
215
+ # Opción para subir múltiples archivos
216
+ image_files_input = gr.File(
217
+ label="Seleccionar una o varias imágenes",
218
+ file_count="multiple", # Permite múltiples archivos
219
+ type="filepath", # *** CORREGIDO A 'filepath' ***
220
+ file_types=[".png", ".jpg", ".jpeg", ".bmp", ".tiff", ".tif"],
221
+ height=200
222
+ )
223
+ gr.Markdown("---")
224
+ # Opción para seleccionar una carpeta
225
+ input_folder_path = gr.Textbox(
226
+ label="O ingresar la ruta a una carpeta con imágenes",
227
+ placeholder="Ej: /ruta/a/mis/fotos (dejar en blanco si subes archivos)",
228
+ interactive=True
229
+ )
230
+ gr.Markdown("*(Si seleccionas archivos y una carpeta, se procesarán AMBOS.)*")
231
+
232
+ with gr.Group(elem_classes="options-card"):
233
+ gr.Markdown("## ⚙️ **Parámetros de Impresión**")
234
+
235
+ longest_side_cm = gr.Slider(
236
+ minimum=1, maximum=200, step=1, value=50,
237
+ label="Lado más largo (cm)",
238
+ info="Define el tamaño físico del lado más largo de la imagen resultante en centímetros.",
239
+ interactive=True
240
+ )
241
+ target_dpi = gr.Slider(
242
+ minimum=72, maximum=1200, step=10, value=300,
243
+ label="DPI (Puntos por pulgada)",
244
+ info="Resolución deseada para impresión (300 DPI es estándar para alta calidad).",
245
+ interactive=True
246
+ )
247
+
248
+ process_button = gr.Button("¡Redimensionar y Guardar como TIFF! 🚀", variant="primary", scale=0)
249
+
250
+ with gr.Column(scale=1):
251
+ with gr.Group(elem_classes="output-card"):
252
+ gr.Markdown("## ✅ **Resultados Procesados**")
253
+
254
+ # Para mostrar el estado del procesamiento
255
+ status_output = gr.Textbox(
256
+ label="Estado del Proceso",
257
+ lines=10,
258
+ interactive=False,
259
+ max_lines=20,
260
+ show_copy_button=True
261
+ )
262
+
263
+ # Para descargar los archivos procesados
264
+ output_files = gr.File(
265
+ label="Imágenes TIFF Redimensionadas",
266
+ file_count="multiple", # Permite descargar múltiples archivos
267
+ type="filepath", # *** CORREGIDO A 'filepath' ***
268
+ interactive=False # No permite al usuario subir archivos aquí
269
+ )
270
+ gr.Markdown("---")
271
+ gr.Markdown("✨*Tus imágenes redimensionadas aparecerán aquí. Haz clic para descargar.*")
272
+
273
+ # Acción del botón
274
+ process_button.click(
275
+ fn=process_images_for_print,
276
+ inputs=[
277
+ image_files_input,
278
+ input_folder_path,
279
+ target_dpi,
280
+ longest_side_cm
281
+ ],
282
+ outputs=[output_files, status_output]
283
+ )
284
+
285
+ # FastAPI + Uvicorn
286
+ if __name__ == '__main__':
287
+ demo.launch(debug=True, show_api=False, server_name="0.0.0.0", server_port=7860)
resize_utils.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import Image
2
+ import numpy as np
3
+ import cv2 # Necesario si vas a trabajar con np arrays en BGR
4
+
5
+ def resize_image_for_print(image_np_bgr, target_dpi=300, target_long_side_cm=50):
6
+ """
7
+ Redimensiona una imagen (numpy array BGR) para impresión a una resolución de 300 dpi
8
+ y un lado más largo de 50 cm, sin alterar la estética.
9
+
10
+ Args:
11
+ image_np_bgr (np.ndarray): Imagen de entrada en formato NumPy BGR.
12
+ target_dpi (int): Resolución deseada en DPI (píxeles por pulgada).
13
+ target_long_side_cm (float): Longitud deseada del lado más largo en centímetros.
14
+
15
+ Returns:
16
+ np.ndarray: La imagen redimensionada en formato NumPy BGR.
17
+ """
18
+ if not isinstance(image_np_bgr, np.ndarray):
19
+ raise TypeError("La entrada debe ser un array de NumPy BGR.")
20
+
21
+ # Convertir de BGR a RGB para PIL si fuera necesario, pero PIL puede trabajar con arrays directamente
22
+ # Para simplicidad y consistencia, vamos a pasar de NumPy BGR a PIL RGB y luego de vuelta a NumPy BGR.
23
+ img_pil_rgb = Image.fromarray(cv2.cvtColor(image_np_bgr, cv2.COLOR_BGR2RGB))
24
+
25
+ # 1. Calcular el tamaño en píxeles basado en el lado más largo deseado y el DPI
26
+ # 1 pulgada = 2.54 cm
27
+ target_long_side_inches = target_long_side_cm / 2.54
28
+ target_long_side_pixels = int(target_long_side_inches * target_dpi)
29
+
30
+ # Obtener el ancho y alto actuales de la imagen PIL
31
+ width, height = img_pil_rgb.size
32
+
33
+ # Determinar cuál es el lado más largo de la imagen original
34
+ if width > height:
35
+ # El ancho es el lado más largo
36
+ new_width = target_long_side_pixels
37
+ new_height = int(height * (new_width / width))
38
+ else:
39
+ # El alto es el lado más largo (o son iguales)
40
+ new_height = target_long_side_pixels
41
+ new_width = int(width * (new_height / height))
42
+
43
+ # 2. Redimensionar la imagen usando un filtro de alta calidad
44
+ resized_img_pil_rgb = img_pil_rgb.resize((new_width, new_height), Image.LANCZOS)
45
+
46
+ # Convertir de nuevo a NumPy BGR
47
+ resized_np_rgb = np.array(resized_img_pil_rgb)
48
+ resized_np_bgr = cv2.cvtColor(resized_np_rgb, cv2.COLOR_RGB2BGR)
49
+
50
+ # Nota: Los metadatos de DPI no se pueden "incrustar" directamente en un np.ndarray.
51
+ # Si realmente necesitas guardarlo con DPI para impresión, la función de guardado
52
+ # debe hacerse fuera de aquí, quizás en el main.py cuando se exporta el resultado final.
53
+ # Por ahora, esta función solo devuelve la imagen redimensionada.
54
+
55
+ return resized_np_bgr