Frost217 commited on
Commit
4216565
·
1 Parent(s): 86a87a9

Add initial FastAPI application with Docker setup and environment configuration

Browse files
Files changed (7) hide show
  1. Docker.fastapi +42 -0
  2. Dockerfile +29 -0
  3. app.py +487 -0
  4. docker-compose.yml +18 -0
  5. environment.env +3 -0
  6. requirements.txt +13 -0
  7. start.sh +17 -0
Docker.fastapi ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ # Install system dependencies with security updates
4
+ RUN apt-get update && apt-get install -y \
5
+ curl \
6
+ && apt-get upgrade -y \
7
+ && apt-get clean \
8
+ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
9
+
10
+ # Create non-root user for security
11
+ RUN groupadd -r appuser && useradd -r -g appuser -m appuser
12
+
13
+ # Set working directory
14
+ WORKDIR /app
15
+
16
+ # Copy requirements and install Python packages
17
+ COPY requirements.txt .
18
+ RUN pip3 install --no-cache-dir --upgrade pip && \
19
+ pip3 install --no-cache-dir -r requirements.txt
20
+
21
+ # Copy FastAPI application
22
+ COPY app.py .
23
+
24
+ # Create logs directory and change ownership
25
+ RUN mkdir -p /app/logs && \
26
+ chown -R appuser:appuser /app
27
+
28
+ # Set environment variables
29
+ ENV HOME=/home/appuser
30
+
31
+ # Switch to non-root user
32
+ USER appuser
33
+
34
+ # Expose port
35
+ EXPOSE 7860
36
+
37
+ # Health check
38
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
39
+ CMD curl -f http://localhost:7860/health || exit 1
40
+
41
+ # Start FastAPI application
42
+ CMD ["python3", "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
Dockerfile ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Base image
2
+ FROM python:3.10-slim
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies
8
+ RUN apt-get update && apt-get install -y \
9
+ git \
10
+ ffmpeg \
11
+ libgl1 \
12
+ libglib2.0-0 \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ # Install Python dependencies
16
+ COPY requirement.txt .
17
+ RUN pip install --no-cache-dir -r requirements.txt
18
+
19
+ # Copy app code
20
+ COPY app.py .
21
+
22
+ # Copy .env if present (optional, for Cloudinary config)
23
+ # COPY .env .
24
+
25
+ # Expose the port used by FastAPI (default: 7860)
26
+ EXPOSE 7860
27
+
28
+ # Start the FastAPI app with uvicorn
29
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,487 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from pydantic import BaseModel, HttpUrl
3
+ from transformers import SegformerImageProcessor, AutoModelForSemanticSegmentation
4
+ from PIL import Image, ImageEnhance, ImageFilter
5
+ import torch.nn as nn
6
+ import torch
7
+ import cv2
8
+ import numpy as np
9
+ import os
10
+ import requests
11
+ import io
12
+ from datetime import datetime
13
+ from scipy import ndimage
14
+ import json
15
+ import tempfile
16
+ import shutil
17
+ from typing import List, Dict, Optional
18
+ import uuid
19
+ import asyncio
20
+ from concurrent.futures import ThreadPoolExecutor
21
+ import logging
22
+ import cloudinary
23
+ import cloudinary.uploader
24
+ from cloudinary.utils import cloudinary_url
25
+ import os
26
+ from dotenv import load_dotenv
27
+
28
+
29
+ # Configure logging
30
+ logging.basicConfig(level=logging.INFO)
31
+ logger = logging.getLogger(__name__)
32
+
33
+ app = FastAPI(title="Fashion Segmentation API", version="1.0.0")
34
+
35
+ # Request/Response models
36
+ class SegmentationRequest(BaseModel):
37
+ image_url: HttpUrl
38
+ settings: Optional[Dict] = {
39
+ "padding": 15,
40
+ "background": "white",
41
+ "quality": "high",
42
+ "outline": "grey_2px"
43
+ }
44
+
45
+ class SegmentInfo(BaseModel):
46
+ class_id: int
47
+ class_name: str
48
+ filename: str
49
+ category: str
50
+ pixel_count: int
51
+ coverage_percent: float
52
+ cloudinary_url: str
53
+ public_id: str
54
+
55
+ class SegmentationResponse(BaseModel):
56
+ success: bool
57
+ processing_time: float
58
+ total_segments: int
59
+ segments: List[SegmentInfo]
60
+ metadata: Dict
61
+
62
+ # Global model storage
63
+ model_cache = {}
64
+ executor = ThreadPoolExecutor(max_workers=4)
65
+
66
+ # Constants
67
+ SEGFORMER_LABELS = {
68
+ 0: "Background", 1: "Hat", 2: "Hair", 3: "Sunglasses", 4: "Upper-clothes",
69
+ 5: "Skirt", 6: "Pants", 7: "Dress", 8: "Belt", 9: "Left-shoe", 10: "Right-shoe",
70
+ 11: "Face", 12: "Left-leg", 13: "Right-leg", 14: "Left-arm", 15: "Right-arm",
71
+ 16: "Bag", 17: "Scarf"
72
+ }
73
+
74
+ CLOTHING_ITEMS = {4, 5, 6, 7, 8, 17} # Upper-clothes, Skirt, Pants, Dress, Belt, Scarf
75
+ ACCESSORIES = {1, 3, 9, 10, 16} # Hat, Sunglasses, Left-shoe, Right-shoe, Bag
76
+ BODY_PARTS = {2, 11, 12, 13, 14, 15} # Hair, Face, Left-leg, Right-leg, Left-arm, Right-arm
77
+
78
+
79
+ load_dotenv()
80
+
81
+ # Cloudinary Configuration
82
+ CLOUDINARY_CONFIG = {
83
+ "cloud_name": os.getenv("CLOUDINARY_CLOUD_NAME"),
84
+ "api_key": os.getenv("CLOUDINARY_API_KEY"),
85
+ "api_secret": os.getenv("CLOUDINARY_API_SECRET")
86
+ }
87
+
88
+ # Configure Cloudinary
89
+ cloudinary.config(
90
+ cloud_name=CLOUDINARY_CONFIG["cloud_name"],
91
+ api_key=CLOUDINARY_CONFIG["api_key"],
92
+ api_secret=CLOUDINARY_CONFIG["api_secret"],
93
+ secure=True
94
+ )
95
+
96
+ async def load_model():
97
+ """Load the segmentation model asynchronously"""
98
+ if "model" not in model_cache:
99
+ logger.info("Loading SegFormer model...")
100
+ try:
101
+ processor = SegformerImageProcessor.from_pretrained("mattmdjaga/segformer_b2_clothes")
102
+ model = AutoModelForSemanticSegmentation.from_pretrained("mattmdjaga/segformer_b2_clothes")
103
+ model_cache["processor"] = processor
104
+ model_cache["model"] = model
105
+ logger.info("Model loaded successfully!")
106
+ except Exception as e:
107
+ logger.error(f"Model loading failed: {e}")
108
+ raise HTTPException(status_code=500, detail=f"Model loading failed: {e}")
109
+
110
+ return model_cache["processor"], model_cache["model"]
111
+
112
+ def download_image(url: str) -> Image.Image:
113
+ """Download image from URL"""
114
+ try:
115
+ response = requests.get(str(url), timeout=30)
116
+ response.raise_for_status()
117
+
118
+ image = Image.open(io.BytesIO(response.content))
119
+ if image.mode != 'RGB':
120
+ image = image.convert('RGB')
121
+
122
+
123
+ print("Image downloaaded succcessfully:",url)
124
+
125
+ return image
126
+ except Exception as e:
127
+ raise HTTPException(status_code=400, detail=f"Failed to download image: {e}")
128
+
129
+ def enhance_image_quality(image):
130
+ """Enhance image quality for high-quality output"""
131
+ if isinstance(image, np.ndarray):
132
+ if len(image.shape) == 3 and image.shape[2] == 3:
133
+ image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
134
+ pil_image = Image.fromarray(image)
135
+ else:
136
+ pil_image = image
137
+
138
+ # High quality enhancement
139
+ pil_image = pil_image.filter(ImageFilter.UnsharpMask(radius=2, percent=150, threshold=3))
140
+ enhancer = ImageEnhance.Sharpness(pil_image)
141
+ pil_image = enhancer.enhance(1.3)
142
+ enhancer = ImageEnhance.Contrast(pil_image)
143
+ pil_image = enhancer.enhance(1.15)
144
+ enhancer = ImageEnhance.Color(pil_image)
145
+ pil_image = enhancer.enhance(1.1)
146
+
147
+ return cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR)
148
+
149
+ def get_category_folder(class_id):
150
+ """Get appropriate folder for class"""
151
+ if class_id in CLOTHING_ITEMS:
152
+ return "clothing"
153
+ elif class_id in ACCESSORIES:
154
+ return "accessories"
155
+ elif class_id in BODY_PARTS:
156
+ return "body_parts"
157
+ else:
158
+ return "clothing" # default
159
+
160
+ def upload_to_cloudinary(file_path: str, public_id: str, folder: str = "fashion_segments") -> Dict:
161
+ """Upload file to Cloudinary and return response with URLs"""
162
+ try:
163
+ # Upload to Cloudinary
164
+ upload_result = cloudinary.uploader.upload(
165
+ file_path,
166
+ public_id=f"{folder}/{public_id}",
167
+ folder=folder,
168
+ resource_type="image",
169
+ format="png",
170
+ quality="auto:best",
171
+ fetch_format="auto"
172
+ )
173
+
174
+ # Generate optimized URL
175
+ optimized_url, _ = cloudinary_url(
176
+ upload_result['public_id'],
177
+ format="png",
178
+ quality="auto:best",
179
+ fetch_format="auto"
180
+ )
181
+
182
+ return {
183
+ 'url': upload_result.get('secure_url', upload_result.get('url')),
184
+ 'optimized_url': optimized_url,
185
+ 'public_id': upload_result['public_id'],
186
+ 'version': upload_result.get('version'),
187
+ 'format': upload_result.get('format'),
188
+ 'width': upload_result.get('width'),
189
+ 'height': upload_result.get('height'),
190
+ 'bytes': upload_result.get('bytes')
191
+ }
192
+
193
+ except Exception as e:
194
+ logger.error(f"Cloudinary upload failed: {e}")
195
+ raise HTTPException(status_code=500, detail=f"Upload failed: {e}")
196
+
197
+ def process_segmentation(image: Image.Image, processor, model, settings: Dict) -> tuple:
198
+ """Process image segmentation"""
199
+ # Process with model
200
+ inputs = processor(images=image, return_tensors="pt")
201
+ outputs = model(**inputs)
202
+ logits = outputs.logits.cpu()
203
+
204
+ # Resize to original image size
205
+ upsampled_logits = nn.functional.interpolate(
206
+ logits,
207
+ size=image.size[::-1], # height, width
208
+ mode="bilinear",
209
+ align_corners=False,
210
+ )
211
+
212
+ pred_seg = upsampled_logits.argmax(dim=1)[0]
213
+
214
+ # Extract bounding boxes
215
+ unique_classes = torch.unique(pred_seg)
216
+ segment_data = {}
217
+ total_pixels = pred_seg.numel()
218
+
219
+ for class_id in unique_classes:
220
+ coords = torch.where(pred_seg == class_id)
221
+ y_coords = coords[0].numpy()
222
+ x_coords = coords[1].numpy()
223
+
224
+ min_x, max_x = int(x_coords.min()), int(x_coords.max())
225
+ min_y, max_y = int(y_coords.min()), int(y_coords.max())
226
+ pixel_count = len(x_coords)
227
+ coverage = (pixel_count / total_pixels) * 100
228
+
229
+ segment_data[int(class_id)] = {
230
+ 'bbox': (min_x, min_y, max_x, max_y),
231
+ 'pixel_count': pixel_count,
232
+ 'coverage_percent': coverage
233
+ }
234
+
235
+ return pred_seg, segment_data
236
+
237
+ def extract_segments(image: Image.Image, pred_seg, segment_data: Dict, settings: Dict) -> List[Dict]:
238
+ """Extract individual segments and upload to Cloudinary"""
239
+ image_np = np.array(image)
240
+ image_bgr = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
241
+ label_map = pred_seg.numpy().astype(np.uint8)
242
+ h, w = label_map.shape
243
+
244
+ extracted_segments = []
245
+ padding = settings.get("padding", 15)
246
+
247
+ # Create temporary directory for processing
248
+ temp_dir = tempfile.mkdtemp()
249
+ session_id = str(uuid.uuid4())[:8] # Shorter session ID
250
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
251
+
252
+ try:
253
+ for class_id, info in segment_data.items():
254
+ if class_id == 0: # Skip background
255
+ continue
256
+
257
+ x1, y1, x2, y2 = info['bbox']
258
+
259
+ # Apply padding
260
+ x1 = max(0, x1 - padding)
261
+ y1 = max(0, y1 - padding)
262
+ x2 = min(w - 1, x2 + padding)
263
+ y2 = min(h - 1, y2 + padding)
264
+
265
+ # Enhanced mask processing
266
+ mask = (label_map == class_id).astype(np.uint8)
267
+ mask_filled = ndimage.binary_fill_holes(mask).astype(np.uint8)
268
+
269
+ # Adaptive kernel size
270
+ segment_area = np.sum(mask_filled)
271
+ kernel_size = max(3, min(7, int(np.sqrt(segment_area) / 100)))
272
+ kernel = np.ones((kernel_size, kernel_size), np.uint8)
273
+
274
+ mask_cleaned = cv2.morphologyEx(mask_filled, cv2.MORPH_CLOSE, kernel, iterations=2)
275
+ mask_cleaned = cv2.morphologyEx(mask_cleaned, cv2.MORPH_OPEN, kernel, iterations=1)
276
+
277
+ # Smooth edges
278
+ mask_smooth = cv2.GaussianBlur(mask_cleaned.astype(np.float32), (3, 3), 1.0)
279
+
280
+ # Crop
281
+ cropped_mask_smooth = mask_smooth[y1:y2+1, x1:x2+1]
282
+ cropped_image = image_bgr[y1:y2+1, x1:x2+1]
283
+
284
+ # Create white background with grey outline
285
+ background = np.full(cropped_image.shape, 248, dtype=np.uint8)
286
+ mask_uint8 = (cropped_mask_smooth * 255).astype(np.uint8)
287
+ contours, _ = cv2.findContours(mask_uint8, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
288
+
289
+ outline_mask = np.zeros_like(mask_uint8)
290
+ cv2.drawContours(outline_mask, contours, -1, 255, thickness=2)
291
+ kernel = np.ones((3,3), np.uint8)
292
+ outline_mask = cv2.dilate(outline_mask, kernel, iterations=1)
293
+
294
+ mask_3d = np.stack([cropped_mask_smooth] * 3, axis=2)
295
+ outline_3d = np.stack([outline_mask/255.0] * 3, axis=2)
296
+ grey_color = np.full(cropped_image.shape, 128, dtype=np.uint8)
297
+
298
+ # Composite image
299
+ final_image = (cropped_image * mask_3d +
300
+ grey_color * outline_3d * (1 - mask_3d) +
301
+ background * (1 - mask_3d) * (1 - outline_3d)).astype(np.uint8)
302
+
303
+ # Enhance quality
304
+ final_image = enhance_image_quality(final_image)
305
+
306
+ # Save temporarily
307
+ class_name = SEGFORMER_LABELS.get(class_id, f"Class_{class_id}")
308
+ category_folder = get_category_folder(class_id)
309
+ filename = f"{class_id:02d}_{class_name.replace(' ', '_')}_{info['pixel_count']}px.png"
310
+
311
+ temp_filepath = os.path.join(temp_dir, filename)
312
+ cv2.imwrite(temp_filepath, final_image)
313
+
314
+ # Create public_id for Cloudinary
315
+ public_id = f"{timestamp}_{session_id}_{category_folder}_{class_id:02d}_{class_name.replace(' ', '_')}"
316
+
317
+ # Upload to Cloudinary
318
+ cloudinary_result = upload_to_cloudinary(
319
+ temp_filepath,
320
+ public_id,
321
+ folder=f"fashion_segments/{category_folder}"
322
+ )
323
+
324
+ extracted_segments.append({
325
+ 'class_id': class_id,
326
+ 'class_name': class_name,
327
+ 'filename': filename,
328
+ 'category': category_folder,
329
+ 'pixel_count': info['pixel_count'],
330
+ 'coverage_percent': info['coverage_percent'],
331
+ 'cloudinary_url': cloudinary_result['optimized_url'],
332
+ 'public_id': cloudinary_result['public_id']
333
+ })
334
+
335
+ logger.info(f"Extracted and uploaded: {class_name} ({info['pixel_count']:,} pixels, {info['coverage_percent']:.1f}% coverage)")
336
+
337
+ finally:
338
+ # Cleanup temporary directory
339
+ shutil.rmtree(temp_dir, ignore_errors=True)
340
+
341
+ return extracted_segments
342
+
343
+ @app.on_event("startup")
344
+ async def startup_event():
345
+ """Load model on startup"""
346
+ await load_model()
347
+
348
+ @app.get("/")
349
+ async def root():
350
+ return {"message": "Fashion Segmentation API with Cloudinary is running!", "version": "1.0.0"}
351
+
352
+ @app.get("/health")
353
+ async def health_check():
354
+ return {
355
+ "status": "healthy",
356
+ "model_loaded": "model" in model_cache,
357
+ "cloudinary_configured": bool(CLOUDINARY_CONFIG["cloud_name"])
358
+ }
359
+
360
+ @app.post("/segment", response_model=SegmentationResponse)
361
+ async def segment_fashion_items(request: SegmentationRequest):
362
+ """
363
+ Segment fashion items from an image URL and return Cloudinary URLs for extracted segments
364
+ """
365
+ start_time = datetime.now()
366
+
367
+ try:
368
+ # Load model
369
+ processor, model = await load_model()
370
+
371
+ # Download image
372
+ logger.info(f"Downloading image from: {request.image_url}")
373
+ image = download_image(request.image_url)
374
+ original_size = image.size
375
+
376
+ # Process segmentation in thread pool
377
+ loop = asyncio.get_event_loop()
378
+ pred_seg, segment_data = await loop.run_in_executor(
379
+ executor, process_segmentation, image, processor, model, request.settings
380
+ )
381
+
382
+ # Extract segments and upload to Cloudinary
383
+ extracted_segments = await loop.run_in_executor(
384
+ executor, extract_segments, image, pred_seg, segment_data, request.settings
385
+ )
386
+
387
+ # Calculate processing time
388
+ end_time = datetime.now()
389
+ processing_time = (end_time - start_time).total_seconds()
390
+
391
+ # Prepare response
392
+ segments = [SegmentInfo(**segment) for segment in extracted_segments]
393
+
394
+ metadata = {
395
+ 'processing_time': processing_time,
396
+ 'image_size': original_size,
397
+ 'total_segments': len(segments),
398
+ 'settings': request.settings,
399
+ 'timestamp': datetime.now().isoformat(),
400
+ 'storage_provider': 'cloudinary'
401
+ }
402
+
403
+ logger.info(f"Processing complete: {len(segments)} segments extracted and uploaded in {processing_time:.2f}s")
404
+
405
+ return SegmentationResponse(
406
+ success=True,
407
+ processing_time=processing_time,
408
+ total_segments=len(segments),
409
+ segments=segments,
410
+ metadata=metadata
411
+ )
412
+
413
+ except Exception as e:
414
+ logger.error(f"Processing failed: {e}")
415
+ return SegmentationResponse(
416
+ success=False,
417
+ processing_time=(datetime.now() - start_time).total_seconds(),
418
+ total_segments=0,
419
+ segments=[],
420
+ metadata={"error": str(e), "storage_provider": "cloudinary"}
421
+ )
422
+
423
+ @app.post("/segment/batch")
424
+ async def segment_multiple_images(image_urls: List[HttpUrl]):
425
+ """
426
+ Process multiple images in batch
427
+ """
428
+ results = []
429
+
430
+ for url in image_urls:
431
+ try:
432
+ request = SegmentationRequest(image_url=url)
433
+ result = await segment_fashion_items(request)
434
+ results.append({"url": str(url), "result": result})
435
+ except Exception as e:
436
+ results.append({"url": str(url), "error": str(e)})
437
+
438
+ return {"batch_results": results}
439
+
440
+ @app.delete("/segment/{public_id}")
441
+ async def delete_segment(public_id: str):
442
+ """
443
+ Delete a segment from Cloudinary by public_id
444
+ """
445
+ try:
446
+ result = cloudinary.uploader.destroy(public_id)
447
+ return {"success": True, "result": result}
448
+ except Exception as e:
449
+ logger.error(f"Failed to delete {public_id}: {e}")
450
+ raise HTTPException(status_code=500, detail=f"Deletion failed: {e}")
451
+
452
+ @app.get("/segment/transform/{public_id}")
453
+ async def get_transformed_url(
454
+ public_id: str,
455
+ width: Optional[int] = None,
456
+ height: Optional[int] = None,
457
+ quality: Optional[str] = "auto",
458
+ format: Optional[str] = "auto"
459
+ ):
460
+ """
461
+ Get a transformed URL for a segment with specified dimensions and quality
462
+ """
463
+ try:
464
+ transformations = {
465
+ "quality": quality,
466
+ "fetch_format": format
467
+ }
468
+
469
+ if width:
470
+ transformations["width"] = width
471
+ if height:
472
+ transformations["height"] = height
473
+
474
+ url, options = cloudinary_url(public_id, **transformations)
475
+
476
+ return {
477
+ "original_public_id": public_id,
478
+ "transformed_url": url,
479
+ "transformations": transformations
480
+ }
481
+ except Exception as e:
482
+ logger.error(f"Failed to generate transformed URL: {e}")
483
+ raise HTTPException(status_code=500, detail=f"URL generation failed: {e}")
484
+
485
+ if __name__ == "__main__":
486
+ import uvicorn
487
+ uvicorn.run(app, host="0.0.0.0", port=7860)
docker-compose.yml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ fastapi:
3
+ build:
4
+ context: .
5
+ dockerfile: Dockerfile.fastapi
6
+ container_name: fashion-analyzer-api
7
+ restart: unless-stopped
8
+ ports:
9
+
10
+ "7860:7860"
11
+ networks:
12
+ dress-segmentation
13
+ volumes:
14
+ ./logs:/app/logs
15
+
16
+ networks:
17
+ dress-segmentation:
18
+ driver: bridge
environment.env ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ CLOUDINARY_CLOUD_NAME=dyvuvklpk
2
+ CLOUDINARY_API_KEY=526511578726322
3
+ CLOUDINARY_API_SECRET=vNX68D7DcmIS6qUupIJgWFRK6Cc
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ transformers
4
+ torch
5
+ torchvision
6
+ pillow
7
+ opencv-python
8
+ numpy
9
+ scipy
10
+ requests
11
+ pydantic
12
+ cloudinary
13
+ python-multipart
start.sh ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ Debug information,
4
+ echo "=== Debug Information ==="
5
+ echo "Current user: $(whoami)"
6
+ echo "User ID: $(id)"
7
+ echo "Current directory: $(pwd)"
8
+ echo "Home directory: $HOME"
9
+ echo "PATH: $PATH"
10
+ echo "========================="
11
+
12
+ echo "Starting Fashion Analyzer with transformers-based AI models..."
13
+
14
+ Start FastAPI on port 7860 (HF Spaces requirement),
15
+ echo "Starting FastAPI server on port 7860..."
16
+ cd /app
17
+ python3 app.py