Dell commited on
Commit
6648d87
·
1 Parent(s): ac51e43
.history/README_20260615121735.md ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: ChitraTech Virtual Try-On
3
+ emoji: 👕
4
+ colorFrom: pink
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: 6.5.1
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ ---
12
+
13
+ # ChitraTech Virtual Try-On
14
+
15
+ This repository is a Hugging Face Space for an on-demand virtual try-on experience powered by **CatVTON**.
16
+
17
+ ## Planned flow
18
+
19
+ 1. A shopper clicks **Try On** on your ecommerce product page.
20
+ 2. Your frontend sends the shopper photo and the selected clothing image to this Hugging Face Space.
21
+ 3. The Space runs CatVTON inference only for that request.
22
+ 4. The generated try-on image is returned to the frontend for preview.
23
+
24
+ ## Current status
25
+
26
+ The current `app.py` is prepared as a **CatVTON-ready Gradio shell**:
27
+ - accepts a person image and a garment image,
28
+ - keeps preprocessing inside the Space,
29
+ - uses lazy pipeline loading for on-demand inference,
30
+ - includes clear placeholders where CatVTON model loading and inference should be added.
31
+
32
+ ## Next steps to finish the Space
33
+
34
+ - clone or copy the CatVTON inference code into this repository,
35
+ - add a `requirements.txt` with CatVTON and runtime dependencies,
36
+ - place model weights in a folder such as `checkpoints/CatVTON` or download them during startup,
37
+ - replace the placeholder pipeline in `app.py` with actual CatVTON inference logic,
38
+ - push updates to Git so Hugging Face rebuilds the Space automatically.
39
+
40
+ ## Frontend integration
41
+
42
+ Your ecommerce frontend can call the Hugging Face Space directly when needed, making this a free, on-demand approach without keeping a GPU server running continuously.
43
+
44
+ ## Recommended repository additions
45
+
46
+ You will likely want to add:
47
+ - `requirements.txt`
48
+ - `checkpoints/` or startup download logic
49
+ - optional `examples/` assets for demo inputs
50
+ - optional `utils/` or `catvton/` helper modules
51
+
52
+ ## Note
53
+
54
+ If you want, the next step is to wire real CatVTON inference into `app.py` and add the missing dependency files for Hugging Face Spaces deployment.
.history/README_20260615121752.md ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: ChitraTech Virtual Try-On
3
+ emoji: 👕
4
+ colorFrom: pink
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: 6.5.1
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ ---
12
+
13
+ # ChitraTech Virtual Try-On
14
+
15
+ This repository is a Hugging Face Space for an on-demand virtual try-on experience powered by **CatVTON**.
16
+
17
+ ## Planned flow
18
+
19
+ 1. A shopper clicks **Try On** on your ecommerce product page.
20
+ 2. Your frontend sends the shopper photo and the selected clothing image to this Hugging Face Space.
21
+ 3. The Space runs CatVTON inference only for that request.
22
+ 4. The generated try-on image is returned to the frontend for preview.
23
+
24
+ ## Current status
25
+
26
+ The current `app.py` is prepared as a **CatVTON-ready Gradio shell**:
27
+ - accepts a person image and a garment image,
28
+ - keeps preprocessing inside the Space,
29
+ - uses lazy pipeline loading for on-demand inference,
30
+ - includes clear placeholders where CatVTON model loading and inference should be added.
31
+
32
+ ## Next steps to finish the Space
33
+
34
+ - clone or copy the CatVTON inference code into this repository,
35
+ - add a `requirements.txt` with CatVTON and runtime dependencies,
36
+ - place model weights in a folder such as `checkpoints/CatVTON` or download them during startup,
37
+ - replace the placeholder pipeline in `app.py` with actual CatVTON inference logic,
38
+ - push updates to Git so Hugging Face rebuilds the Space automatically.
39
+
40
+ ## Frontend integration
41
+
42
+ Your ecommerce frontend can call the Hugging Face Space directly when needed, making this a free, on-demand approach without keeping a GPU server running continuously.
43
+
44
+ ## Recommended repository additions
45
+
46
+ You will likely want to add:
47
+ - `requirements.txt`
48
+ - `checkpoints/` or startup download logic
49
+ - optional `examples/` assets for demo inputs
50
+ - optional `utils/` or `catvton/` helper modules
51
+
52
+ ## Note
53
+
54
+ If you want, the next step is to wire real CatVTON inference into `app.py` and add the missing dependency files for Hugging Face Spaces deployment.
.history/README_20260615121943.md ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: ChitraTech Virtual Try-On
3
+ emoji: 👕
4
+ colorFrom: pink
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: 6.5.1
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ ---
12
+
13
+ # ChitraTech Virtual Try-On
14
+
15
+ This repository is a Hugging Face Space for an on-demand virtual try-on experience powered by **CatVTON**.
16
+
17
+ ## Planned flow
18
+
19
+ 1. A shopper clicks **Try On** on your ecommerce product page.
20
+ 2. Your frontend sends the shopper photo and the selected clothing image to this Hugging Face Space.
21
+ 3. The Space runs CatVTON inference only for that request.
22
+ 4. The generated try-on image is returned to the frontend for preview.
23
+
24
+ ## Current status
25
+
26
+ The current Space now includes:
27
+ - a Gradio interface for person and garment uploads,
28
+ - lazy pipeline initialization for on-demand inference,
29
+ - automatic detection of CatVTON code under `CatVTON/`,
30
+ - automatic detection of model weights under `checkpoints/CatVTON/`,
31
+ - a safe demo fallback when CatVTON is not fully installed yet.
32
+
33
+ ## Repository structure
34
+
35
+ Recommended layout:
36
+ - `app.py`
37
+ - `requirements.txt`
38
+ - `CatVTON/` for the cloned CatVTON repository
39
+ - `checkpoints/CatVTON/` for model weights
40
+ - optional `examples/` for demo assets
41
+
42
+ ## Setup steps
43
+
44
+ ### 1. Add CatVTON source code
45
+
46
+ Clone or copy your CatVTON implementation into:
47
+ - `CatVTON/`
48
+
49
+ If your fork uses a different folder, set:
50
+ - `CATVTON_REPO_DIR`
51
+
52
+ ### 2. Add model weights
53
+
54
+ Place CatVTON checkpoints in:
55
+ - `checkpoints/CatVTON/`
56
+
57
+ Or set:
58
+ - `CATVTON_MODEL_DIR`
59
+
60
+ ### 3. Confirm the inference entrypoint
61
+
62
+ The current app tries common Python and script entrypoints automatically. If your CatVTON fork uses different class names, function names, or CLI arguments, update the loading logic in `app.py`.
63
+
64
+ ### 4. Push to Hugging Face Spaces
65
+
66
+ After adding the CatVTON repo and weights references, push the repository. Hugging Face will rebuild the Space automatically.
67
+
68
+ ## Frontend integration
69
+
70
+ Your ecommerce frontend can call the Hugging Face Space directly when needed, making this a free, on-demand approach without keeping a GPU server running continuously.
71
+
72
+ ## Important note
73
+
74
+ This app is now deployment-ready as a shell, but **real try-on output depends on your exact CatVTON fork and checkpoint format**. If the repository layout differs, `app.py` may need a small adapter for the correct import path or inference call.
.history/README_20260615122003.md ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: ChitraTech Virtual Try-On
3
+ emoji: 👕
4
+ colorFrom: pink
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: 6.5.1
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ ---
12
+
13
+ # ChitraTech Virtual Try-On
14
+
15
+ This repository is a Hugging Face Space for an on-demand virtual try-on experience powered by **CatVTON**.
16
+
17
+ ## Planned flow
18
+
19
+ 1. A shopper clicks **Try On** on your ecommerce product page.
20
+ 2. Your frontend sends the shopper photo and the selected clothing image to this Hugging Face Space.
21
+ 3. The Space runs CatVTON inference only for that request.
22
+ 4. The generated try-on image is returned to the frontend for preview.
23
+
24
+ ## Current status
25
+
26
+ The current Space now includes:
27
+ - a Gradio interface for person and garment uploads,
28
+ - lazy pipeline initialization for on-demand inference,
29
+ - automatic detection of CatVTON code under `CatVTON/`,
30
+ - automatic detection of model weights under `checkpoints/CatVTON/`,
31
+ - a safe demo fallback when CatVTON is not fully installed yet.
32
+
33
+ ## Repository structure
34
+
35
+ Recommended layout:
36
+ - `app.py`
37
+ - `requirements.txt`
38
+ - `CatVTON/` for the cloned CatVTON repository
39
+ - `checkpoints/CatVTON/` for model weights
40
+ - optional `examples/` for demo assets
41
+
42
+ ## Setup steps
43
+
44
+ ### 1. Add CatVTON source code
45
+
46
+ Clone or copy your CatVTON implementation into:
47
+ - `CatVTON/`
48
+
49
+ If your fork uses a different folder, set:
50
+ - `CATVTON_REPO_DIR`
51
+
52
+ ### 2. Add model weights
53
+
54
+ Place CatVTON checkpoints in:
55
+ - `checkpoints/CatVTON/`
56
+
57
+ Or set:
58
+ - `CATVTON_MODEL_DIR`
59
+
60
+ ### 3. Confirm the inference entrypoint
61
+
62
+ The current app tries common Python and script entrypoints automatically. If your CatVTON fork uses different class names, function names, or CLI arguments, update the loading logic in `app.py`.
63
+
64
+ ### 4. Push to Hugging Face Spaces
65
+
66
+ After adding the CatVTON repo and weights references, push the repository. Hugging Face will rebuild the Space automatically.
67
+
68
+ ## Frontend integration
69
+
70
+ Your ecommerce frontend can call the Hugging Face Space directly when needed, making this a free, on-demand approach without keeping a GPU server running continuously.
71
+
72
+ ## Important note
73
+
74
+ This app is now deployment-ready as a shell, but **real try-on output depends on your exact CatVTON fork and checkpoint format**. If the repository layout differs, `app.py` may need a small adapter for the correct import path or inference call.
.history/app_20260615121735.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import gradio as gr
9
+ from PIL import Image, ImageOps, ImageDraw
10
+
11
+ APP_TITLE = "ChitraTech Virtual Try-On"
12
+ APP_DESCRIPTION = (
13
+ "Upload a person image and a garment image to run on-demand CatVTON virtual try-on inference. "
14
+ "This Space is structured so you can swap the placeholder pipeline with your CatVTON model loader and inference code."
15
+ )
16
+ MODEL_DIR = Path(os.getenv("CATVTON_MODEL_DIR", "./checkpoints/CatVTON"))
17
+ DEVICE = os.getenv("CATVTON_DEVICE", "cuda")
18
+ MAX_IMAGE_SIZE = 1024
19
+
20
+
21
+ @dataclass
22
+ class CatVTONPipeline:
23
+ model_dir: Path
24
+ device: str
25
+ ready: bool = False
26
+
27
+ def load(self) -> None:
28
+ """Load model weights once when the first request arrives."""
29
+ # TODO: Replace this placeholder with actual CatVTON imports and checkpoint loading.
30
+ # Example:
31
+ # from catvton.inference import CatVTONInferencer
32
+ # self.model = CatVTONInferencer(checkpoint_path=str(self.model_dir), device=self.device)
33
+ self.ready = True
34
+
35
+ def run(self, person_image: Image.Image, garment_image: Image.Image) -> Image.Image:
36
+ if not self.ready:
37
+ self.load()
38
+
39
+ # TODO: Replace this placeholder with actual CatVTON inference.
40
+ # Example:
41
+ # result = self.model.infer(person_image, garment_image)
42
+ # return result
43
+ return build_placeholder_preview(person_image, garment_image)
44
+
45
+
46
+ pipeline = CatVTONPipeline(model_dir=MODEL_DIR, device=DEVICE)
47
+
48
+
49
+ def prepare_image(image: Image.Image) -> Image.Image:
50
+ image = ImageOps.exif_transpose(image).convert("RGB")
51
+ image.thumbnail((MAX_IMAGE_SIZE, MAX_IMAGE_SIZE))
52
+ return image
53
+
54
+
55
+ def build_placeholder_preview(person_image: Image.Image, garment_image: Image.Image) -> Image.Image:
56
+ preview = person_image.copy().convert("RGB")
57
+ garment_thumb = garment_image.copy().convert("RGB")
58
+ garment_thumb.thumbnail((preview.width // 3, preview.height // 3))
59
+
60
+ overlay = Image.new("RGBA", preview.size, (0, 0, 0, 0))
61
+ x = preview.width - garment_thumb.width - 24
62
+ y = 24
63
+ overlay.paste(garment_thumb.convert("RGBA"), (x, y))
64
+
65
+ draw = ImageDraw.Draw(overlay)
66
+ label_y = y + garment_thumb.height + 10
67
+ draw.rounded_rectangle(
68
+ [(x, label_y), (min(preview.width - 24, x + 220), label_y + 40)],
69
+ radius=12,
70
+ fill=(17, 24, 39, 210),
71
+ )
72
+ draw.text((x + 12, label_y + 12), "Placeholder preview", fill=(255, 255, 255, 255))
73
+
74
+ return Image.alpha_composite(preview.convert("RGBA"), overlay).convert("RGB")
75
+
76
+
77
+ def try_on(person_image: Optional[Image.Image], garment_image: Optional[Image.Image]) -> Image.Image:
78
+ if person_image is None or garment_image is None:
79
+ raise gr.Error("Please upload both a person image and a garment image.")
80
+
81
+ prepared_person = prepare_image(person_image)
82
+ prepared_garment = prepare_image(garment_image)
83
+ return pipeline.run(prepared_person, prepared_garment)
84
+
85
+
86
+ with gr.Blocks(theme=gr.themes.Soft(), title=APP_TITLE) as demo:
87
+ gr.Markdown(f"# {APP_TITLE}")
88
+ gr.Markdown(APP_DESCRIPTION)
89
+
90
+ with gr.Row():
91
+ with gr.Column():
92
+ person_input = gr.Image(type="pil", label="User photo")
93
+ garment_input = gr.Image(type="pil", label="Clothing image")
94
+ submit_button = gr.Button("Try On", variant="primary")
95
+ with gr.Column():
96
+ result_output = gr.Image(type="pil", label="Generated try-on image")
97
+
98
+ gr.Examples(
99
+ examples=[],
100
+ inputs=[person_input, garment_input],
101
+ label="Optional: add example image pairs after you upload assets to the repository.",
102
+ )
103
+
104
+ gr.Markdown(
105
+ """
106
+ ### Integration notes
107
+ - Your ecommerce frontend can call this Space on demand whenever a shopper clicks **Try On**.
108
+ - Replace the placeholder pipeline with CatVTON inference code and store model weights under `checkpoints/` or download them at startup.
109
+ - Keep preprocessing logic here so the frontend only needs to send two images.
110
+ """
111
+ )
112
+
113
+ submit_button.click(fn=try_on, inputs=[person_input, garment_input], outputs=result_output)
114
+
115
+
116
+ demo.launch()
.history/app_20260615121752.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import gradio as gr
9
+ from PIL import Image, ImageOps, ImageDraw
10
+
11
+ APP_TITLE = "ChitraTech Virtual Try-On"
12
+ APP_DESCRIPTION = (
13
+ "Upload a person image and a garment image to run on-demand CatVTON virtual try-on inference. "
14
+ "This Space is structured so you can swap the placeholder pipeline with your CatVTON model loader and inference code."
15
+ )
16
+ MODEL_DIR = Path(os.getenv("CATVTON_MODEL_DIR", "./checkpoints/CatVTON"))
17
+ DEVICE = os.getenv("CATVTON_DEVICE", "cuda")
18
+ MAX_IMAGE_SIZE = 1024
19
+
20
+
21
+ @dataclass
22
+ class CatVTONPipeline:
23
+ model_dir: Path
24
+ device: str
25
+ ready: bool = False
26
+
27
+ def load(self) -> None:
28
+ """Load model weights once when the first request arrives."""
29
+ # TODO: Replace this placeholder with actual CatVTON imports and checkpoint loading.
30
+ # Example:
31
+ # from catvton.inference import CatVTONInferencer
32
+ # self.model = CatVTONInferencer(checkpoint_path=str(self.model_dir), device=self.device)
33
+ self.ready = True
34
+
35
+ def run(self, person_image: Image.Image, garment_image: Image.Image) -> Image.Image:
36
+ if not self.ready:
37
+ self.load()
38
+
39
+ # TODO: Replace this placeholder with actual CatVTON inference.
40
+ # Example:
41
+ # result = self.model.infer(person_image, garment_image)
42
+ # return result
43
+ return build_placeholder_preview(person_image, garment_image)
44
+
45
+
46
+ pipeline = CatVTONPipeline(model_dir=MODEL_DIR, device=DEVICE)
47
+
48
+
49
+ def prepare_image(image: Image.Image) -> Image.Image:
50
+ image = ImageOps.exif_transpose(image).convert("RGB")
51
+ image.thumbnail((MAX_IMAGE_SIZE, MAX_IMAGE_SIZE))
52
+ return image
53
+
54
+
55
+ def build_placeholder_preview(person_image: Image.Image, garment_image: Image.Image) -> Image.Image:
56
+ preview = person_image.copy().convert("RGB")
57
+ garment_thumb = garment_image.copy().convert("RGB")
58
+ garment_thumb.thumbnail((preview.width // 3, preview.height // 3))
59
+
60
+ overlay = Image.new("RGBA", preview.size, (0, 0, 0, 0))
61
+ x = preview.width - garment_thumb.width - 24
62
+ y = 24
63
+ overlay.paste(garment_thumb.convert("RGBA"), (x, y))
64
+
65
+ draw = ImageDraw.Draw(overlay)
66
+ label_y = y + garment_thumb.height + 10
67
+ draw.rounded_rectangle(
68
+ [(x, label_y), (min(preview.width - 24, x + 220), label_y + 40)],
69
+ radius=12,
70
+ fill=(17, 24, 39, 210),
71
+ )
72
+ draw.text((x + 12, label_y + 12), "Placeholder preview", fill=(255, 255, 255, 255))
73
+
74
+ return Image.alpha_composite(preview.convert("RGBA"), overlay).convert("RGB")
75
+
76
+
77
+ def try_on(person_image: Optional[Image.Image], garment_image: Optional[Image.Image]) -> Image.Image:
78
+ if person_image is None or garment_image is None:
79
+ raise gr.Error("Please upload both a person image and a garment image.")
80
+
81
+ prepared_person = prepare_image(person_image)
82
+ prepared_garment = prepare_image(garment_image)
83
+ return pipeline.run(prepared_person, prepared_garment)
84
+
85
+
86
+ with gr.Blocks(theme=gr.themes.Soft(), title=APP_TITLE) as demo:
87
+ gr.Markdown(f"# {APP_TITLE}")
88
+ gr.Markdown(APP_DESCRIPTION)
89
+
90
+ with gr.Row():
91
+ with gr.Column():
92
+ person_input = gr.Image(type="pil", label="User photo")
93
+ garment_input = gr.Image(type="pil", label="Clothing image")
94
+ submit_button = gr.Button("Try On", variant="primary")
95
+ with gr.Column():
96
+ result_output = gr.Image(type="pil", label="Generated try-on image")
97
+
98
+ gr.Examples(
99
+ examples=[],
100
+ inputs=[person_input, garment_input],
101
+ label="Optional: add example image pairs after you upload assets to the repository.",
102
+ )
103
+
104
+ gr.Markdown(
105
+ """
106
+ ### Integration notes
107
+ - Your ecommerce frontend can call this Space on demand whenever a shopper clicks **Try On**.
108
+ - Replace the placeholder pipeline with CatVTON inference code and store model weights under `checkpoints/` or download them at startup.
109
+ - Keep preprocessing logic here so the frontend only needs to send two images.
110
+ """
111
+ )
112
+
113
+ submit_button.click(fn=try_on, inputs=[person_input, garment_input], outputs=result_output)
114
+
115
+
116
+ demo.launch()
.history/app_20260615121921.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ from dataclasses import dataclass, field
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import gradio as gr
11
+ from PIL import Image, ImageDraw, ImageOps
12
+
13
+ APP_TITLE = "ChitraTech Virtual Try-On"
14
+ APP_DESCRIPTION = (
15
+ "Upload a person image and a garment image to run on-demand CatVTON virtual try-on inference. "
16
+ "If CatVTON source code and weights are present, the Space uses them automatically; otherwise it falls back to a demo preview."
17
+ )
18
+ MODEL_DIR = Path(os.getenv("CATVTON_MODEL_DIR", "./checkpoints/CatVTON"))
19
+ CATVTON_REPO_DIR = Path(os.getenv("CATVTON_REPO_DIR", "./CatVTON"))
20
+ DEVICE = os.getenv("CATVTON_DEVICE", "cuda")
21
+ MAX_IMAGE_SIZE = 1024
22
+ DEFAULT_STEPS = int(os.getenv("CATVTON_STEPS", "30"))
23
+ DEFAULT_GUIDANCE_SCALE = float(os.getenv("CATVTON_GUIDANCE_SCALE", "2.5"))
24
+
25
+
26
+ @dataclass
27
+ class CatVTONPipeline:
28
+ model_dir: Path
29
+ repo_dir: Path
30
+ device: str
31
+ ready: bool = False
32
+ backend_name: str = "placeholder"
33
+ backend_error: Optional[str] = None
34
+ model: object | None = field(default=None, init=False, repr=False)
35
+
36
+ def load(self) -> None:
37
+ """Load model weights once when the first request arrives."""
38
+ self.backend_error = None
39
+
40
+ if not self.repo_dir.exists():
41
+ self.backend_name = "placeholder"
42
+ self.ready = True
43
+ self.backend_error = (
44
+ f"CatVTON source folder not found at '{self.repo_dir}'. "
45
+ "Add the CatVTON repository files there to enable real inference."
46
+ )
47
+ return
48
+
49
+ if not self.model_dir.exists():
50
+ self.backend_name = "placeholder"
51
+ self.ready = True
52
+ self.backend_error = (
53
+ f"CatVTON checkpoint folder not found at '{self.model_dir}'. "
54
+ "Add model weights to enable real inference."
55
+ )
56
+ return
57
+
58
+ try:
59
+ if str(self.repo_dir.resolve()) not in sys.path:
60
+ sys.path.insert(0, str(self.repo_dir.resolve()))
61
+
62
+ self.model = self._try_load_native_backend()
63
+ if self.model is None:
64
+ self.model = self._try_load_cli_backend()
65
+
66
+ if self.model is None:
67
+ self.backend_name = "placeholder"
68
+ self.backend_error = (
69
+ "CatVTON files were found, but no supported inference entrypoint was detected. "
70
+ "Update `_try_load_native_backend` or `_try_load_cli_backend` for your CatVTON fork."
71
+ )
72
+ else:
73
+ self.ready = True
74
+ return
75
+ except Exception as exc: # pragma: no cover - runtime integration path
76
+ self.backend_name = "placeholder"
77
+ self.backend_error = f"CatVTON loading failed: {exc}"
78
+
79
+ self.ready = True
80
+
81
+ def _try_load_native_backend(self) -> object | None:
82
+ """Try Python-native CatVTON integrations for common repository layouts."""
83
+ import importlib
84
+
85
+ candidates = [
86
+ ("app", "CatVTONPipeline"),
87
+ ("inference", "CatVTONPipeline"),
88
+ ("catvton.inference", "CatVTONPipeline"),
89
+ ("predict", "Predictor"),
90
+ ]
91
+
92
+ for module_name, class_name in candidates:
93
+ try:
94
+ module = importlib.import_module(module_name)
95
+ backend_class = getattr(module, class_name, None)
96
+ if backend_class is None:
97
+ continue
98
+ self.backend_name = f"python:{module_name}.{class_name}"
99
+ return backend_class(model_path=str(self.model_dir), device=self.device)
100
+ except TypeError:
101
+ try:
102
+ self.backend_name = f"python:{module_name}.{class_name}"
103
+ return backend_class(checkpoint_path=str(self.model_dir), device=self.device)
104
+ except Exception:
105
+ continue
106
+ except Exception:
107
+ continue
108
+
109
+ return None
110
+
111
+ def _try_load_cli_backend(self) -> object | None:
112
+ """Fallback to a subprocess-based backend for CatVTON forks that expose a script."""
113
+ cli_candidates = [
114
+ self.repo_dir / "inference.py",
115
+ self.repo_dir / "predict.py",
116
+ self.repo_dir / "app.py",
117
+ ]
118
+ for script_path in cli_candidates:
119
+ if script_path.exists():
120
+ self.backend_name = f"cli:{script_path.name}"
121
+ return script_path
122
+ return None
123
+
124
+ def run(self, person_image: Image.Image, garment_image: Image.Image) -> Image.Image:
125
+ if not self.ready:
126
+ self.load()
127
+
128
+ if self.backend_name.startswith("python:") and self.model is not None:
129
+ try:
130
+ return self._run_python_backend(person_image, garment_image)
131
+ except Exception as exc: # pragma: no cover - runtime integration path
132
+ self.backend_error = f"CatVTON inference failed: {exc}"
133
+
134
+ if self.backend_name.startswith("cli:") and self.model is not None:
135
+ try:
136
+ return self._run_cli_backend(person_image, garment_image)
137
+ except Exception as exc: # pragma: no cover - runtime integration path
138
+ self.backend_error = f"CatVTON CLI inference failed: {exc}"
139
+
140
+ return build_placeholder_preview(person_image, garment_image, self.backend_error)
141
+
142
+ def _run_python_backend(self, person_image: Image.Image, garment_image: Image.Image) -> Image.Image:
143
+ assert self.model is not None
144
+
145
+ if hasattr(self.model, "infer"):
146
+ result = self.model.infer(
147
+ person_image=person_image,
148
+ garment_image=garment_image,
149
+ num_inference_steps=DEFAULT_STEPS,
150
+ guidance_scale=DEFAULT_GUIDANCE_SCALE,
151
+ )
152
+ elif callable(self.model):
153
+ result = self.model(
154
+ person_image=person_image,
155
+ garment_image=garment_image,
156
+ num_inference_steps=DEFAULT_STEPS,
157
+ guidance_scale=DEFAULT_GUIDANCE_SCALE,
158
+ )
159
+ else:
160
+ raise RuntimeError("Loaded CatVTON backend is not callable.")
161
+
162
+ if isinstance(result, Image.Image):
163
+ return result.convert("RGB")
164
+ if isinstance(result, dict):
165
+ for key in ("image", "result", "output"):
166
+ value = result.get(key)
167
+ if isinstance(value, Image.Image):
168
+ return value.convert("RGB")
169
+ raise RuntimeError("Unsupported CatVTON output format.")
170
+
171
+ def _run_cli_backend(self, person_image: Image.Image, garment_image: Image.Image) -> Image.Image:
172
+ script_path = Path(self.model)
173
+ input_dir = Path("/tmp/catvton-inputs") if os.name != "nt" else Path("./tmp/catvton-inputs")
174
+ output_dir = Path("/tmp/catvton-outputs") if os.name != "nt" else Path("./tmp/catvton-outputs")
175
+ input_dir.mkdir(parents=True, exist_ok=True)
176
+ output_dir.mkdir(parents=True, exist_ok=True)
177
+
178
+ person_path = input_dir / "person.png"
179
+ garment_path = input_dir / "garment.png"
180
+ output_path = output_dir / "result.png"
181
+ person_image.save(person_path)
182
+ garment_image.save(garment_path)
183
+
184
+ command = [
185
+ sys.executable,
186
+ str(script_path),
187
+ "--person",
188
+ str(person_path),
189
+ "--cloth",
190
+ str(garment_path),
191
+ "--output",
192
+ str(output_path),
193
+ "--checkpoint",
194
+ str(self.model_dir),
195
+ ]
196
+
197
+ subprocess.run(command, check=True, cwd=str(self.repo_dir))
198
+ if not output_path.exists():
199
+ raise RuntimeError("CatVTON CLI completed without writing an output image.")
200
+ return Image.open(output_path).convert("RGB")
201
+
202
+
203
+ pipeline = CatVTONPipeline(model_dir=MODEL_DIR, repo_dir=CATVTON_REPO_DIR, device=DEVICE)
204
+
205
+
206
+ def prepare_image(image: Image.Image) -> Image.Image:
207
+ image = ImageOps.exif_transpose(image).convert("RGB")
208
+ image.thumbnail((MAX_IMAGE_SIZE, MAX_IMAGE_SIZE))
209
+ return image
210
+
211
+
212
+ def build_placeholder_preview(
213
+ person_image: Image.Image,
214
+ garment_image: Image.Image,
215
+ error_message: Optional[str] = None,
216
+ ) -> Image.Image:
217
+ preview = person_image.copy().convert("RGB")
218
+ garment_thumb = garment_image.copy().convert("RGB")
219
+ garment_thumb.thumbnail((preview.width // 3, preview.height // 3))
220
+
221
+ overlay = Image.new("RGBA", preview.size, (0, 0, 0, 0))
222
+ x = preview.width - garment_thumb.width - 24
223
+ y = 24
224
+ overlay.paste(garment_thumb.convert("RGBA"), (x, y))
225
+
226
+ draw = ImageDraw.Draw(overlay)
227
+ label_y = y + garment_thumb.height + 10
228
+ draw.rounded_rectangle(
229
+ [(x, label_y), (min(preview.width - 24, x + 270), label_y + 44)],
230
+ radius=12,
231
+ fill=(17, 24, 39, 210),
232
+ )
233
+ draw.text((x + 12, label_y + 13), "Demo preview mode", fill=(255, 255, 255, 255))
234
+
235
+ if error_message:
236
+ panel_top = max(24, preview.height - 170)
237
+ draw.rounded_rectangle(
238
+ [(24, panel_top), (preview.width - 24, preview.height - 24)],
239
+ radius=16,
240
+ fill=(127, 29, 29, 220),
241
+ )
242
+ wrapped = error_message[:180] + ("..." if len(error_message) > 180 else "")
243
+ draw.text((40, panel_top + 18), wrapped, fill=(255, 255, 255, 255))
244
+
245
+ return Image.alpha_composite(preview.convert("RGBA"), overlay).convert("RGB")
246
+
247
+
248
+ def try_on(person_image: Optional[Image.Image], garment_image: Optional[Image.Image]) -> Image.Image:
249
+ if person_image is None or garment_image is None:
250
+ raise gr.Error("Please upload both a person image and a garment image.")
251
+
252
+ prepared_person = prepare_image(person_image)
253
+ prepared_garment = prepare_image(garment_image)
254
+ return pipeline.run(prepared_person, prepared_garment)
255
+
256
+
257
+ with gr.Blocks(theme=gr.themes.Soft(), title=APP_TITLE) as demo:
258
+ gr.Markdown(f"# {APP_TITLE}")
259
+ gr.Markdown(APP_DESCRIPTION)
260
+ gr.Markdown(
261
+ f"**Backend status:** `{pipeline.backend_name}` | Expected source: `{CATVTON_REPO_DIR}` | Expected weights: `{MODEL_DIR}`"
262
+ )
263
+
264
+ with gr.Row():
265
+ with gr.Column():
266
+ person_input = gr.Image(type="pil", label="User photo")
267
+ garment_input = gr.Image(type="pil", label="Clothing image")
268
+ submit_button = gr.Button("Try On", variant="primary")
269
+ with gr.Column():
270
+ result_output = gr.Image(type="pil", label="Generated try-on image")
271
+
272
+ gr.Markdown(
273
+ """
274
+ ### Setup notes
275
+ - Put the CatVTON repository code in `CatVTON/` or set `CATVTON_REPO_DIR`.
276
+ - Put model checkpoints in `checkpoints/CatVTON/` or set `CATVTON_MODEL_DIR`.
277
+ - If no compatible CatVTON entrypoint is found, the Space stays usable in demo mode.
278
+ """
279
+ )
280
+
281
+ submit_button.click(fn=try_on, inputs=[person_input, garment_input], outputs=result_output)
282
+
283
+
284
+ demo.launch()
.history/app_20260615122003.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ from dataclasses import dataclass, field
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import gradio as gr
11
+ from PIL import Image, ImageDraw, ImageOps
12
+
13
+ APP_TITLE = "ChitraTech Virtual Try-On"
14
+ APP_DESCRIPTION = (
15
+ "Upload a person image and a garment image to run on-demand CatVTON virtual try-on inference. "
16
+ "If CatVTON source code and weights are present, the Space uses them automatically; otherwise it falls back to a demo preview."
17
+ )
18
+ MODEL_DIR = Path(os.getenv("CATVTON_MODEL_DIR", "./checkpoints/CatVTON"))
19
+ CATVTON_REPO_DIR = Path(os.getenv("CATVTON_REPO_DIR", "./CatVTON"))
20
+ DEVICE = os.getenv("CATVTON_DEVICE", "cuda")
21
+ MAX_IMAGE_SIZE = 1024
22
+ DEFAULT_STEPS = int(os.getenv("CATVTON_STEPS", "30"))
23
+ DEFAULT_GUIDANCE_SCALE = float(os.getenv("CATVTON_GUIDANCE_SCALE", "2.5"))
24
+
25
+
26
+ @dataclass
27
+ class CatVTONPipeline:
28
+ model_dir: Path
29
+ repo_dir: Path
30
+ device: str
31
+ ready: bool = False
32
+ backend_name: str = "placeholder"
33
+ backend_error: Optional[str] = None
34
+ model: object | None = field(default=None, init=False, repr=False)
35
+
36
+ def load(self) -> None:
37
+ """Load model weights once when the first request arrives."""
38
+ self.backend_error = None
39
+
40
+ if not self.repo_dir.exists():
41
+ self.backend_name = "placeholder"
42
+ self.ready = True
43
+ self.backend_error = (
44
+ f"CatVTON source folder not found at '{self.repo_dir}'. "
45
+ "Add the CatVTON repository files there to enable real inference."
46
+ )
47
+ return
48
+
49
+ if not self.model_dir.exists():
50
+ self.backend_name = "placeholder"
51
+ self.ready = True
52
+ self.backend_error = (
53
+ f"CatVTON checkpoint folder not found at '{self.model_dir}'. "
54
+ "Add model weights to enable real inference."
55
+ )
56
+ return
57
+
58
+ try:
59
+ if str(self.repo_dir.resolve()) not in sys.path:
60
+ sys.path.insert(0, str(self.repo_dir.resolve()))
61
+
62
+ self.model = self._try_load_native_backend()
63
+ if self.model is None:
64
+ self.model = self._try_load_cli_backend()
65
+
66
+ if self.model is None:
67
+ self.backend_name = "placeholder"
68
+ self.backend_error = (
69
+ "CatVTON files were found, but no supported inference entrypoint was detected. "
70
+ "Update `_try_load_native_backend` or `_try_load_cli_backend` for your CatVTON fork."
71
+ )
72
+ else:
73
+ self.ready = True
74
+ return
75
+ except Exception as exc: # pragma: no cover - runtime integration path
76
+ self.backend_name = "placeholder"
77
+ self.backend_error = f"CatVTON loading failed: {exc}"
78
+
79
+ self.ready = True
80
+
81
+ def _try_load_native_backend(self) -> object | None:
82
+ """Try Python-native CatVTON integrations for common repository layouts."""
83
+ import importlib
84
+
85
+ candidates = [
86
+ ("app", "CatVTONPipeline"),
87
+ ("inference", "CatVTONPipeline"),
88
+ ("catvton.inference", "CatVTONPipeline"),
89
+ ("predict", "Predictor"),
90
+ ]
91
+
92
+ for module_name, class_name in candidates:
93
+ try:
94
+ module = importlib.import_module(module_name)
95
+ backend_class = getattr(module, class_name, None)
96
+ if backend_class is None:
97
+ continue
98
+ self.backend_name = f"python:{module_name}.{class_name}"
99
+ return backend_class(model_path=str(self.model_dir), device=self.device)
100
+ except TypeError:
101
+ try:
102
+ self.backend_name = f"python:{module_name}.{class_name}"
103
+ return backend_class(checkpoint_path=str(self.model_dir), device=self.device)
104
+ except Exception:
105
+ continue
106
+ except Exception:
107
+ continue
108
+
109
+ return None
110
+
111
+ def _try_load_cli_backend(self) -> object | None:
112
+ """Fallback to a subprocess-based backend for CatVTON forks that expose a script."""
113
+ cli_candidates = [
114
+ self.repo_dir / "inference.py",
115
+ self.repo_dir / "predict.py",
116
+ self.repo_dir / "app.py",
117
+ ]
118
+ for script_path in cli_candidates:
119
+ if script_path.exists():
120
+ self.backend_name = f"cli:{script_path.name}"
121
+ return script_path
122
+ return None
123
+
124
+ def run(self, person_image: Image.Image, garment_image: Image.Image) -> Image.Image:
125
+ if not self.ready:
126
+ self.load()
127
+
128
+ if self.backend_name.startswith("python:") and self.model is not None:
129
+ try:
130
+ return self._run_python_backend(person_image, garment_image)
131
+ except Exception as exc: # pragma: no cover - runtime integration path
132
+ self.backend_error = f"CatVTON inference failed: {exc}"
133
+
134
+ if self.backend_name.startswith("cli:") and self.model is not None:
135
+ try:
136
+ return self._run_cli_backend(person_image, garment_image)
137
+ except Exception as exc: # pragma: no cover - runtime integration path
138
+ self.backend_error = f"CatVTON CLI inference failed: {exc}"
139
+
140
+ return build_placeholder_preview(person_image, garment_image, self.backend_error)
141
+
142
+ def _run_python_backend(self, person_image: Image.Image, garment_image: Image.Image) -> Image.Image:
143
+ assert self.model is not None
144
+
145
+ if hasattr(self.model, "infer"):
146
+ result = self.model.infer(
147
+ person_image=person_image,
148
+ garment_image=garment_image,
149
+ num_inference_steps=DEFAULT_STEPS,
150
+ guidance_scale=DEFAULT_GUIDANCE_SCALE,
151
+ )
152
+ elif callable(self.model):
153
+ result = self.model(
154
+ person_image=person_image,
155
+ garment_image=garment_image,
156
+ num_inference_steps=DEFAULT_STEPS,
157
+ guidance_scale=DEFAULT_GUIDANCE_SCALE,
158
+ )
159
+ else:
160
+ raise RuntimeError("Loaded CatVTON backend is not callable.")
161
+
162
+ if isinstance(result, Image.Image):
163
+ return result.convert("RGB")
164
+ if isinstance(result, dict):
165
+ for key in ("image", "result", "output"):
166
+ value = result.get(key)
167
+ if isinstance(value, Image.Image):
168
+ return value.convert("RGB")
169
+ raise RuntimeError("Unsupported CatVTON output format.")
170
+
171
+ def _run_cli_backend(self, person_image: Image.Image, garment_image: Image.Image) -> Image.Image:
172
+ script_path = Path(self.model)
173
+ input_dir = Path("/tmp/catvton-inputs") if os.name != "nt" else Path("./tmp/catvton-inputs")
174
+ output_dir = Path("/tmp/catvton-outputs") if os.name != "nt" else Path("./tmp/catvton-outputs")
175
+ input_dir.mkdir(parents=True, exist_ok=True)
176
+ output_dir.mkdir(parents=True, exist_ok=True)
177
+
178
+ person_path = input_dir / "person.png"
179
+ garment_path = input_dir / "garment.png"
180
+ output_path = output_dir / "result.png"
181
+ person_image.save(person_path)
182
+ garment_image.save(garment_path)
183
+
184
+ command = [
185
+ sys.executable,
186
+ str(script_path),
187
+ "--person",
188
+ str(person_path),
189
+ "--cloth",
190
+ str(garment_path),
191
+ "--output",
192
+ str(output_path),
193
+ "--checkpoint",
194
+ str(self.model_dir),
195
+ ]
196
+
197
+ subprocess.run(command, check=True, cwd=str(self.repo_dir))
198
+ if not output_path.exists():
199
+ raise RuntimeError("CatVTON CLI completed without writing an output image.")
200
+ return Image.open(output_path).convert("RGB")
201
+
202
+
203
+ pipeline = CatVTONPipeline(model_dir=MODEL_DIR, repo_dir=CATVTON_REPO_DIR, device=DEVICE)
204
+
205
+
206
+ def prepare_image(image: Image.Image) -> Image.Image:
207
+ image = ImageOps.exif_transpose(image).convert("RGB")
208
+ image.thumbnail((MAX_IMAGE_SIZE, MAX_IMAGE_SIZE))
209
+ return image
210
+
211
+
212
+ def build_placeholder_preview(
213
+ person_image: Image.Image,
214
+ garment_image: Image.Image,
215
+ error_message: Optional[str] = None,
216
+ ) -> Image.Image:
217
+ preview = person_image.copy().convert("RGB")
218
+ garment_thumb = garment_image.copy().convert("RGB")
219
+ garment_thumb.thumbnail((preview.width // 3, preview.height // 3))
220
+
221
+ overlay = Image.new("RGBA", preview.size, (0, 0, 0, 0))
222
+ x = preview.width - garment_thumb.width - 24
223
+ y = 24
224
+ overlay.paste(garment_thumb.convert("RGBA"), (x, y))
225
+
226
+ draw = ImageDraw.Draw(overlay)
227
+ label_y = y + garment_thumb.height + 10
228
+ draw.rounded_rectangle(
229
+ [(x, label_y), (min(preview.width - 24, x + 270), label_y + 44)],
230
+ radius=12,
231
+ fill=(17, 24, 39, 210),
232
+ )
233
+ draw.text((x + 12, label_y + 13), "Demo preview mode", fill=(255, 255, 255, 255))
234
+
235
+ if error_message:
236
+ panel_top = max(24, preview.height - 170)
237
+ draw.rounded_rectangle(
238
+ [(24, panel_top), (preview.width - 24, preview.height - 24)],
239
+ radius=16,
240
+ fill=(127, 29, 29, 220),
241
+ )
242
+ wrapped = error_message[:180] + ("..." if len(error_message) > 180 else "")
243
+ draw.text((40, panel_top + 18), wrapped, fill=(255, 255, 255, 255))
244
+
245
+ return Image.alpha_composite(preview.convert("RGBA"), overlay).convert("RGB")
246
+
247
+
248
+ def try_on(person_image: Optional[Image.Image], garment_image: Optional[Image.Image]) -> Image.Image:
249
+ if person_image is None or garment_image is None:
250
+ raise gr.Error("Please upload both a person image and a garment image.")
251
+
252
+ prepared_person = prepare_image(person_image)
253
+ prepared_garment = prepare_image(garment_image)
254
+ return pipeline.run(prepared_person, prepared_garment)
255
+
256
+
257
+ with gr.Blocks(theme=gr.themes.Soft(), title=APP_TITLE) as demo:
258
+ gr.Markdown(f"# {APP_TITLE}")
259
+ gr.Markdown(APP_DESCRIPTION)
260
+ gr.Markdown(
261
+ f"**Backend status:** `{pipeline.backend_name}` | Expected source: `{CATVTON_REPO_DIR}` | Expected weights: `{MODEL_DIR}`"
262
+ )
263
+
264
+ with gr.Row():
265
+ with gr.Column():
266
+ person_input = gr.Image(type="pil", label="User photo")
267
+ garment_input = gr.Image(type="pil", label="Clothing image")
268
+ submit_button = gr.Button("Try On", variant="primary")
269
+ with gr.Column():
270
+ result_output = gr.Image(type="pil", label="Generated try-on image")
271
+
272
+ gr.Markdown(
273
+ """
274
+ ### Setup notes
275
+ - Put the CatVTON repository code in `CatVTON/` or set `CATVTON_REPO_DIR`.
276
+ - Put model checkpoints in `checkpoints/CatVTON/` or set `CATVTON_MODEL_DIR`.
277
+ - If no compatible CatVTON entrypoint is found, the Space stays usable in demo mode.
278
+ """
279
+ )
280
+
281
+ submit_button.click(fn=try_on, inputs=[person_input, garment_input], outputs=result_output)
282
+
283
+
284
+ demo.launch()
.history/app_20260615122555.py ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ import gradio as gr
10
+ import numpy as np
11
+ import torch
12
+ from diffusers.image_processor import VaeImageProcessor
13
+ from huggingface_hub import snapshot_download
14
+ from PIL import Image, ImageOps
15
+
16
+ APP_TITLE = "ChitraTech Virtual Try-On"
17
+ APP_DESCRIPTION = (
18
+ "Upload a shopper photo and clothing image to run on-demand CatVTON virtual try-on inference "
19
+ "using the Zheng-Chong CatVTON implementation."
20
+ )
21
+ CATVTON_REPO_DIR = Path(os.getenv("CATVTON_REPO_DIR", "./CatVTON"))
22
+ CATVTON_RESUME_PATH = os.getenv("CATVTON_RESUME_PATH", "zhengchong/CatVTON")
23
+ CATVTON_BASE_MODEL = os.getenv("CATVTON_BASE_MODEL", "booksforcharlie/stable-diffusion-inpainting")
24
+ CATVTON_OUTPUT_DIR = Path(os.getenv("CATVTON_OUTPUT_DIR", "./outputs"))
25
+ DEVICE = os.getenv("CATVTON_DEVICE", "cuda")
26
+ DEFAULT_WIDTH = int(os.getenv("CATVTON_WIDTH", "768"))
27
+ DEFAULT_HEIGHT = int(os.getenv("CATVTON_HEIGHT", "1024"))
28
+ DEFAULT_STEPS = int(os.getenv("CATVTON_STEPS", "50"))
29
+ DEFAULT_GUIDANCE_SCALE = float(os.getenv("CATVTON_GUIDANCE_SCALE", "2.5"))
30
+ DEFAULT_MIXED_PRECISION = os.getenv("CATVTON_MIXED_PRECISION", "bf16")
31
+ DEFAULT_SEED = int(os.getenv("CATVTON_SEED", "42"))
32
+
33
+
34
+ @dataclass
35
+ class CatVTONRuntime:
36
+ repo_dir: Path
37
+ device: str
38
+ pipeline: object | None = field(default=None, init=False, repr=False)
39
+ automasker: object | None = field(default=None, init=False, repr=False)
40
+ mask_processor: object | None = field(default=None, init=False, repr=False)
41
+ resize_and_crop: object | None = field(default=None, init=False, repr=False)
42
+ resize_and_padding: object | None = field(default=None, init=False, repr=False)
43
+ vis_mask: object | None = field(default=None, init=False, repr=False)
44
+ ready: bool = False
45
+ status: str = "not loaded"
46
+
47
+ def load(self) -> None:
48
+ if self.ready:
49
+ return
50
+
51
+ if not self.repo_dir.exists():
52
+ raise RuntimeError(f"CatVTON repository not found at '{self.repo_dir}'.")
53
+
54
+ repo_path = str(self.repo_dir.resolve())
55
+ if repo_path not in sys.path:
56
+ sys.path.insert(0, repo_path)
57
+
58
+ from model.cloth_masker import AutoMasker, vis_mask
59
+ from model.pipeline import CatVTONPipeline
60
+ from utils import init_weight_dtype, resize_and_crop, resize_and_padding
61
+
62
+ repo_weights_dir = Path(snapshot_download(repo_id=CATVTON_RESUME_PATH))
63
+ self.pipeline = CatVTONPipeline(
64
+ base_ckpt=CATVTON_BASE_MODEL,
65
+ attn_ckpt=str(repo_weights_dir),
66
+ attn_ckpt_version="mix",
67
+ weight_dtype=init_weight_dtype(DEFAULT_MIXED_PRECISION),
68
+ use_tf32=True,
69
+ device=self.device,
70
+ )
71
+ self.mask_processor = VaeImageProcessor(
72
+ vae_scale_factor=8,
73
+ do_normalize=False,
74
+ do_binarize=True,
75
+ do_convert_grayscale=True,
76
+ )
77
+ self.automasker = AutoMasker(
78
+ densepose_ckpt=os.path.join(repo_weights_dir, "DensePose"),
79
+ schp_ckpt=os.path.join(repo_weights_dir, "SCHP"),
80
+ device=self.device,
81
+ )
82
+ self.resize_and_crop = resize_and_crop
83
+ self.resize_and_padding = resize_and_padding
84
+ self.vis_mask = vis_mask
85
+ CATVTON_OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
86
+ self.ready = True
87
+ self.status = "loaded"
88
+
89
+ def run(
90
+ self,
91
+ person_image: Image.Image,
92
+ garment_image: Image.Image,
93
+ cloth_type: str,
94
+ num_inference_steps: int,
95
+ guidance_scale: float,
96
+ seed: int,
97
+ show_type: str,
98
+ ) -> Image.Image:
99
+ self.load()
100
+ assert self.pipeline is not None
101
+ assert self.automasker is not None
102
+ assert self.mask_processor is not None
103
+ assert self.resize_and_crop is not None
104
+ assert self.resize_and_padding is not None
105
+ assert self.vis_mask is not None
106
+
107
+ person_image = self.resize_and_crop(person_image.convert("RGB"), (DEFAULT_WIDTH, DEFAULT_HEIGHT))
108
+ garment_image = self.resize_and_padding(garment_image.convert("RGB"), (DEFAULT_WIDTH, DEFAULT_HEIGHT))
109
+
110
+ generated_mask = self.automasker(person_image, cloth_type)["mask"]
111
+ generated_mask = self.mask_processor.blur(generated_mask, blur_factor=9)
112
+
113
+ generator = None
114
+ if seed != -1:
115
+ generator = torch.Generator(device=self.device).manual_seed(seed)
116
+
117
+ result_image = self.pipeline(
118
+ image=person_image,
119
+ condition_image=garment_image,
120
+ mask=generated_mask,
121
+ num_inference_steps=num_inference_steps,
122
+ guidance_scale=guidance_scale,
123
+ generator=generator,
124
+ )[0]
125
+
126
+ if show_type == "result only":
127
+ return result_image.convert("RGB")
128
+
129
+ masked_person = self.vis_mask(person_image, generated_mask)
130
+ return compose_preview(person_image, garment_image, masked_person, result_image, show_type)
131
+
132
+
133
+ runtime = CatVTONRuntime(repo_dir=CATVTON_REPO_DIR, device=DEVICE)
134
+
135
+
136
+ def prepare_image(image: Image.Image) -> Image.Image:
137
+ return ImageOps.exif_transpose(image).convert("RGB")
138
+
139
+
140
+ def image_grid(images: list[Image.Image], rows: int, cols: int) -> Image.Image:
141
+ if len(images) != rows * cols:
142
+ raise ValueError("The number of images does not match the grid shape.")
143
+ width, height = images[0].size
144
+ grid = Image.new("RGB", size=(cols * width, rows * height))
145
+ for index, image in enumerate(images):
146
+ grid.paste(image, box=(index % cols * width, index // cols * height))
147
+ return grid
148
+
149
+
150
+ def compose_preview(
151
+ person_image: Image.Image,
152
+ garment_image: Image.Image,
153
+ masked_person: Image.Image,
154
+ result_image: Image.Image,
155
+ show_type: str,
156
+ ) -> Image.Image:
157
+ width, height = person_image.size
158
+ if show_type == "input & result":
159
+ side_panel = image_grid([person_image, garment_image], 2, 1).resize((width // 2, height), Image.NEAREST)
160
+ else:
161
+ side_panel = image_grid([person_image, masked_person, garment_image], 3, 1).resize((width // 3, height), Image.NEAREST)
162
+
163
+ preview = Image.new("RGB", (side_panel.width + 5 + width, height), color=(255, 255, 255))
164
+ preview.paste(side_panel, (0, 0))
165
+ preview.paste(result_image.convert("RGB"), (side_panel.width + 5, 0))
166
+ return preview
167
+
168
+
169
+ def try_on(
170
+ person_image: Optional[Image.Image],
171
+ garment_image: Optional[Image.Image],
172
+ cloth_type: str,
173
+ num_inference_steps: int,
174
+ guidance_scale: float,
175
+ seed: int,
176
+ show_type: str,
177
+ ) -> Image.Image:
178
+ if person_image is None or garment_image is None:
179
+ raise gr.Error("Please upload both a shopper photo and a clothing image.")
180
+
181
+ prepared_person = prepare_image(person_image)
182
+ prepared_garment = prepare_image(garment_image)
183
+
184
+ try:
185
+ return runtime.run(
186
+ person_image=prepared_person,
187
+ garment_image=prepared_garment,
188
+ cloth_type=cloth_type,
189
+ num_inference_steps=num_inference_steps,
190
+ guidance_scale=guidance_scale,
191
+ seed=seed,
192
+ show_type=show_type,
193
+ )
194
+ except Exception as exc:
195
+ raise gr.Error(f"CatVTON inference failed: {exc}") from exc
196
+
197
+
198
+ with gr.Blocks(theme=gr.themes.Soft(), title=APP_TITLE) as demo:
199
+ gr.Markdown(f"# {APP_TITLE}")
200
+ gr.Markdown(APP_DESCRIPTION)
201
+ gr.Markdown(
202
+ f"**Runtime:** repo=`{CATVTON_REPO_DIR}` | weights=`{CATVTON_RESUME_PATH}` | device=`{DEVICE}`"
203
+ )
204
+
205
+ with gr.Row():
206
+ with gr.Column(scale=1):
207
+ person_input = gr.Image(type="pil", label="Shopper photo")
208
+ garment_input = gr.Image(type="pil", label="Clothing image")
209
+ cloth_type_input = gr.Radio(
210
+ label="Garment type",
211
+ choices=["upper", "lower", "overall"],
212
+ value="upper",
213
+ )
214
+ submit_button = gr.Button("Try On", variant="primary")
215
+ with gr.Accordion("Advanced options", open=False):
216
+ step_input = gr.Slider(label="Inference steps", minimum=10, maximum=100, step=5, value=DEFAULT_STEPS)
217
+ guidance_input = gr.Slider(label="Guidance scale", minimum=0.0, maximum=7.5, step=0.5, value=DEFAULT_GUIDANCE_SCALE)
218
+ seed_input = gr.Slider(label="Seed", minimum=-1, maximum=10000, step=1, value=DEFAULT_SEED)
219
+ show_type_input = gr.Radio(
220
+ label="Preview mode",
221
+ choices=["result only", "input & result", "input & mask & result"],
222
+ value="result only",
223
+ )
224
+ with gr.Column(scale=1):
225
+ result_output = gr.Image(type="pil", label="Try-on result")
226
+
227
+ gr.Markdown(
228
+ """
229
+ ### Notes
230
+ - This app now uses the real Zheng-Chong `CatVTON/` codebase.
231
+ - Model weights are downloaded on demand from Hugging Face using `zhengchong/CatVTON` by default.
232
+ - For commercial use, verify the upstream CatVTON license and model terms before deployment.
233
+ """
234
+ )
235
+
236
+ submit_button.click(
237
+ fn=try_on,
238
+ inputs=[person_input, garment_input, cloth_type_input, step_input, guidance_input, seed_input, show_type_input],
239
+ outputs=result_output,
240
+ )
241
+
242
+
243
+ demo.queue().launch(show_error=True)
.history/app_20260615122559.py ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ import gradio as gr
10
+ import numpy as np
11
+ import torch
12
+ from diffusers.image_processor import VaeImageProcessor
13
+ from huggingface_hub import snapshot_download
14
+ from PIL import Image, ImageOps
15
+
16
+ APP_TITLE = "ChitraTech Virtual Try-On"
17
+ APP_DESCRIPTION = (
18
+ "Upload a shopper photo and clothing image to run on-demand CatVTON virtual try-on inference "
19
+ "using the Zheng-Chong CatVTON implementation."
20
+ )
21
+ CATVTON_REPO_DIR = Path(os.getenv("CATVTON_REPO_DIR", "./CatVTON"))
22
+ CATVTON_RESUME_PATH = os.getenv("CATVTON_RESUME_PATH", "zhengchong/CatVTON")
23
+ CATVTON_BASE_MODEL = os.getenv("CATVTON_BASE_MODEL", "booksforcharlie/stable-diffusion-inpainting")
24
+ CATVTON_OUTPUT_DIR = Path(os.getenv("CATVTON_OUTPUT_DIR", "./outputs"))
25
+ DEVICE = os.getenv("CATVTON_DEVICE", "cuda")
26
+ DEFAULT_WIDTH = int(os.getenv("CATVTON_WIDTH", "768"))
27
+ DEFAULT_HEIGHT = int(os.getenv("CATVTON_HEIGHT", "1024"))
28
+ DEFAULT_STEPS = int(os.getenv("CATVTON_STEPS", "50"))
29
+ DEFAULT_GUIDANCE_SCALE = float(os.getenv("CATVTON_GUIDANCE_SCALE", "2.5"))
30
+ DEFAULT_MIXED_PRECISION = os.getenv("CATVTON_MIXED_PRECISION", "bf16")
31
+ DEFAULT_SEED = int(os.getenv("CATVTON_SEED", "42"))
32
+
33
+
34
+ @dataclass
35
+ class CatVTONRuntime:
36
+ repo_dir: Path
37
+ device: str
38
+ pipeline: object | None = field(default=None, init=False, repr=False)
39
+ automasker: object | None = field(default=None, init=False, repr=False)
40
+ mask_processor: object | None = field(default=None, init=False, repr=False)
41
+ resize_and_crop: object | None = field(default=None, init=False, repr=False)
42
+ resize_and_padding: object | None = field(default=None, init=False, repr=False)
43
+ vis_mask: object | None = field(default=None, init=False, repr=False)
44
+ ready: bool = False
45
+ status: str = "not loaded"
46
+
47
+ def load(self) -> None:
48
+ if self.ready:
49
+ return
50
+
51
+ if not self.repo_dir.exists():
52
+ raise RuntimeError(f"CatVTON repository not found at '{self.repo_dir}'.")
53
+
54
+ repo_path = str(self.repo_dir.resolve())
55
+ if repo_path not in sys.path:
56
+ sys.path.insert(0, repo_path)
57
+
58
+ from model.cloth_masker import AutoMasker, vis_mask
59
+ from model.pipeline import CatVTONPipeline
60
+ from utils import init_weight_dtype, resize_and_crop, resize_and_padding
61
+
62
+ repo_weights_dir = Path(snapshot_download(repo_id=CATVTON_RESUME_PATH))
63
+ self.pipeline = CatVTONPipeline(
64
+ base_ckpt=CATVTON_BASE_MODEL,
65
+ attn_ckpt=str(repo_weights_dir),
66
+ attn_ckpt_version="mix",
67
+ weight_dtype=init_weight_dtype(DEFAULT_MIXED_PRECISION),
68
+ use_tf32=True,
69
+ device=self.device,
70
+ )
71
+ self.mask_processor = VaeImageProcessor(
72
+ vae_scale_factor=8,
73
+ do_normalize=False,
74
+ do_binarize=True,
75
+ do_convert_grayscale=True,
76
+ )
77
+ self.automasker = AutoMasker(
78
+ densepose_ckpt=os.path.join(repo_weights_dir, "DensePose"),
79
+ schp_ckpt=os.path.join(repo_weights_dir, "SCHP"),
80
+ device=self.device,
81
+ )
82
+ self.resize_and_crop = resize_and_crop
83
+ self.resize_and_padding = resize_and_padding
84
+ self.vis_mask = vis_mask
85
+ CATVTON_OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
86
+ self.ready = True
87
+ self.status = "loaded"
88
+
89
+ def run(
90
+ self,
91
+ person_image: Image.Image,
92
+ garment_image: Image.Image,
93
+ cloth_type: str,
94
+ num_inference_steps: int,
95
+ guidance_scale: float,
96
+ seed: int,
97
+ show_type: str,
98
+ ) -> Image.Image:
99
+ self.load()
100
+ assert self.pipeline is not None
101
+ assert self.automasker is not None
102
+ assert self.mask_processor is not None
103
+ assert self.resize_and_crop is not None
104
+ assert self.resize_and_padding is not None
105
+ assert self.vis_mask is not None
106
+
107
+ person_image = self.resize_and_crop(person_image.convert("RGB"), (DEFAULT_WIDTH, DEFAULT_HEIGHT))
108
+ garment_image = self.resize_and_padding(garment_image.convert("RGB"), (DEFAULT_WIDTH, DEFAULT_HEIGHT))
109
+
110
+ generated_mask = self.automasker(person_image, cloth_type)["mask"]
111
+ generated_mask = self.mask_processor.blur(generated_mask, blur_factor=9)
112
+
113
+ generator = None
114
+ if seed != -1:
115
+ generator = torch.Generator(device=self.device).manual_seed(seed)
116
+
117
+ result_image = self.pipeline(
118
+ image=person_image,
119
+ condition_image=garment_image,
120
+ mask=generated_mask,
121
+ num_inference_steps=num_inference_steps,
122
+ guidance_scale=guidance_scale,
123
+ generator=generator,
124
+ )[0]
125
+
126
+ if show_type == "result only":
127
+ return result_image.convert("RGB")
128
+
129
+ masked_person = self.vis_mask(person_image, generated_mask)
130
+ return compose_preview(person_image, garment_image, masked_person, result_image, show_type)
131
+
132
+
133
+ runtime = CatVTONRuntime(repo_dir=CATVTON_REPO_DIR, device=DEVICE)
134
+
135
+
136
+ def prepare_image(image: Image.Image) -> Image.Image:
137
+ return ImageOps.exif_transpose(image).convert("RGB")
138
+
139
+
140
+ def image_grid(images: list[Image.Image], rows: int, cols: int) -> Image.Image:
141
+ if len(images) != rows * cols:
142
+ raise ValueError("The number of images does not match the grid shape.")
143
+ width, height = images[0].size
144
+ grid = Image.new("RGB", size=(cols * width, rows * height))
145
+ for index, image in enumerate(images):
146
+ grid.paste(image, box=(index % cols * width, index // cols * height))
147
+ return grid
148
+
149
+
150
+ def compose_preview(
151
+ person_image: Image.Image,
152
+ garment_image: Image.Image,
153
+ masked_person: Image.Image,
154
+ result_image: Image.Image,
155
+ show_type: str,
156
+ ) -> Image.Image:
157
+ width, height = person_image.size
158
+ if show_type == "input & result":
159
+ side_panel = image_grid([person_image, garment_image], 2, 1).resize((width // 2, height), Image.NEAREST)
160
+ else:
161
+ side_panel = image_grid([person_image, masked_person, garment_image], 3, 1).resize((width // 3, height), Image.NEAREST)
162
+
163
+ preview = Image.new("RGB", (side_panel.width + 5 + width, height), color=(255, 255, 255))
164
+ preview.paste(side_panel, (0, 0))
165
+ preview.paste(result_image.convert("RGB"), (side_panel.width + 5, 0))
166
+ return preview
167
+
168
+
169
+ def try_on(
170
+ person_image: Optional[Image.Image],
171
+ garment_image: Optional[Image.Image],
172
+ cloth_type: str,
173
+ num_inference_steps: int,
174
+ guidance_scale: float,
175
+ seed: int,
176
+ show_type: str,
177
+ ) -> Image.Image:
178
+ if person_image is None or garment_image is None:
179
+ raise gr.Error("Please upload both a shopper photo and a clothing image.")
180
+
181
+ prepared_person = prepare_image(person_image)
182
+ prepared_garment = prepare_image(garment_image)
183
+
184
+ try:
185
+ return runtime.run(
186
+ person_image=prepared_person,
187
+ garment_image=prepared_garment,
188
+ cloth_type=cloth_type,
189
+ num_inference_steps=num_inference_steps,
190
+ guidance_scale=guidance_scale,
191
+ seed=seed,
192
+ show_type=show_type,
193
+ )
194
+ except Exception as exc:
195
+ raise gr.Error(f"CatVTON inference failed: {exc}") from exc
196
+
197
+
198
+ with gr.Blocks(theme=gr.themes.Soft(), title=APP_TITLE) as demo:
199
+ gr.Markdown(f"# {APP_TITLE}")
200
+ gr.Markdown(APP_DESCRIPTION)
201
+ gr.Markdown(
202
+ f"**Runtime:** repo=`{CATVTON_REPO_DIR}` | weights=`{CATVTON_RESUME_PATH}` | device=`{DEVICE}`"
203
+ )
204
+
205
+ with gr.Row():
206
+ with gr.Column(scale=1):
207
+ person_input = gr.Image(type="pil", label="Shopper photo")
208
+ garment_input = gr.Image(type="pil", label="Clothing image")
209
+ cloth_type_input = gr.Radio(
210
+ label="Garment type",
211
+ choices=["upper", "lower", "overall"],
212
+ value="upper",
213
+ )
214
+ submit_button = gr.Button("Try On", variant="primary")
215
+ with gr.Accordion("Advanced options", open=False):
216
+ step_input = gr.Slider(label="Inference steps", minimum=10, maximum=100, step=5, value=DEFAULT_STEPS)
217
+ guidance_input = gr.Slider(label="Guidance scale", minimum=0.0, maximum=7.5, step=0.5, value=DEFAULT_GUIDANCE_SCALE)
218
+ seed_input = gr.Slider(label="Seed", minimum=-1, maximum=10000, step=1, value=DEFAULT_SEED)
219
+ show_type_input = gr.Radio(
220
+ label="Preview mode",
221
+ choices=["result only", "input & result", "input & mask & result"],
222
+ value="result only",
223
+ )
224
+ with gr.Column(scale=1):
225
+ result_output = gr.Image(type="pil", label="Try-on result")
226
+
227
+ gr.Markdown(
228
+ """
229
+ ### Notes
230
+ - This app now uses the real Zheng-Chong `CatVTON/` codebase.
231
+ - Model weights are downloaded on demand from Hugging Face using `zhengchong/CatVTON` by default.
232
+ - For commercial use, verify the upstream CatVTON license and model terms before deployment.
233
+ """
234
+ )
235
+
236
+ submit_button.click(
237
+ fn=try_on,
238
+ inputs=[person_input, garment_input, cloth_type_input, step_input, guidance_input, seed_input, show_type_input],
239
+ outputs=result_output,
240
+ )
241
+
242
+
243
+ demo.queue().launch(show_error=True)
.history/requirements_20260615121839.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio==6.5.1
2
+ pillow>=10.0.0
3
+ numpy>=1.26.0
4
+ torch>=2.3.0
5
+ torchvision>=0.18.0
6
+ transformers>=4.40.0
7
+ accelerate>=0.30.0
8
+ diffusers>=0.29.0
9
+ safetensors>=0.4.3
10
+ huggingface_hub>=0.23.0
11
+ opencv-python-headless>=4.9.0.80
12
+ scikit-image>=0.22.0
.history/requirements_20260615122003.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio==6.5.1
2
+ pillow>=10.0.0
3
+ numpy>=1.26.0
4
+ torch>=2.3.0
5
+ torchvision>=0.18.0
6
+ transformers>=4.40.0
7
+ accelerate>=0.30.0
8
+ diffusers>=0.29.0
9
+ safetensors>=0.4.3
10
+ huggingface_hub>=0.23.0
11
+ opencv-python-headless>=4.9.0.80
12
+ scikit-image>=0.22.0
.history/requirements_20260615122555.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch==2.4.0
2
+ torchvision==0.19.0
3
+ accelerate==0.31.0
4
+ git+https://github.com/huggingface/diffusers.git
5
+ matplotlib==3.9.1
6
+ numpy==1.26.4
7
+ opencv-python-headless==4.10.0.84
8
+ pillow==10.3.0
9
+ PyYAML==6.0.1
10
+ scipy==1.13.1
11
+ setuptools==51.0.0
12
+ scikit-image==0.24.0
13
+ tqdm==4.66.4
14
+ transformers==4.46.3
15
+ fvcore==0.1.5.post20221221
16
+ cloudpickle==3.0.0
17
+ omegaconf==2.3.0
18
+ pycocotools==2.0.8
19
+ av==12.3.0
20
+ gradio==4.41.0
21
+ peft>=0.17.0
22
+ huggingface_hub>=0.34.0,<2.0
23
+ safetensors>=0.4.3
.history/requirements_20260615122559.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch==2.4.0
2
+ torchvision==0.19.0
3
+ accelerate==0.31.0
4
+ git+https://github.com/huggingface/diffusers.git
5
+ matplotlib==3.9.1
6
+ numpy==1.26.4
7
+ opencv-python-headless==4.10.0.84
8
+ pillow==10.3.0
9
+ PyYAML==6.0.1
10
+ scipy==1.13.1
11
+ setuptools==51.0.0
12
+ scikit-image==0.24.0
13
+ tqdm==4.66.4
14
+ transformers==4.46.3
15
+ fvcore==0.1.5.post20221221
16
+ cloudpickle==3.0.0
17
+ omegaconf==2.3.0
18
+ pycocotools==2.0.8
19
+ av==12.3.0
20
+ gradio==4.41.0
21
+ peft>=0.17.0
22
+ huggingface_hub>=0.34.0,<2.0
23
+ safetensors>=0.4.3
CatVTON ADDED
@@ -0,0 +1 @@
 
 
1
+ Subproject commit 7818397f25613beedb3d861a34769f607cfcf3b1
README.md CHANGED
@@ -1,16 +1,74 @@
1
  ---
2
- title: ChitraTech
3
- emoji: 💬
4
- colorFrom: yellow
5
- colorTo: purple
6
  sdk: gradio
7
  sdk_version: 6.5.1
8
  app_file: app.py
9
  pinned: false
10
- hf_oauth: true
11
- hf_oauth_scopes:
12
- - inference-api
13
  license: mit
14
  ---
15
 
16
- An example chatbot using [Gradio](https://gradio.app), [`huggingface_hub`](https://huggingface.co/docs/huggingface_hub/v0.22.2/en/index), and the [Hugging Face Inference API](https://huggingface.co/docs/api-inference/index).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: ChitraTech Virtual Try-On
3
+ emoji: 👕
4
+ colorFrom: pink
5
+ colorTo: indigo
6
  sdk: gradio
7
  sdk_version: 6.5.1
8
  app_file: app.py
9
  pinned: false
 
 
 
10
  license: mit
11
  ---
12
 
13
+ # ChitraTech Virtual Try-On
14
+
15
+ This repository is a Hugging Face Space for an on-demand virtual try-on experience powered by **CatVTON**.
16
+
17
+ ## Planned flow
18
+
19
+ 1. A shopper clicks **Try On** on your ecommerce product page.
20
+ 2. Your frontend sends the shopper photo and the selected clothing image to this Hugging Face Space.
21
+ 3. The Space runs CatVTON inference only for that request.
22
+ 4. The generated try-on image is returned to the frontend for preview.
23
+
24
+ ## Current status
25
+
26
+ The current Space now includes:
27
+ - a Gradio interface for person and garment uploads,
28
+ - lazy pipeline initialization for on-demand inference,
29
+ - automatic detection of CatVTON code under `CatVTON/`,
30
+ - automatic detection of model weights under `checkpoints/CatVTON/`,
31
+ - a safe demo fallback when CatVTON is not fully installed yet.
32
+
33
+ ## Repository structure
34
+
35
+ Recommended layout:
36
+ - `app.py`
37
+ - `requirements.txt`
38
+ - `CatVTON/` for the cloned CatVTON repository
39
+ - `checkpoints/CatVTON/` for model weights
40
+ - optional `examples/` for demo assets
41
+
42
+ ## Setup steps
43
+
44
+ ### 1. Add CatVTON source code
45
+
46
+ Clone or copy your CatVTON implementation into:
47
+ - `CatVTON/`
48
+
49
+ If your fork uses a different folder, set:
50
+ - `CATVTON_REPO_DIR`
51
+
52
+ ### 2. Add model weights
53
+
54
+ Place CatVTON checkpoints in:
55
+ - `checkpoints/CatVTON/`
56
+
57
+ Or set:
58
+ - `CATVTON_MODEL_DIR`
59
+
60
+ ### 3. Confirm the inference entrypoint
61
+
62
+ The current app tries common Python and script entrypoints automatically. If your CatVTON fork uses different class names, function names, or CLI arguments, update the loading logic in `app.py`.
63
+
64
+ ### 4. Push to Hugging Face Spaces
65
+
66
+ After adding the CatVTON repo and weights references, push the repository. Hugging Face will rebuild the Space automatically.
67
+
68
+ ## Frontend integration
69
+
70
+ Your ecommerce frontend can call the Hugging Face Space directly when needed, making this a free, on-demand approach without keeping a GPU server running continuously.
71
+
72
+ ## Important note
73
+
74
+ This app is now deployment-ready as a shell, but **real try-on output depends on your exact CatVTON fork and checkpoint format**. If the repository layout differs, `app.py` may need a small adapter for the correct import path or inference call.
app.py CHANGED
@@ -1,16 +1,243 @@
 
 
 
 
 
 
 
 
1
  import gradio as gr
 
 
 
 
 
2
 
3
- def try_on(person, cloth):
4
- return person
5
-
6
- demo = gr.Interface(
7
- fn=try_on,
8
- inputs=[
9
- gr.Image(type="pil"),
10
- gr.Image(type="pil")
11
- ],
12
- outputs="image",
13
- title="CP-VTON Test Demo"
14
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- demo.launch()
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
  import gradio as gr
10
+ import numpy as np
11
+ import torch
12
+ from diffusers.image_processor import VaeImageProcessor
13
+ from huggingface_hub import snapshot_download
14
+ from PIL import Image, ImageOps
15
 
16
+ APP_TITLE = "ChitraTech Virtual Try-On"
17
+ APP_DESCRIPTION = (
18
+ "Upload a shopper photo and clothing image to run on-demand CatVTON virtual try-on inference "
19
+ "using the Zheng-Chong CatVTON implementation."
 
 
 
 
 
 
 
20
  )
21
+ CATVTON_REPO_DIR = Path(os.getenv("CATVTON_REPO_DIR", "./CatVTON"))
22
+ CATVTON_RESUME_PATH = os.getenv("CATVTON_RESUME_PATH", "zhengchong/CatVTON")
23
+ CATVTON_BASE_MODEL = os.getenv("CATVTON_BASE_MODEL", "booksforcharlie/stable-diffusion-inpainting")
24
+ CATVTON_OUTPUT_DIR = Path(os.getenv("CATVTON_OUTPUT_DIR", "./outputs"))
25
+ DEVICE = os.getenv("CATVTON_DEVICE", "cuda")
26
+ DEFAULT_WIDTH = int(os.getenv("CATVTON_WIDTH", "768"))
27
+ DEFAULT_HEIGHT = int(os.getenv("CATVTON_HEIGHT", "1024"))
28
+ DEFAULT_STEPS = int(os.getenv("CATVTON_STEPS", "50"))
29
+ DEFAULT_GUIDANCE_SCALE = float(os.getenv("CATVTON_GUIDANCE_SCALE", "2.5"))
30
+ DEFAULT_MIXED_PRECISION = os.getenv("CATVTON_MIXED_PRECISION", "bf16")
31
+ DEFAULT_SEED = int(os.getenv("CATVTON_SEED", "42"))
32
+
33
+
34
+ @dataclass
35
+ class CatVTONRuntime:
36
+ repo_dir: Path
37
+ device: str
38
+ pipeline: object | None = field(default=None, init=False, repr=False)
39
+ automasker: object | None = field(default=None, init=False, repr=False)
40
+ mask_processor: object | None = field(default=None, init=False, repr=False)
41
+ resize_and_crop: object | None = field(default=None, init=False, repr=False)
42
+ resize_and_padding: object | None = field(default=None, init=False, repr=False)
43
+ vis_mask: object | None = field(default=None, init=False, repr=False)
44
+ ready: bool = False
45
+ status: str = "not loaded"
46
+
47
+ def load(self) -> None:
48
+ if self.ready:
49
+ return
50
+
51
+ if not self.repo_dir.exists():
52
+ raise RuntimeError(f"CatVTON repository not found at '{self.repo_dir}'.")
53
+
54
+ repo_path = str(self.repo_dir.resolve())
55
+ if repo_path not in sys.path:
56
+ sys.path.insert(0, repo_path)
57
+
58
+ from model.cloth_masker import AutoMasker, vis_mask
59
+ from model.pipeline import CatVTONPipeline
60
+ from utils import init_weight_dtype, resize_and_crop, resize_and_padding
61
+
62
+ repo_weights_dir = Path(snapshot_download(repo_id=CATVTON_RESUME_PATH))
63
+ self.pipeline = CatVTONPipeline(
64
+ base_ckpt=CATVTON_BASE_MODEL,
65
+ attn_ckpt=str(repo_weights_dir),
66
+ attn_ckpt_version="mix",
67
+ weight_dtype=init_weight_dtype(DEFAULT_MIXED_PRECISION),
68
+ use_tf32=True,
69
+ device=self.device,
70
+ )
71
+ self.mask_processor = VaeImageProcessor(
72
+ vae_scale_factor=8,
73
+ do_normalize=False,
74
+ do_binarize=True,
75
+ do_convert_grayscale=True,
76
+ )
77
+ self.automasker = AutoMasker(
78
+ densepose_ckpt=os.path.join(repo_weights_dir, "DensePose"),
79
+ schp_ckpt=os.path.join(repo_weights_dir, "SCHP"),
80
+ device=self.device,
81
+ )
82
+ self.resize_and_crop = resize_and_crop
83
+ self.resize_and_padding = resize_and_padding
84
+ self.vis_mask = vis_mask
85
+ CATVTON_OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
86
+ self.ready = True
87
+ self.status = "loaded"
88
+
89
+ def run(
90
+ self,
91
+ person_image: Image.Image,
92
+ garment_image: Image.Image,
93
+ cloth_type: str,
94
+ num_inference_steps: int,
95
+ guidance_scale: float,
96
+ seed: int,
97
+ show_type: str,
98
+ ) -> Image.Image:
99
+ self.load()
100
+ assert self.pipeline is not None
101
+ assert self.automasker is not None
102
+ assert self.mask_processor is not None
103
+ assert self.resize_and_crop is not None
104
+ assert self.resize_and_padding is not None
105
+ assert self.vis_mask is not None
106
+
107
+ person_image = self.resize_and_crop(person_image.convert("RGB"), (DEFAULT_WIDTH, DEFAULT_HEIGHT))
108
+ garment_image = self.resize_and_padding(garment_image.convert("RGB"), (DEFAULT_WIDTH, DEFAULT_HEIGHT))
109
+
110
+ generated_mask = self.automasker(person_image, cloth_type)["mask"]
111
+ generated_mask = self.mask_processor.blur(generated_mask, blur_factor=9)
112
+
113
+ generator = None
114
+ if seed != -1:
115
+ generator = torch.Generator(device=self.device).manual_seed(seed)
116
+
117
+ result_image = self.pipeline(
118
+ image=person_image,
119
+ condition_image=garment_image,
120
+ mask=generated_mask,
121
+ num_inference_steps=num_inference_steps,
122
+ guidance_scale=guidance_scale,
123
+ generator=generator,
124
+ )[0]
125
+
126
+ if show_type == "result only":
127
+ return result_image.convert("RGB")
128
+
129
+ masked_person = self.vis_mask(person_image, generated_mask)
130
+ return compose_preview(person_image, garment_image, masked_person, result_image, show_type)
131
+
132
+
133
+ runtime = CatVTONRuntime(repo_dir=CATVTON_REPO_DIR, device=DEVICE)
134
+
135
+
136
+ def prepare_image(image: Image.Image) -> Image.Image:
137
+ return ImageOps.exif_transpose(image).convert("RGB")
138
+
139
+
140
+ def image_grid(images: list[Image.Image], rows: int, cols: int) -> Image.Image:
141
+ if len(images) != rows * cols:
142
+ raise ValueError("The number of images does not match the grid shape.")
143
+ width, height = images[0].size
144
+ grid = Image.new("RGB", size=(cols * width, rows * height))
145
+ for index, image in enumerate(images):
146
+ grid.paste(image, box=(index % cols * width, index // cols * height))
147
+ return grid
148
+
149
+
150
+ def compose_preview(
151
+ person_image: Image.Image,
152
+ garment_image: Image.Image,
153
+ masked_person: Image.Image,
154
+ result_image: Image.Image,
155
+ show_type: str,
156
+ ) -> Image.Image:
157
+ width, height = person_image.size
158
+ if show_type == "input & result":
159
+ side_panel = image_grid([person_image, garment_image], 2, 1).resize((width // 2, height), Image.NEAREST)
160
+ else:
161
+ side_panel = image_grid([person_image, masked_person, garment_image], 3, 1).resize((width // 3, height), Image.NEAREST)
162
+
163
+ preview = Image.new("RGB", (side_panel.width + 5 + width, height), color=(255, 255, 255))
164
+ preview.paste(side_panel, (0, 0))
165
+ preview.paste(result_image.convert("RGB"), (side_panel.width + 5, 0))
166
+ return preview
167
+
168
+
169
+ def try_on(
170
+ person_image: Optional[Image.Image],
171
+ garment_image: Optional[Image.Image],
172
+ cloth_type: str,
173
+ num_inference_steps: int,
174
+ guidance_scale: float,
175
+ seed: int,
176
+ show_type: str,
177
+ ) -> Image.Image:
178
+ if person_image is None or garment_image is None:
179
+ raise gr.Error("Please upload both a shopper photo and a clothing image.")
180
+
181
+ prepared_person = prepare_image(person_image)
182
+ prepared_garment = prepare_image(garment_image)
183
+
184
+ try:
185
+ return runtime.run(
186
+ person_image=prepared_person,
187
+ garment_image=prepared_garment,
188
+ cloth_type=cloth_type,
189
+ num_inference_steps=num_inference_steps,
190
+ guidance_scale=guidance_scale,
191
+ seed=seed,
192
+ show_type=show_type,
193
+ )
194
+ except Exception as exc:
195
+ raise gr.Error(f"CatVTON inference failed: {exc}") from exc
196
+
197
+
198
+ with gr.Blocks(theme=gr.themes.Soft(), title=APP_TITLE) as demo:
199
+ gr.Markdown(f"# {APP_TITLE}")
200
+ gr.Markdown(APP_DESCRIPTION)
201
+ gr.Markdown(
202
+ f"**Runtime:** repo=`{CATVTON_REPO_DIR}` | weights=`{CATVTON_RESUME_PATH}` | device=`{DEVICE}`"
203
+ )
204
+
205
+ with gr.Row():
206
+ with gr.Column(scale=1):
207
+ person_input = gr.Image(type="pil", label="Shopper photo")
208
+ garment_input = gr.Image(type="pil", label="Clothing image")
209
+ cloth_type_input = gr.Radio(
210
+ label="Garment type",
211
+ choices=["upper", "lower", "overall"],
212
+ value="upper",
213
+ )
214
+ submit_button = gr.Button("Try On", variant="primary")
215
+ with gr.Accordion("Advanced options", open=False):
216
+ step_input = gr.Slider(label="Inference steps", minimum=10, maximum=100, step=5, value=DEFAULT_STEPS)
217
+ guidance_input = gr.Slider(label="Guidance scale", minimum=0.0, maximum=7.5, step=0.5, value=DEFAULT_GUIDANCE_SCALE)
218
+ seed_input = gr.Slider(label="Seed", minimum=-1, maximum=10000, step=1, value=DEFAULT_SEED)
219
+ show_type_input = gr.Radio(
220
+ label="Preview mode",
221
+ choices=["result only", "input & result", "input & mask & result"],
222
+ value="result only",
223
+ )
224
+ with gr.Column(scale=1):
225
+ result_output = gr.Image(type="pil", label="Try-on result")
226
+
227
+ gr.Markdown(
228
+ """
229
+ ### Notes
230
+ - This app now uses the real Zheng-Chong `CatVTON/` codebase.
231
+ - Model weights are downloaded on demand from Hugging Face using `zhengchong/CatVTON` by default.
232
+ - For commercial use, verify the upstream CatVTON license and model terms before deployment.
233
+ """
234
+ )
235
+
236
+ submit_button.click(
237
+ fn=try_on,
238
+ inputs=[person_input, garment_input, cloth_type_input, step_input, guidance_input, seed_input, show_type_input],
239
+ outputs=result_output,
240
+ )
241
+
242
 
243
+ demo.queue().launch(show_error=True)
requirements.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch==2.4.0
2
+ torchvision==0.19.0
3
+ accelerate==0.31.0
4
+ git+https://github.com/huggingface/diffusers.git
5
+ matplotlib==3.9.1
6
+ numpy==1.26.4
7
+ opencv-python-headless==4.10.0.84
8
+ pillow==10.3.0
9
+ PyYAML==6.0.1
10
+ scipy==1.13.1
11
+ setuptools==51.0.0
12
+ scikit-image==0.24.0
13
+ tqdm==4.66.4
14
+ transformers==4.46.3
15
+ fvcore==0.1.5.post20221221
16
+ cloudpickle==3.0.0
17
+ omegaconf==2.3.0
18
+ pycocotools==2.0.8
19
+ av==12.3.0
20
+ gradio==4.41.0
21
+ peft>=0.17.0
22
+ huggingface_hub>=0.34.0,<2.0
23
+ safetensors>=0.4.3