niofrequency
/

File size: 5,825 Bytes
5092131
 
 
fe666df
 
 
 
b0a9bbb
5092131
 
 
 
08ce7d8
 
 
fe666df
 
 
5092131
 
 
fe666df
 
5092131
08ce7d8
fe666df
 
 
 
 
 
 
 
 
 
52a6787
b0a9bbb
 
 
 
 
 
 
 
 
 
9bb587e
08ce7d8
00d76a8
 
9bb587e
08ce7d8
5092131
 
 
 
 
 
 
 
9bb587e
5092131
 
 
 
 
 
 
 
 
 
 
 
 
 
fe666df
00d76a8
 
5092131
5b667b7
5092131
 
9bb587e
08ce7d8
 
5092131
5b667b7
08ce7d8
5092131
5b667b7
 
5092131
fe666df
 
08ce7d8
fe666df
 
52a6787
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fe666df
 
52a6787
fe666df
 
52a6787
fe666df
52a6787
fe666df
 
 
 
 
52a6787
 
fe666df
 
 
 
 
52a6787
08ce7d8
fe666df
 
 
 
08ce7d8
5eb0e7e
 
fe666df
5092131
36e6e96
fe666df
 
52a6787
 
 
 
 
00d76a8
fe666df
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import torch
import base64
import io
import math
import gc
from PIL import Image, ImageOps
from diffusers import StableDiffusionXLPipeline, StableDiffusionXLInpaintPipeline, DPMSolverMultistepScheduler
from transformers import CLIPVisionModelWithProjection, CLIPImageProcessor
import os

class EndpointHandler():
    def __init__(self, path=""):
        print("Loading ARX Elite Engine...")
        model_path = os.path.join(path, "biglust.safetensors")
        
        # 1. Load Base Model first to prevent the 4-vs-9 channel crash
        print("Loading Base Model...")
        base_pipe = StableDiffusionXLPipeline.from_single_file(
            model_path,
            torch_dtype=torch.float16,
            use_safetensors=True,
            safety_checker=None,
            low_cpu_mem_usage=True
        )
        
        # 2. Dynamically convert to Inpainting Architecture
        print("Converting Base Model to Inpainting Architecture...")
        components = base_pipe.components
        self.pipe = StableDiffusionXLInpaintPipeline(**components)
        
        # Flush RAM to prevent Out-Of-Memory crashes
        del base_pipe
        gc.collect()
        torch.cuda.empty_cache()
        
        # 3. Load the CLIP Vision Encoder (The AI's Eyes)
        print("Loading CLIP Vision Encoder...")
        self.pipe.image_encoder = CLIPVisionModelWithProjection.from_pretrained(
            "h94/IP-Adapter",
            subfolder="models/image_encoder",
            torch_dtype=torch.float16,
            low_cpu_mem_usage=True
        ).to("cuda")
        self.pipe.feature_extractor = CLIPImageProcessor()
        
        # 4. Load standard IP-Adapter for Subject Lock
        print("Loading IP-Adapter weights...")
        self.pipe.load_ip_adapter(
            "h94/IP-Adapter", 
            subfolder="sdxl_models", 
            weight_name="ip-adapter-plus_sdxl_vit-h.safetensors"
        )

        self.pipe.scheduler = DPMSolverMultistepScheduler.from_config(
            self.pipe.scheduler.config, 
            use_karras_sigmas=True,
            algorithm_type="sde-dpmsolver++"
        )
        
        self.pipe.to("cuda")
        print("ARX Inpainting + IP-Adapter Ready.")

    def decode_base64_image(self, image_string):
        if "," in image_string:
            image_string = image_string.split(",")[1]
        image_bytes = base64.b64decode(image_string)
        return Image.open(io.BytesIO(image_bytes)).convert("RGB")

    def encode_image_base64(self, image):
        buffered = io.BytesIO()
        image.save(buffered, format="PNG")
        return base64.b64encode(buffered.getvalue()).decode('utf-8')

    def __call__(self, data):
        inputs = data.pop("inputs", data)
        
        prompt = inputs.get("prompt", "masterpiece, best quality")
        negative_prompt = inputs.get("negative_prompt", "blurry, lowres, bad anatomy, worst quality, ugly")
        
        strength = float(inputs.get("strength", 0.85))
        guidance_scale = float(inputs.get("guidance_scale", 7.5))
        num_inference_steps = int(inputs.get("steps", 30)) 
        
        ip_scale = float(inputs.get("ip_scale", 0.6))
        
        init_image_b64 = inputs.get("init_image")
        mask_image_b64 = inputs.get("mask_image")
        reference_image_b64 = inputs.get("reference_image")

        if not init_image_b64 or not mask_image_b64:
            return {"error": "Missing init_image or mask_image."}

        raw_init = self.decode_base64_image(init_image_b64)
        raw_mask = self.decode_base64_image(mask_image_b64).convert("L")
        
        orig_w, orig_h = raw_init.size

        # 1. SMART SCALING: Keep aspect ratio perfectly intact while fitting SDXL's limits
        max_size = 1024
        if orig_w > orig_h:
            new_w = max_size
            new_h = int(max_size * (orig_h / orig_w))
        else:
            new_h = max_size
            new_w = int(max_size * (orig_w / orig_h))
            
        scaled_init = raw_init.resize((new_w, new_h), Image.LANCZOS)
        scaled_mask = raw_mask.resize((new_w, new_h), Image.LANCZOS)

        # 2. THE INVISIBLE PAD: Nearest multiple of 64
        pad_w = math.ceil(new_w / 64) * 64
        pad_h = math.ceil(new_h / 64) * 64
        
        init_padded = Image.new("RGB", (pad_w, pad_h), (0, 0, 0))
        init_padded.paste(scaled_init, (0, 0))
        
        mask_padded = Image.new("L", (pad_w, pad_h), 0)
        mask_padded.paste(scaled_mask, (0, 0))

        # 3. EXPLICIT DIMENSIONS: This stops the AI from squashing your photo!
        kwargs = {
            "prompt": prompt,
            "negative_prompt": negative_prompt,
            "image": init_padded,
            "mask_image": mask_padded,
            "width": pad_w,  
            "height": pad_h, 
            "strength": strength,
            "guidance_scale": guidance_scale,
            "num_inference_steps": num_inference_steps
        }

        # 4. IP-ADAPTER LOGIC
        if reference_image_b64:
            raw_ref = self.decode_base64_image(reference_image_b64)
            ip_image = ImageOps.fit(raw_ref, (224, 224), method=Image.LANCZOS)
            kwargs["ip_adapter_image"] = ip_image
            self.pipe.set_ip_adapter_scale(ip_scale)
        else:
            blank_image = Image.new("RGB", (224, 224), (0, 0, 0))
            kwargs["ip_adapter_image"] = blank_image
            self.pipe.set_ip_adapter_scale(0.0)

        # Generate!
        result_padded = self.pipe(**kwargs).images[0]
        
        # 5. SLICE OFF THE PADDING
        cropped_result = result_padded.crop((0, 0, new_w, new_h))
        
        # 6. RESTORE EXACT ORIGINAL DIMENSIONS FOR THE UI
        final_result = cropped_result.resize((orig_w, orig_h), Image.LANCZOS)

        return {"image": self.encode_image_base64(final_result)}