Spaces:
Running on Zero
Running on Zero
Delete app.py
Browse files
app.py
DELETED
|
@@ -1,379 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import gc
|
| 3 |
-
import gradio as gr
|
| 4 |
-
from gradio import Server
|
| 5 |
-
from fastapi.responses import HTMLResponse
|
| 6 |
-
import numpy as np
|
| 7 |
-
import spaces
|
| 8 |
-
import torch
|
| 9 |
-
import random
|
| 10 |
-
import base64
|
| 11 |
-
import json
|
| 12 |
-
from io import BytesIO
|
| 13 |
-
from PIL import Image
|
| 14 |
-
|
| 15 |
-
MAX_SEED = np.iinfo(np.int32).max
|
| 16 |
-
LANCZOS = getattr(Image, "Resampling", Image).LANCZOS
|
| 17 |
-
|
| 18 |
-
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 19 |
-
|
| 20 |
-
print("CUDA_VISIBLE_DEVICES=", os.environ.get("CUDA_VISIBLE_DEVICES"))
|
| 21 |
-
print("torch.__version__ =", torch.__version__)
|
| 22 |
-
print("torch.version.cuda =", torch.version.cuda)
|
| 23 |
-
print("cuda available:", torch.cuda.is_available())
|
| 24 |
-
print("cuda device count:", torch.cuda.device_count())
|
| 25 |
-
if torch.cuda.is_available():
|
| 26 |
-
print("current device:", torch.cuda.current_device())
|
| 27 |
-
print("device name:", torch.cuda.get_device_name(torch.cuda.current_device()))
|
| 28 |
-
|
| 29 |
-
print("Using device:", device)
|
| 30 |
-
|
| 31 |
-
from diffusers import FlowMatchEulerDiscreteScheduler
|
| 32 |
-
from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
|
| 33 |
-
from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
|
| 34 |
-
from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
|
| 35 |
-
|
| 36 |
-
dtype = torch.bfloat16
|
| 37 |
-
|
| 38 |
-
pipe = QwenImageEditPlusPipeline.from_pretrained(
|
| 39 |
-
"Qwen/Qwen-Image-Edit-2511",
|
| 40 |
-
transformer=QwenImageTransformer2DModel.from_pretrained(
|
| 41 |
-
"prithivMLmods/Qwen-Image-Edit-Rapid-AIO-V19",
|
| 42 |
-
torch_dtype=dtype,
|
| 43 |
-
device_map="cuda",
|
| 44 |
-
),
|
| 45 |
-
torch_dtype=dtype,
|
| 46 |
-
).to(device)
|
| 47 |
-
|
| 48 |
-
try:
|
| 49 |
-
pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
|
| 50 |
-
print("Flash Attention 3 Processor set successfully.")
|
| 51 |
-
except Exception as e:
|
| 52 |
-
print(f"Warning: Could not set FA3 processor: {e}")
|
| 53 |
-
|
| 54 |
-
ADAPTER_SPECS = {
|
| 55 |
-
"Multiple-Angles": {
|
| 56 |
-
"repo": "dx8152/Qwen-Edit-2509-Multiple-angles",
|
| 57 |
-
"weights": "镜头转换.safetensors",
|
| 58 |
-
"adapter_name": "multiple-angles",
|
| 59 |
-
},
|
| 60 |
-
"Photo-to-Anime": {
|
| 61 |
-
"repo": "autoweeb/Qwen-Image-Edit-2509-Photo-to-Anime",
|
| 62 |
-
"weights": "Qwen-Image-Edit-2509-Photo-to-Anime_000001000.safetensors",
|
| 63 |
-
"adapter_name": "photo-to-anime",
|
| 64 |
-
},
|
| 65 |
-
"Anime-V2": {
|
| 66 |
-
"repo": "prithivMLmods/Qwen-Image-Edit-2511-Anime",
|
| 67 |
-
"weights": "Qwen-Image-Edit-2511-Anime-2000.safetensors",
|
| 68 |
-
"adapter_name": "anime-v2",
|
| 69 |
-
},
|
| 70 |
-
"Light-Migration": {
|
| 71 |
-
"repo": "dx8152/Qwen-Edit-2509-Light-Migration",
|
| 72 |
-
"weights": "参考色调.safetensors",
|
| 73 |
-
"adapter_name": "light-migration",
|
| 74 |
-
},
|
| 75 |
-
"Upscaler": {
|
| 76 |
-
"repo": "starsfriday/Qwen-Image-Edit-2511-Upscale2K",
|
| 77 |
-
"weights": "qwen_image_edit_2511_upscale.safetensors",
|
| 78 |
-
"adapter_name": "upscale-2k",
|
| 79 |
-
},
|
| 80 |
-
"Style-Transfer": {
|
| 81 |
-
"repo": "zooeyy/Style-Transfer",
|
| 82 |
-
"weights": "Style Transfer-Alpha-V0.1.safetensors",
|
| 83 |
-
"adapter_name": "style-transfer",
|
| 84 |
-
},
|
| 85 |
-
"Manga-Tone": {
|
| 86 |
-
"repo": "nappa114514/Qwen-Image-Edit-2509-Manga-Tone",
|
| 87 |
-
"weights": "tone001.safetensors",
|
| 88 |
-
"adapter_name": "manga-tone",
|
| 89 |
-
},
|
| 90 |
-
"Anything2Real": {
|
| 91 |
-
"repo": "lrzjason/Anything2Real_2601",
|
| 92 |
-
"weights": "anything2real_2601.safetensors",
|
| 93 |
-
"adapter_name": "anything2real",
|
| 94 |
-
},
|
| 95 |
-
"Fal-Multiple-Angles": {
|
| 96 |
-
"repo": "fal/Qwen-Image-Edit-2511-Multiple-Angles-LoRA",
|
| 97 |
-
"weights": "qwen-image-edit-2511-multiple-angles-lora.safetensors",
|
| 98 |
-
"adapter_name": "fal-multiple-angles",
|
| 99 |
-
},
|
| 100 |
-
"Polaroid-Photo": {
|
| 101 |
-
"repo": "prithivMLmods/Qwen-Image-Edit-2511-Polaroid-Photo",
|
| 102 |
-
"weights": "Qwen-Image-Edit-2511-Polaroid-Photo.safetensors",
|
| 103 |
-
"adapter_name": "polaroid-photo",
|
| 104 |
-
},
|
| 105 |
-
"Unblur-Anything": {
|
| 106 |
-
"repo": "prithivMLmods/Qwen-Image-Edit-2511-Unblur-Upscale",
|
| 107 |
-
"weights": "Qwen-Image-Edit-Unblur-Upscale_15.safetensors",
|
| 108 |
-
"adapter_name": "unblur-anything",
|
| 109 |
-
},
|
| 110 |
-
"Midnight-Noir-Eyes-Spotlight": {
|
| 111 |
-
"repo": "prithivMLmods/Qwen-Image-Edit-2511-Midnight-Noir-Eyes-Spotlight",
|
| 112 |
-
"weights": "Qwen-Image-Edit-2511-Midnight-Noir-Eyes-Spotlight.safetensors",
|
| 113 |
-
"adapter_name": "midnight-noir-eyes-spotlight",
|
| 114 |
-
},
|
| 115 |
-
"Hyper-Realistic-Portrait": {
|
| 116 |
-
"repo": "prithivMLmods/Qwen-Image-Edit-2511-Hyper-Realistic-Portrait",
|
| 117 |
-
"weights": "HRP_20.safetensors",
|
| 118 |
-
"adapter_name": "hyper-realistic-portrait",
|
| 119 |
-
},
|
| 120 |
-
"Ultra-Realistic-Portrait": {
|
| 121 |
-
"repo": "prithivMLmods/Qwen-Image-Edit-2511-Ultra-Realistic-Portrait",
|
| 122 |
-
"weights": "URP_20.safetensors",
|
| 123 |
-
"adapter_name": "ultra-realistic-portrait",
|
| 124 |
-
},
|
| 125 |
-
"Pixar-Inspired-3D": {
|
| 126 |
-
"repo": "prithivMLmods/Qwen-Image-Edit-2511-Pixar-Inspired-3D",
|
| 127 |
-
"weights": "PI3_20.safetensors",
|
| 128 |
-
"adapter_name": "pi3",
|
| 129 |
-
},
|
| 130 |
-
"Noir-Comic-Book": {
|
| 131 |
-
"repo": "prithivMLmods/Qwen-Image-Edit-2511-Noir-Comic-Book-Panel",
|
| 132 |
-
"weights": "Noir-Comic-Book-Panel_20.safetensors",
|
| 133 |
-
"adapter_name": "ncb",
|
| 134 |
-
},
|
| 135 |
-
"Any-light": {
|
| 136 |
-
"repo": "lilylilith/QIE-2511-MP-AnyLight",
|
| 137 |
-
"weights": "QIE-2511-AnyLight_.safetensors",
|
| 138 |
-
"adapter_name": "any-light",
|
| 139 |
-
},
|
| 140 |
-
"Studio-DeLight": {
|
| 141 |
-
"repo": "prithivMLmods/QIE-2511-Studio-DeLight",
|
| 142 |
-
"weights": "QIE-2511-Studio-DeLight-5000.safetensors",
|
| 143 |
-
"adapter_name": "studio-delight",
|
| 144 |
-
},
|
| 145 |
-
"Cinematic-FlatLog": {
|
| 146 |
-
"repo": "prithivMLmods/QIE-2511-Cinematic-FlatLog-Control",
|
| 147 |
-
"weights": "QIE-2511-Cinematic-FlatLog-Control-3200.safetensors",
|
| 148 |
-
"adapter_name": "flat-log",
|
| 149 |
-
},
|
| 150 |
-
}
|
| 151 |
-
|
| 152 |
-
LOADED_ADAPTERS: set = set()
|
| 153 |
-
ADAPTER_NAMES = list(ADAPTER_SPECS.keys())
|
| 154 |
-
|
| 155 |
-
EXAMPLES_CONFIG = [
|
| 156 |
-
{"images": ["examples/B.jpg"], "prompt": "Transform into anime.", "lora": "Photo-to-Anime"},
|
| 157 |
-
{"images": ["examples/HRP.jpg"], "prompt": "Transform into a hyper-realistic face portrait.", "lora": "Hyper-Realistic-Portrait"},
|
| 158 |
-
{"images": ["examples/A.jpeg"], "prompt": "Rotate the camera 45 degrees to the right.", "lora": "Multiple-Angles"},
|
| 159 |
-
{"images": ["examples/U.jpg"], "prompt": "Upscale this picture to 4K resolution.", "lora": "Upscaler"},
|
| 160 |
-
{"images": ["examples/L1.jpg", "examples/L2.jpg"], "prompt": "Apply the lighting from image 2 to image 1.", "lora": "Any-light"},
|
| 161 |
-
{"images": ["examples/PP1.jpg"], "prompt": "cinematic polaroid with soft grain subtle vignette gentle lighting white frame handwritten photographed preserving realistic texture and details.", "lora": "Polaroid-Photo"},
|
| 162 |
-
{"images": ["examples/Z1.jpg"], "prompt": "Front-right quarter view.", "lora": "Fal-Multiple-Angles"},
|
| 163 |
-
{"images": ["examples/URP.jpg"], "prompt": "Transform into a cinematic flat log.", "lora": "Cinematic-FlatLog"},
|
| 164 |
-
{"images": ["examples/SL.jpg"], "prompt": "Neutral uniform lighting. Preserve identity and composition.", "lora": "Studio-DeLight"},
|
| 165 |
-
{"images": ["examples/PI.jpg"], "prompt": "Transform it into Pixar-inspired 3D.", "lora": "Pixar-Inspired-3D"},
|
| 166 |
-
{"images": ["examples/MT.jpg"], "prompt": "Paint with manga tone.", "lora": "Manga-Tone"},
|
| 167 |
-
{"images": ["examples/NCB.jpg"], "prompt": "Transform into a noir comic book style.", "lora": "Noir-Comic-Book"},
|
| 168 |
-
{"images": ["examples/URP.jpg"], "prompt": "Ultra-realistic portrait.", "lora": "Ultra-Realistic-Portrait"},
|
| 169 |
-
{"images": ["examples/MN.jpg"], "prompt": "Transform into Midnight Noir Eyes Spotlight.", "lora": "Midnight-Noir-Eyes-Spotlight"},
|
| 170 |
-
{"images": ["examples/ST1.jpg", "examples/ST2.jpg"], "prompt": "Convert Image 1 to the style of Image 2.", "lora": "Style-Transfer"},
|
| 171 |
-
{"images": ["examples/R1.jpg"], "prompt": "Change the picture to realistic photograph.", "lora": "Anything2Real"},
|
| 172 |
-
{"images": ["examples/UA.jpeg"], "prompt": "Unblur and upscale.", "lora": "Unblur-Anything"},
|
| 173 |
-
{"images": ["examples/L1.jpg", "examples/L2.jpg"], "prompt": "Refer to the color tone, remove the original lighting from Image 1, and relight Image 1 based on the lighting and color tone of Image 2.", "lora": "Light-Migration"},
|
| 174 |
-
{"images": ["examples/P1.jpg"], "prompt": "Transform into anime (while preserving the background and remaining elements maintaining realism and original details.)", "lora": "Anime-V2"},
|
| 175 |
-
]
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
def make_thumb_b64(path, max_dim=220):
|
| 179 |
-
if not os.path.exists(path):
|
| 180 |
-
return ""
|
| 181 |
-
try:
|
| 182 |
-
img = Image.open(path).convert("RGB")
|
| 183 |
-
img.thumbnail((max_dim, max_dim), LANCZOS)
|
| 184 |
-
buf = BytesIO()
|
| 185 |
-
img.save(buf, format="JPEG", quality=65)
|
| 186 |
-
return f"data:image/jpeg;base64,{base64.b64encode(buf.getvalue()).decode()}"
|
| 187 |
-
except Exception as e:
|
| 188 |
-
print(f"Thumbnail error for {path}: {e}")
|
| 189 |
-
return ""
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
def encode_full_image(path):
|
| 193 |
-
if not os.path.exists(path):
|
| 194 |
-
return ""
|
| 195 |
-
try:
|
| 196 |
-
with open(path, "rb") as f:
|
| 197 |
-
data = f.read()
|
| 198 |
-
ext = path.rsplit(".", 1)[-1].lower()
|
| 199 |
-
mime = {"jpg": "image/jpeg", "jpeg": "image/jpeg", "png": "image/png", "webp": "image/webp"}.get(ext, "image/jpeg")
|
| 200 |
-
return f"data:{mime};base64,{base64.b64encode(data).decode()}"
|
| 201 |
-
except Exception as e:
|
| 202 |
-
print(f"Encode error for {path}: {e}")
|
| 203 |
-
return ""
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
def build_client_config():
|
| 207 |
-
"""Static config consumed by the frontend: LoRA list + example cards."""
|
| 208 |
-
examples = []
|
| 209 |
-
for i, ex in enumerate(EXAMPLES_CONFIG):
|
| 210 |
-
examples.append({
|
| 211 |
-
"idx": i,
|
| 212 |
-
"thumbs": [make_thumb_b64(p) for p in ex["images"]],
|
| 213 |
-
"n_images": len(ex["images"]),
|
| 214 |
-
"lora": ex["lora"],
|
| 215 |
-
"prompt": ex["prompt"],
|
| 216 |
-
})
|
| 217 |
-
return {
|
| 218 |
-
"loras": ADAPTER_NAMES,
|
| 219 |
-
"default_lora": "Photo-to-Anime",
|
| 220 |
-
"examples": examples,
|
| 221 |
-
}
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
print("Building client config (example thumbnails)…")
|
| 225 |
-
CLIENT_CONFIG = build_client_config()
|
| 226 |
-
print(f"Built config with {len(EXAMPLES_CONFIG)} examples and {len(ADAPTER_NAMES)} LoRAs.")
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
def b64_to_pil_list(b64_json_str):
|
| 230 |
-
if not b64_json_str or b64_json_str.strip() in ("", "[]"):
|
| 231 |
-
return []
|
| 232 |
-
try:
|
| 233 |
-
b64_list = json.loads(b64_json_str)
|
| 234 |
-
except Exception:
|
| 235 |
-
return []
|
| 236 |
-
pil_images = []
|
| 237 |
-
for b64_str in b64_list:
|
| 238 |
-
if not b64_str or not isinstance(b64_str, str):
|
| 239 |
-
continue
|
| 240 |
-
try:
|
| 241 |
-
if b64_str.startswith("data:image"):
|
| 242 |
-
_, data = b64_str.split(",", 1)
|
| 243 |
-
else:
|
| 244 |
-
data = b64_str
|
| 245 |
-
image_data = base64.b64decode(data)
|
| 246 |
-
pil_images.append(Image.open(BytesIO(image_data)).convert("RGB"))
|
| 247 |
-
except Exception as e:
|
| 248 |
-
print(f"Error decoding image: {e}")
|
| 249 |
-
return pil_images
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
def pil_to_b64_png(image: Image.Image) -> str:
|
| 253 |
-
buf = BytesIO()
|
| 254 |
-
image.save(buf, format="PNG")
|
| 255 |
-
return f"data:image/png;base64,{base64.b64encode(buf.getvalue()).decode()}"
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
def update_dimensions_on_upload(image):
|
| 259 |
-
if image is None:
|
| 260 |
-
return 1024, 1024
|
| 261 |
-
w, h = image.size
|
| 262 |
-
if w > h:
|
| 263 |
-
nw = 1024
|
| 264 |
-
nh = int(nw * h / w)
|
| 265 |
-
else:
|
| 266 |
-
nh = 1024
|
| 267 |
-
nw = int(nh * w / h)
|
| 268 |
-
return (nw // 8) * 8, (nh // 8) * 8
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
# ── Gradio Server (Server mode): FastAPI + Gradio queue/API engine ────────────
|
| 272 |
-
app = Server(title="Qwen-Image-Edit-2511-LoRAs-Fast")
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
@app.mcp.tool(name="edit_image")
|
| 276 |
-
@app.api(name="edit_image")
|
| 277 |
-
@spaces.GPU(size="xlarge")
|
| 278 |
-
def infer(
|
| 279 |
-
images_b64_json: str,
|
| 280 |
-
prompt: str,
|
| 281 |
-
lora_adapter: str,
|
| 282 |
-
seed: int,
|
| 283 |
-
randomize_seed: bool,
|
| 284 |
-
guidance_scale: float,
|
| 285 |
-
steps: int,
|
| 286 |
-
) -> dict:
|
| 287 |
-
"""Edit one or more images with Qwen-Image-Edit-2511 + a lazily-loaded LoRA.
|
| 288 |
-
|
| 289 |
-
Returns {"image": <base64 PNG data URL>, "seed": <seed used>}.
|
| 290 |
-
"""
|
| 291 |
-
gc.collect()
|
| 292 |
-
torch.cuda.empty_cache()
|
| 293 |
-
|
| 294 |
-
pil_images = b64_to_pil_list(images_b64_json)
|
| 295 |
-
if not pil_images:
|
| 296 |
-
raise gr.Error("Please upload at least one image to edit.")
|
| 297 |
-
if not prompt or prompt.strip() == "":
|
| 298 |
-
raise gr.Error("Please enter an edit prompt.")
|
| 299 |
-
|
| 300 |
-
spec = ADAPTER_SPECS.get(lora_adapter)
|
| 301 |
-
if not spec:
|
| 302 |
-
raise gr.Error(f"Configuration not found for: {lora_adapter}")
|
| 303 |
-
|
| 304 |
-
adapter_name = spec["adapter_name"]
|
| 305 |
-
if adapter_name not in LOADED_ADAPTERS:
|
| 306 |
-
print(f"--- Downloading and Loading Adapter: {lora_adapter} ---")
|
| 307 |
-
try:
|
| 308 |
-
pipe.load_lora_weights(spec["repo"], weight_name=spec["weights"], adapter_name=adapter_name)
|
| 309 |
-
LOADED_ADAPTERS.add(adapter_name)
|
| 310 |
-
except Exception as e:
|
| 311 |
-
raise gr.Error(f"Failed to load adapter {lora_adapter}: {e}")
|
| 312 |
-
else:
|
| 313 |
-
print(f"--- Adapter {lora_adapter} already loaded. ---")
|
| 314 |
-
|
| 315 |
-
pipe.set_adapters([adapter_name], adapter_weights=[1.0])
|
| 316 |
-
|
| 317 |
-
if randomize_seed:
|
| 318 |
-
seed = random.randint(0, MAX_SEED)
|
| 319 |
-
|
| 320 |
-
generator = torch.Generator(device=device).manual_seed(seed)
|
| 321 |
-
negative_prompt = (
|
| 322 |
-
"worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, "
|
| 323 |
-
"extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry"
|
| 324 |
-
)
|
| 325 |
-
width, height = update_dimensions_on_upload(pil_images[0])
|
| 326 |
-
|
| 327 |
-
try:
|
| 328 |
-
result_image = pipe(
|
| 329 |
-
image=pil_images,
|
| 330 |
-
prompt=prompt,
|
| 331 |
-
negative_prompt=negative_prompt,
|
| 332 |
-
height=height,
|
| 333 |
-
width=width,
|
| 334 |
-
num_inference_steps=steps,
|
| 335 |
-
generator=generator,
|
| 336 |
-
true_cfg_scale=guidance_scale,
|
| 337 |
-
).images[0]
|
| 338 |
-
return {"image": pil_to_b64_png(result_image), "seed": seed}
|
| 339 |
-
except Exception as e:
|
| 340 |
-
raise e
|
| 341 |
-
finally:
|
| 342 |
-
gc.collect()
|
| 343 |
-
torch.cuda.empty_cache()
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
@app.api(name="load_example", queue=False)
|
| 347 |
-
def load_example(idx: float) -> dict:
|
| 348 |
-
"""Return base64-encoded example images + prompt + LoRA for a given example index."""
|
| 349 |
-
try:
|
| 350 |
-
i = int(idx)
|
| 351 |
-
except (ValueError, TypeError):
|
| 352 |
-
i = -1
|
| 353 |
-
if i < 0 or i >= len(EXAMPLES_CONFIG):
|
| 354 |
-
return {"images": [], "prompt": "", "lora": "", "names": [], "status": "error"}
|
| 355 |
-
ex = EXAMPLES_CONFIG[i]
|
| 356 |
-
b64_list, names = [], []
|
| 357 |
-
for path in ex["images"]:
|
| 358 |
-
b64 = encode_full_image(path)
|
| 359 |
-
if b64:
|
| 360 |
-
b64_list.append(b64)
|
| 361 |
-
names.append(os.path.basename(path))
|
| 362 |
-
return {"images": b64_list, "prompt": ex["prompt"], "lora": ex["lora"], "names": names, "status": "ok"}
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
@app.get("/api/config")
|
| 366 |
-
def client_config():
|
| 367 |
-
"""Plain FastAPI route: LoRA choices + example card data for the frontend."""
|
| 368 |
-
return CLIENT_CONFIG
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
@app.get("/", response_class=HTMLResponse)
|
| 372 |
-
async def homepage():
|
| 373 |
-
html_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "index.html")
|
| 374 |
-
with open(html_path, "r", encoding="utf-8") as f:
|
| 375 |
-
return f.read()
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
if __name__ == "__main__":
|
| 379 |
-
app.launch(show_error=True, mcp_server=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|