foooxus.bat
54 |
55 | Or if you prefer
56 | ```
57 | # Check you are in your (venv) virtual environment
58 | # if not, activate it
59 | venv\Scripts\activate
60 |
61 | # Run the FoooXus app
62 | python foooxus.py
63 | ```
64 |
65 | Terminal shows now versions and much more informations to understand what happens
66 | 
67 |
68 | Open your browser on http://127.0.0.1:7878 to view FoooXus UI
69 |
70 | ## How to update FoooXus
71 | To update FoooXus from this github repository, open a terminal in your FoooXus-Fooocus-Extender
72 | ```
73 | git pull
74 | ```
75 |
76 | FoooXus can now force Performance Settings when those models are used. New button to regenerate Models illustrations are displayed
77 | 
78 |
79 |
80 | ## Updates on Fooocus
81 | - Fooocus could be updated at every moment. It could broke the API.
82 | - FoooXus version is tested with V2.4.1 Fooocus Release.
83 |
84 | ## History Log
85 | V1.0.0 Update to V2.5.5 Fooocus Release
86 |
87 | V0.9.7 Update to V2.4.1 Fooocus Release
88 |
89 | V0.9.6 New optional loras-directory parameter on config.json
90 | To fix issue https://github.com/toutjavascript/FoooXus-Fooocus-Extender/issues/7 where Loras are not found,
91 | you can add a new line in config.json. For example:
92 | "loras-directory": "D:\\datas\\StabilityMatrix\\Data\\Models\\Lora",
93 |
94 | V0.9.5 Force optimized Performance Settings when Turbo or Lightning models are used
95 |
96 | V0.9.1 Adapt to Gradio API to Fooocus 2.3.0
97 | Auto clean /outputs/tmp/ folder at launch
98 |
99 | V0.9.0 Adapt to Gradio API to Fooocus 2.2.1
100 |
101 | V0.8.3 Fix issue on macOS https://github.com/toutjavascript/FoooXus-Fooocus-Extender/issues/5
102 |
103 | V0.8.2 Add a button to clear queue
104 |
105 | V0.8.1 Improve the error messages
106 |
107 | V0.8 New standalone fooocus.exe
108 | - new config.json options
109 | - download the fooocus.exe executable file to avoid tedious python, git, pip installations
110 |
111 | V0.6 Updates on install process
112 | - If you have issues, please tell me, with a copy of a terminal
113 |
114 | V0.5 Very First release
115 | - Please, tell me if it is broken somewhere
116 | - Please, share your ideas by opening new discussions
117 |
118 | ## Some functionnalities
119 | Get notified when new image is generated
120 | 
121 |
122 | View history
123 | 
124 |
125 | Python terminal shows all generations
126 | 
127 |
128 | ## Tested and validated on Windows 10, 11 and macOS
129 | FoooXus should work on Linux too
130 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==3.0.2
2 | GPUtil==1.4.0
3 | gradio==3.41.2
4 | gradio_client==0.5.0
5 | Pillow==10.2.0
6 | psutil==5.9.5
7 | py_cpuinfo==9.0.0
8 |
--------------------------------------------------------------------------------
/src/api.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 | import traceback
4 | import timeit
5 | from datetime import datetime
6 | import sys
7 | from src import utils
8 | from src import console
9 | from gradio_client import Client
10 |
11 |
12 | class ConfigApi:
13 | def __init__(self):
14 | self.ping=29
15 | self.cancel=0
16 | self.init=51
17 | self.generate=67
18 | self.models=46
19 | self.styles=64
20 | self.checkDelta=False
21 | self.delta=0
22 | self.deltaPing=0
23 | self.deltaCancel=0
24 | self.deltaInit=0
25 | self.deltaGenerate=0
26 | self.deltaModels=0
27 | self.deltaStyles=0
28 |
29 |
30 | class FooocusApi:
31 | def __init__(self, base_url, outputFolder, FOOOCUS_MIN_RELEASE):
32 | self.FOOOCUS_MIN_RELEASE=FOOOCUS_MIN_RELEASE
33 | self.config=ConfigApi()
34 | self.client=None
35 | self.outputFolder=outputFolder
36 | if base_url[0:7] != "http://"[0:7]:
37 | base_url="http://"+base_url
38 | self.base_url = base_url.strip()
39 | self.emptyImage="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=="
40 |
41 | def getClient(self):
42 | if (self.client==None):
43 | # print("Open API client to Fooocus instance at "+self.base_url+" ...")
44 | try:
45 | self.client = Client(self.base_url, serialize=False)
46 | console.printBB("[ok]Great! FoooXus is now connected to Fooocus[/ok]")
47 | return self.client
48 | except Exception as e:
49 | self.client=None
50 | console.printBB("[error]Connection to Fooocus failed...[/error]")
51 | exceptionName=type(e).__name__
52 | if exceptionName=="ConnectionError":
53 | console.printBB("[error] ConnectionError, check that:[/error]")
54 | console.printBB("[error] - Fooocus must be running, on the same device[/error]")
55 | console.printBB("[error] - Fooocus address config.json is set to "+self.base_url+" [/error]")
56 | else:
57 | console.printBB("[error] "+exceptionName+"[/error]")
58 |
59 | print(f"{e}")
60 | print("")
61 | return None
62 | else:
63 | return self.client
64 |
65 |
66 | # Ping Fooocus instance
67 | def pingFooocus(self, firstCall=False):
68 | try:
69 | client=self.getClient()
70 | if(client is None):
71 | # Not connected, so no call to predict
72 | return {"ajax":True, "error":True}
73 |
74 | result = client.predict( fn_index=self.config.ping+self.config.deltaPing )
75 | if firstCall:
76 | console.printBB("[ok]FoooXus is connected to Fooocus. Let's play in the web UI ![/ok]")
77 | return {"ajax":True, "error":False, "ping":True, "fooocusUrl": self.base_url}
78 | except Exception as e:
79 | if self.config.deltaPing<=10:
80 | console.printBB("[warning]pingFooocus() failed. Trying to find the right gradio API fn_index ("+str(self.config.ping)+"+"+str(self.config.deltaPing)+")[/warning]")
81 | self.config.deltaPing+=1
82 | #self.config.deltaInit+=1
83 | #self.config.deltaGenerate+=1
84 | #self.config.deltaCancel+=1
85 | #self.config.deltaStyles+=1
86 | self.config.deltaModels+=1
87 | return self.pingFooocus(firstCall)
88 | else:
89 | console.printExceptionError(e)
90 | console.printBB("[error]FoooXus is not connected to Fooocus gradio API :([/error]")
91 | console.printBB("[error] - Fooocus must be running, on the same device[/error]")
92 | console.printBB("[error] - Fooocus must be at least "+self.FOOOCUS_MIN_RELEASE+"[/error]")
93 | console.printBB("[error] - If FoooXus app has already worked, Fooocus may have changed version.[/error]")
94 | console.printBB("[error] it may break API calls[/error]")
95 | console.printBB("")
96 | return {"ajax":True, "error":True}
97 |
98 | # Cancel generation
99 | def sendCancel(self):
100 | try:
101 | console.printBB("[b] /!\ You have canceled the queue[/b]")
102 | console.printBB("[b] [/b]")
103 | result = self.getClient().predict( fn_index=self.config.cancel )
104 | return {"ajax":True, "error":False}
105 | except Exception as e:
106 | console.printExceptionError(e)
107 | return {"ajax":True, "error":True}
108 |
109 |
110 | # Get list of all models installed on the Fooocus folder
111 | def getModels(self):
112 | try:
113 | result = self.getClient().predict( fn_index=self.config.models+self.config.deltaModels )
114 | models=[]
115 | if (result[0]):
116 | if (result[0]["choices"]):
117 | for model in result[0]["choices"]:
118 | models.append(model)
119 | return {"ajax":True, "error":False, "models":models}
120 | except Exception as e:
121 | if self.config.deltaModels<10:
122 | console.printBB("[warning]getModels() failed. Trying to find the right gradio API fn_index (i+"+str(self.config.deltaModels)+")[/warning]")
123 | self.config.deltaModels+=1
124 | self.config.deltaInit+=1
125 | self.config.deltaGenerate+=1
126 | return self.getModels()
127 | else:
128 | print("[error] getModels exception[/error]")
129 | print(f"Error: {e}")
130 | return {"ajax":True, "error":True}
131 |
132 |
133 | # Get all styles available for Fooocus
134 | def getStyles(self):
135 | try:
136 | result = self.getClient().predict( [], fn_index=self.config.styles+self.config.deltaStyles )
137 | styles=[]
138 | if ("choices" in result):
139 | for style in result["choices"]:
140 | if style[0]!="Random Style":
141 | styles.append(style[0])
142 | return {"ajax":True, "error":False, "styles":styles}
143 | except:
144 | if self.config.deltaStyles<10:
145 | console.printBB("[warning]getStyles() failed. Trying to find the right gradio API fn_index (i+"+str(self.config.deltaStyles)+")[/warning]")
146 | self.config.deltaStyles+=1
147 | self.config.deltaModels+=1
148 | self.config.deltaInit+=1
149 | self.config.deltaGenerate+=1
150 | return self.getStyles()
151 | else:
152 | return {"ajax":True, "error":True}
153 |
154 | # Get all Loras from the loras-directory
155 | def getLoras(self, dir):
156 | try:
157 | loras=utils.getFiles(dir, ".safetensors")
158 | return {"ajax":True, "error":False, "loras":loras}
159 | except:
160 | return {"ajax":True, "error":True}
161 |
162 |
163 | # InitFooocus does not exist since Fooocus V2.2.1. All parameters in CreateImage
164 | def initFooocus(self, metadata={}):
165 |
166 | return False
167 |
168 | try:
169 | adm=metadata.get("ADM Guidance").replace("(","").replace(")","")
170 | admSplit=adm.split(", ")
171 | result = self.getClient().predict(
172 | True, # bool in 'Disable Preview' Checkbox component
173 | float(admSplit[0]), # int | float (numeric value between 0.1 and 3.0) in 'Positive ADM Guidance Scaler' Slider component
174 | float(admSplit[1]), # int | float (numeric value between 0.1 and 3.0) in 'Negative ADM Guidance Scaler' Slider component
175 | float(admSplit[2]), # int | float (numeric value between 0.0 and 1.0) in 'ADM Guidance End At Step' Slider component
176 | 7, # int | float (numeric value between 1.0 and 30.0) in 'CFG Mimicking from TSNR' Slider component
177 | metadata["Sampler"], # str (Option from: ['euler', 'euler_ancestral', 'heun', 'heunpp2', 'dpm_2', 'dpm_2_ancestral', 'lms', 'dpm_fast', 'dpm_adaptive', 'dpmpp_2s_ancestral', 'dpmpp_sde', 'dpmpp_sde_gpu', 'dpmpp_2m', 'dpmpp_2m_sde', 'dpmpp_2m_sde_gpu', 'dpmpp_3m_sde', 'dpmpp_3m_sde_gpu', 'ddpm', 'lcm', 'ddim', 'uni_pc', 'uni_pc_bh2']) in 'Sampler' Dropdown component
178 | metadata["Scheduler"], # str (Option from: ['normal', 'karras', 'exponential', 'sgm_uniform', 'simple', 'ddim_uniform', 'lcm', 'turbo']) in 'Scheduler' Dropdown component
179 | False, # bool in 'Generate Image Grid for Each Batch' Checkbox component
180 | -1, # int | float (numeric value between -1 and 200) in 'Forced Overwrite of Sampling Step' Slider component
181 | -1, # int | float (numeric value between -1 and 200) in 'Forced Overwrite of Refiner Switch Step' Slider component
182 | -1, # int | float (numeric value between -1 and 2048) in 'Forced Overwrite of Generating Width' Slider component
183 | -1, # int | float (numeric value between -1 and 2048) in 'Forced Overwrite of Generating Height' Slider component
184 | -1, # int | float (numeric value between -1 and 1.0) in 'Forced Overwrite of Denoising Strength of "Vary"' Slider component
185 | -1, # int | float (numeric value between -1 and 1.0) in 'Forced Overwrite of Denoising Strength of "Upscale"' Slider component
186 | False, # bool in 'Mixing Image Prompt and Vary/Upscale' Checkbox component
187 | False, # bool in 'Mixing Image Prompt and Inpaint' Checkbox component
188 | False, # bool in 'Debug Preprocessors' Checkbox component
189 | False, # bool in 'Skip Preprocessors' Checkbox component
190 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Softness of ControlNet' Slider component
191 | 1, # int | float (numeric value between 1 and 255) in 'Canny Low Threshold' Slider component
192 | 1, # int | float (numeric value between 1 and 255) in 'Canny High Threshold' Slider component
193 | "joint", # str (Option from: ['joint', 'separate', 'vae']) in 'Refiner swap method' Dropdown component
194 | False, # bool in 'Enabled' Checkbox component
195 | 0, # int | float (numeric value between 0 and 2) in 'B1' Slider component
196 | 0, # int | float (numeric value between 0 and 2) in 'B2' Slider component
197 | 0, # int | float (numeric value between 0 and 4) in 'S1' Slider component
198 | 0, # int | float (numeric value between 0 and 4) in 'S2' Slider component
199 | False, # bool in 'Debug Inpaint Preprocessing' Checkbox component
200 | False, # bool in 'Disable initial latent in inpaint' Checkbox component
201 | "None", # str (Option from: ['None', 'v1', 'v2.5', 'v2.6']) in 'Inpaint Engine' Dropdown component
202 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Denoising Strength' Slider component
203 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Respective Field' Slider component
204 | False, # bool in 'Enable Mask Upload' Checkbox component
205 | False, # bool in 'Invert Mask' Checkbox component
206 | -64, # int | float (numeric value between -64 and 64) in 'Mask Erode or Dilate' Slider component
207 | fn_index=self.config.init+self.config.deltaInit
208 | )
209 | return {"ajax":True, "error":False, "init":True}
210 | except Exception as e:
211 | if self.config.deltaInit<10:
212 | console.printBB("[warning]initFooocus() failed. Trying to find the right gradio API fn_index (i+"+str(self.config.deltaInit)+")[/warning]")
213 | self.config.deltaInit+=1
214 | self.config.deltaGenerate+=1
215 | return self.initFooocus(metadata)
216 | else:
217 | print("getModels exception")
218 | print(f"Error: {e}")
219 | return {"ajax":True, "error":True}
220 |
221 |
222 |
223 | def sendCreateImage(self, metadata, uid):
224 | try:
225 | adm=metadata.get("ADM Guidance").replace("(","").replace(")","")
226 | admSplit=adm.split(", ")
227 | start_time = timeit.default_timer()
228 | # self.initFooocus(metadata) # not present in Fooocus V2.2.1
229 |
230 | # Check Turbo or Lighting models
231 |
232 |
233 |
234 | now = datetime.now()
235 | console.printBB("[hour]"+now.strftime("%H:%M:%S")+"[/hour] [b]#"+uid+"[/b] New generation asked and sent to Fooocus")
236 | console.printBB(" [fade]Prompt:[/fade] "+metadata["Prompt"])
237 | console.printBB(" [fade]Model:[/fade] "+metadata["Base Model"])
238 | performance=metadata["Performance"]
239 | if "lightning" in metadata["Base Model"].lower():
240 | console.printBB(" [fade]Lightning model:[/fade] "+"Force to Lightning Performance")
241 | performance="Lightning"
242 |
243 | if "turbo" in metadata["Base Model"].lower():
244 | console.printBB(" [fade]Turbo model:[/fade] "+"Force to Extreme Speed Performance")
245 | performance="Extreme Speed"
246 | console.printBB(" [fade]Styles:[/fade] "+"," .join(str(s) for s in metadata["Styles"]))
247 |
248 |
249 | result = self.getClient().predict(
250 | False, # bool in 'Generate Image Grid for Each Batch' Checkbox component
251 | metadata["Prompt"], # str in 'parameter_10' Textbox component
252 | metadata["Negative Prompt"],# str in 'Negative Prompt' Textbox component
253 | metadata["Styles"], # List[str] in 'Selected Styles' Checkboxgroup component
254 | performance, # str in 'Performance' Radio component
255 | '1024×1024', # str in 'Aspect Ratios' Radio component
256 | 1, # int | float (numeric value between 1 and 64) in 'Image Number' Slider component
257 | "png", # str in 'Output Format' Radio component
258 | metadata["Seed"], # str in 'Seed' Textbox component
259 | True, # bool in 'Read wildcards in order' Checkbox component
260 | metadata["Sharpness"], # int | float (numeric value between 0.0 and 30.0) in 'Image Sharpness' Slider component
261 | metadata["Guidance Scale"], # int | float (numeric value between 1.0 and 30.0) in 'Guidance Scale' Slider component
262 | metadata["Base Model"], # str in 'Base Model (SDXL only)' Dropdown component
263 | metadata["Refiner Model"], # str in 'Refiner (SDXL or SD 1.5)' Dropdown component
264 | metadata["Refiner Switch"], # int | float (numeric value between 0.1 and 1.0) in 'Refiner Switch At' Slider component
265 | True, # bool in 'Enable' Checkbox component
266 | metadata["Lora 1"], # str in 'LoRA 1' Dropdown component
267 | metadata["Weight Lora 1"], # int | float (numeric value between -2 and 2) in 'Weight' Slider component
268 | True, # bool in 'Enable' Checkbox component
269 | metadata["Lora 2"], # str in 'LoRA 2' Dropdown component
270 | metadata["Weight Lora 2"], # int | float (numeric value between -2 and 2) in 'Weight' Slider component
271 | True, # bool in 'Enable' Checkbox component
272 | metadata["Lora 3"], # str in 'LoRA 3' Dropdown component
273 | metadata["Weight Lora 3"], # int | float (numeric value between -2 and 2) in 'Weight' Slider component
274 | True, # bool in 'Enable' Checkbox component
275 | metadata["Lora 4"], # str in 'LoRA 4' Dropdown component
276 | metadata["Weight Lora 4"], # int | float (numeric value between -2 and 2) in 'Weight' Slider component
277 | True, # bool in 'Enable' Checkbox component
278 | metadata["Lora 5"], # str in 'LoRA 5' Dropdown component
279 | metadata["Weight Lora 5"], # int | float (numeric value between -2 and 2) in 'Weight' Slider component
280 | True, # bool in 'Input Image' Checkbox component
281 | "Howdy!", # str in 'parameter_91' Textbox component
282 | "Disabled", # str in 'Upscale or Variation:' Radio component
283 | self.emptyImage, # str (filepath or URL to image) in 'Drag above image to here' Image component
284 | ["Left"], # List[str] in 'Outpaint Direction' Checkboxgroup component
285 | self.emptyImage, # str (filepath or URL to image) in 'Drag inpaint or outpaint image to here' Image component
286 | "Howdy!", # str in 'Inpaint Additional Prompt' Textbox component
287 | self.emptyImage, # str (filepath or URL to image) in 'Mask Upload' Image component
288 | True, # bool in 'Disable Preview' Checkbox component
289 | True, # bool in 'Disable Intermediate Results' Checkbox component
290 | True, # bool in 'Disable seed increment' Checkbox component
291 | False, # bool in 'Black Out NSFW' Checkbox component
292 | float(admSplit[0]), # int | float (numeric value between 0.1 and 3.0) in 'Positive ADM Guidance Scaler' Slider component
293 | float(admSplit[1]), # int | float (numeric value between 0.1 and 3.0) in 'Negative ADM Guidance Scaler' Slider component
294 | float(admSplit[2]), # int | float (numeric value between 0.0 and 1.0) in 'ADM Guidance End At Step' Slider component
295 | 7, # int | float (numeric value between 1.0 and 30.0) in 'CFG Mimicking from TSNR' Slider component
296 | 1, # int | float (numeric value between 1 and 12) 'CLIP Skip' Slider component
297 | metadata["Sampler"], # str (Option from: ['euler', 'euler_ancestral', 'heun', 'heunpp2', 'dpm_2', 'dpm_2_ancestral', 'lms', 'dpm_fast', 'dpm_adaptive', 'dpmpp_2s_ancestral', 'dpmpp_sde', 'dpmpp_sde_gpu', 'dpmpp_2m', 'dpmpp_2m_sde', 'dpmpp_2m_sde_gpu', 'dpmpp_3m_sde', 'dpmpp_3m_sde_gpu', 'ddpm', 'lcm', 'ddim', 'uni_pc', 'uni_pc_bh2']) in 'Sampler' Dropdown component
298 | metadata["Scheduler"], # str (Option from: ['normal', 'karras', 'exponential', 'sgm_uniform', 'simple', 'ddim_uniform', 'lcm', 'turbo']) in 'Scheduler' Dropdown component
299 | "Default (model)", # str (Option from: ['Default (model)']) 'VAE' Dropdown component
300 | -1, # int | float (numeric value between -1 and 200) in 'Forced Overwrite of Sampling Step' Slider component
301 | -1, # int | float (numeric value between -1 and 200) in 'Forced Overwrite of Refiner Switch Step' Slider component
302 | -1, # int | float (numeric value between -1 and 2048) in 'Forced Overwrite of Generating Width' Slider component
303 | -1, # int | float (numeric value between -1 and 2048) in 'Forced Overwrite of Generating Height' Slider component
304 | -1, # int | float (numeric value between -1 and 1.0) in 'Forced Overwrite of Denoising Strength of "Vary"' Slider component
305 | -1, # int | float (numeric value between -1 and 1.0) in 'Forced Overwrite of Denoising Strength of "Upscale"' Slider component
306 | False, # bool in 'Mixing Image Prompt and Vary/Upscale' Checkbox component
307 | False, # bool in 'Mixing Image Prompt and Inpaint' Checkbox component
308 | False, # bool in 'Debug Preprocessors' Checkbox component
309 | False, # bool in 'Skip Preprocessors' Checkbox component
310 | 1, # int | float (numeric value between 1 and 255) in 'Canny Low Threshold' Slider component
311 | 1, # int | float (numeric value between 1 and 255) in 'Canny High Threshold' Slider component
312 | "joint", # str (Option from: ['joint', 'separate', 'vae']) in 'Refiner swap method' Dropdown component
313 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Softness of ControlNet' Slider component
314 | False, # bool in 'Enabled' Checkbox component
315 | 0, # int | float (numeric value between 0 and 2) in 'B1' Slider component
316 | 0, # int | float (numeric value between 0 and 2) in 'B2' Slider component
317 | 0, # int | float (numeric value between 0 and 4) in 'S1' Slider component
318 | 0, # int | float (numeric value between 0 and 4) in 'S2' Slider component
319 | False, # bool in 'Debug Inpaint Preprocessing' Checkbox component
320 | False, # bool in 'Disable initial latent in inpaint' Checkbox component
321 | "None", # str (Option from: ['None', 'v1', 'v2.5', 'v2.6']) in 'Inpaint Engine' Dropdown component
322 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Denoising Strength' Slider component
323 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Respective Field' Slider component
324 | False, # bool in 'Enable Mask Upload' Checkbox component
325 | False, # bool in 'Invert Mask' Checkbox component
326 | -64, # int | float (numeric value between -64 and 64) in 'Mask Erode or Dilate' Slider component
327 | False, # bool in 'Save only final enhanced image' Checkbox component
328 | False, # bool in 'Save Metadata to Images' Checkbox component
329 | "fooocus", # str in 'Metadata Scheme' Radio component
330 | self.emptyImage, # str (filepath or URL to image) in 'Image' Image component
331 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component
332 | 0, # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component
333 | "ImagePrompt", # str in 'Type' Radio component
334 | self.emptyImage, # str (filepath or URL to image) in 'Image' Image component
335 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component
336 | 0, # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component
337 | "ImagePrompt", # str in 'Type' Radio component
338 | self.emptyImage, # str (filepath or URL to image) in 'Image' Image component
339 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component
340 | 0, # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component
341 | "ImagePrompt", # str in 'Type' Radio component
342 | self.emptyImage, # str (filepath or URL to image) in 'Image' Image component
343 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component
344 | 0, # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component
345 | "ImagePrompt", # str in 'Type' Radio component
346 | False, # bool in 'Debug GroundingDINO' Checkbox component
347 | -64, # int | float (numeric value between -64 and 64) in 'GroundingDINO Box Erode or Dilate' Slider component
348 | False, # bool in 'Debug Enhance Masks' Checkbox component
349 | self.emptyImage, # str (filepath or URL to image)
350 | False, # bool in 'Enhance' Checkbox component
351 | "Disabled", # str in 'Upscale or Variation:' Radio component
352 | "Before First Enhancement", # str in 'Order of Processing' Radio component
353 | "Original Prompts", # str in 'Prompt' Radio component
354 | False, # bool in 'Enable' Checkbox component
355 | "Howdy!", # str in 'Detection prompt' Textbox component
356 | "Howdy!", # str in 'Enhancement positive prompt' Textbox component
357 | "Howdy!", # str in 'Enhancement negative prompt' Textbox component
358 | "u2net", # str (Option from: ['u2net', 'u2netp', 'u2net_human_seg', 'u2net_cloth_seg', 'silueta', 'isnet-general-use', 'isnet-anime', 'sam'])
359 | "full", # str (Option from: ['full', 'upper', 'lower']) in 'Cloth category' Dropdown component
360 | "vit_b", # str (Option from: ['vit_b', 'vit_l', 'vit_h']) in 'SAM model' Dropdown component
361 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Text Threshold' Slider component
362 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Box Threshold' Slider component
363 | 0, # int | float (numeric value between 0 and 10) in 'Maximum number of detections' Slider component
364 | False, # bool in 'Disable initial latent in inpaint' Checkbox component
365 | "None", # str (Option from: ['None', 'v1', 'v2.5', 'v2.6']) in 'Inpaint Engine' Dropdown component
366 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Denoising Strength' Slider component
367 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Respective Field' Slider component
368 | -64, # int | float (numeric value between -64 and 64) in 'Mask Erode or Dilate' Slider component
369 | False, # bool in 'Invert Mask' Checkbox component
370 | False, # bool in 'Enable' Checkbox component
371 | "Howdy!", # str in 'Detection prompt' Textbox component
372 | "Howdy!", # str in 'Enhancement positive prompt' Textbox component
373 | "Howdy!", # str in 'Enhancement negative prompt' Textbox component
374 | "u2net", # str (Option from: ['u2net', 'u2netp', 'u2net_human_seg', 'u2net_cloth_seg', 'silueta', 'isnet-general-use', 'isnet-anime', 'sam']) in 'Mask generation model' Dropdown component
375 | "full", # str (Option from: ['full', 'upper', 'lower']) in 'Cloth category' Dropdown component
376 | "vit_b", # str (Option from: ['vit_b', 'vit_l', 'vit_h']) in 'SAM model' Dropdown component
377 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Text Threshold' Slider component
378 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Box Threshold' Slider component
379 | 0, # int | float (numeric value between 0 and 10) in 'Maximum number of detections' Slider component
380 | False, # bool in 'Disable initial latent in inpaint' Checkbox component
381 | "None", # str (Option from: ['None', 'v1', 'v2.5', 'v2.6']) in 'Inpaint Engine' Dropdown component
382 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Denoising Strength' Slider component
383 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Respective Field' Slider component
384 | -64, # int | float (numeric value between -64 and 64) in 'Mask Erode or Dilate' Slider component
385 | False, # bool in 'Invert Mask' Checkbox component
386 | False, # bool in 'Enable' Checkbox component
387 | "Howdy!", # str in 'Detection prompt' Textbox component
388 | "Howdy!", # str in 'Enhancement positive prompt' Textbox component
389 | "Howdy!", # str in 'Enhancement negative prompt' Textbox component
390 | "u2net", # str (Option from: ['u2net', 'u2netp', 'u2net_human_seg', 'u2net_cloth_seg', 'silueta', 'isnet-general-use', 'isnet-anime', 'sam'])
391 | "full", # str (Option from: ['full', 'upper', 'lower']) in 'Cloth category' Dropdown component
392 | "vit_b", # str (Option from: ['vit_b', 'vit_l', 'vit_h']) in 'SAM model' Dropdown component
393 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Text Threshold' Slider component
394 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Box Threshold' Slider component
395 | 0, # int | float (numeric value between 0 and 10) in 'Maximum number of detections' Slider component
396 | False, # bool in 'Disable initial latent in inpaint' Checkbox component
397 | "None", # str (Option from: ['None', 'v1', 'v2.5', 'v2.6']) in 'Inpaint Engine' Dropdown component
398 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Denoising Strength' Slider component
399 | 0, # int | float (numeric value between 0.0 and 1.0) in 'Inpaint Respective Field' Slider component
400 | -64, # int | float (numeric value between -64 and 64) in 'Mask Erode or Dilate' Slider component
401 | False, # bool in 'Invert Mask' Checkbox component
402 | fn_index=self.config.generate+self.config.deltaGenerate
403 | )
404 |
405 | result = self.getClient().predict(fn_index=self.config.generate+1)
406 |
407 | end_time = timeit.default_timer()
408 |
409 |
410 |
411 | # Generation failure
412 | if len(result[3]['value'])==0:
413 | console.printBB(" [b]#"+uid+"[/b] Generation image [error]failed[/error]")
414 | return {
415 | "ajax":True,
416 | "error":True,
417 | "image":False,
418 | "uid": uid,
419 | "metadata": metadata,
420 | "start_time": start_time,
421 | "end_time": end_time,
422 | "elapsed_time": round((end_time - start_time)*1000),
423 | "temp": "",
424 | "name": "",
425 | "file_size": "",
426 | "result": result
427 | }
428 |
429 | # Generation success
430 | if "name" in result[3]['value'][0]:
431 | now = datetime.now()
432 | console.printBB("[hour]"+now.strftime("%H:%M:%S")+"[/hour] [b]#"+uid+"[/b] [ok]Image is generated[/ok]")
433 |
434 | picture=result[3]['value'][0]['name']
435 | name=self.outputFolder+"/"+uid+result[3]['value'][0]['name'][-4:]
436 | shutil.copyfile(picture, name)
437 |
438 | if "action" in metadata:
439 | if "compress" in metadata["action"]:
440 | if "resize" in metadata["action"]:
441 | if "copy" in metadata["action"]:
442 | w, h=metadata["action"]["resize"]
443 | # dirname = os.path.dirname(sys.argv[0])
444 | # filename = utils.pathJoin(dirname, metadata["action"]["copy"])
445 | # print("Remove file ? "+filename)
446 | # if os.path.exists(filename):
447 | # print("REMOVE FILE: "+filename)
448 | # os.remove(filename)
449 | utils.resizeAndCompressImage(picture, w, h, metadata["action"]["compress"], metadata["action"]["copy"])
450 |
451 | image={ "ajax": True,
452 | "error": False,
453 | "image": True,
454 | "uid": uid,
455 | "metadata": metadata,
456 | "start_time": start_time,
457 | "end_time": end_time,
458 | "elapsed_time": round((end_time - start_time)*1000),
459 | "temp": result[3]['value'][0]['name'],
460 | "name": "/"+name,
461 | "file_size": os.path.getsize(picture),
462 | "result": result
463 | }
464 |
465 |
466 |
467 | return image
468 |
469 | except Exception as e:
470 | if self.config.deltaGenerate<0:
471 | console.printBB("[warning]sendCreateImage() failed. Trying to find the right gradio API fn_index (i+"+str(self.config.deltaGenerate)+")[/warning]")
472 | self.config.deltaGenerate+=1
473 | return self.sendCreateImage(metadata, uid)
474 | else:
475 | print("sendCreateImage exception")
476 | print(f"Error: {e}")
477 | print(" Check the Fooocus console terminal to view more indications")
478 | traceback.print_exc() # Print the full traceback
479 | return {"ajax":True, "error":True}
480 |
--------------------------------------------------------------------------------
/src/console.py:
--------------------------------------------------------------------------------
1 | # Python Console Module that allows to print BB code in terminal
2 | # Pimp your terminal with colors
3 | # @toutjavascript https://github.com/toutjavascript
4 | # V1 : 2024
5 |
6 |
7 |
8 | import re
9 | import inspect
10 |
11 | # parse BB code and print it in console
12 | def printBB(text):
13 | text=re.sub(r"(\[h1\])([^\[\]]+)(\[/h1\])", "\033[32;1m\\2\033[0m",text,re.IGNORECASE)
14 | text=re.sub(r"(\[ok\])(.+)(\[/ok\])", "\033[32;1m\\2\033[0m",text,re.IGNORECASE)
15 | text=re.sub(r"(\[error\])([^\[\]]+)(\[/error\])", "\033[31;1m\\2\033[0m",text,re.IGNORECASE)
16 | text=re.sub(r"(\[b\])([^\[\]]+)(\[/b\])","\033[1m\\2\033[0m",text,re.IGNORECASE)
17 | text=re.sub(r"(\[u\])([^\[\]]+)(\[/u\])","\033[4m\\2\033[24m",text,re.IGNORECASE)
18 | text=re.sub(r"(\[d\])([^\[\]]+)(\[/d\])","\033[2m\\2\033[22m",text,re.IGNORECASE)
19 | text=re.sub(r"(\[fade\])([^\[\]]+)(\[/fade\])","\033[2m\\2\033[22m",text,re.IGNORECASE)
20 | text=re.sub(r"(\[warning\])([^\[\]]+)(\[/warning\])","\033[33m\\2\033[22m",text,re.IGNORECASE)
21 | text=re.sub(r"(\[reset\])","\033[0m\033[49m",text,re.IGNORECASE)
22 | text=re.sub(r"(\[reverse\])(.+)(\[/reverse\])","\033[7m\\2\033[0m",text,re.IGNORECASE)
23 | text=re.sub(r"(\[header\])([^\[\]]+)(\[/header\])","\\033[1m \\2\033[0m",text,re.IGNORECASE)
24 | text=re.sub(r"(\[hour\])([^\[\]]+)(\[/hour\])","\\033[48;5;255m\\2\033[0m",text,re.IGNORECASE)
25 | text=re.sub(r"(\[shell\])([^\[\]]+)(\[/shell\])","\\033[44;1;97m\\2\033[0m",text,re.IGNORECASE)
26 | print(text)
27 |
28 |
29 | def printExceptionError(error):
30 | caller_name = inspect.stack()[1].function
31 | file=inspect.stack()[1].filename
32 | file=file[file.rfind("\\")+1:]
33 | printBB("[error]Exception Error on "+file+"/"+caller_name+"():[/error]");
34 | printBB(" [error]"+repr(error)+"[/error]")
35 |
36 | # From this great tuto https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
37 | def test():
38 | for i in range(30, 37 + 1):
39 | print("\033[%dm%d\t\t\033[%dm%d" % (i, i, i + 60, i + 60))
40 |
41 | print("\033[39m\\033[49m - Reset color")
42 | print("\\033[2K - Clear Line")
43 | print("\\033[FoooXus: Fooocus Extender is a web application that allows you to manage your Fooocus installation.
30 |It is a simple and intuitive tool that will help you to organize your models, styles, loras and your generated images.
31 |Foooxus uses Fooocus Gradio API and increases generation possibities :
This first release is not finalized but Fooocus users asked me to publish it as soon as possible. I am working on an UI to generate multiple images by variating one or more parameters.
38 | So you will can generate a picture with multiple Guidance scale values, different refiners and Loras in one batch. You will choose the best images after completion.
39 |
This web app is connected to a python web server running on
44 |Your Fooocus install is running on
45 |When you interact with Fooocus User Interface, you activate FoooXus python programm, which exchanges informations and images with Fooocus.
46 |If FoooXus python app or Fooocus app are closed, the web UI will detect it and no work could be processed.
47 | 48 | 49 | 50 |Presets are set in the config.json
file. You can add or remove presets on each part (Models, Styles and LoRAs) .
Open the Models Tab. You will see the presets and the Models list. No image are generated at first start.
57 |Click on the button to launch generation of missing picture. And wait a little to view images.
58 |Images are stored in ./outputs/illustrations/styles/
folder, reduced by default to 512*512 with 80% JPG compression to gain space (You can modify that values in config.json).
The ./outputs/tmp/
folder stored all the generated images. You can empty it sometimes.
Don't forget that all Fooocus generated images are stored in the your/fooocus/install/outputs/YYYY-MM-DD/
folder. You can manage all your images with my Github repository toutjavascript/Fooocus-Log-Viewer.
FoooXus depends on Fooocus app. Sometimes, a Fooocus update changes the API calls and can break FoooXus. Run Fooocus with no-update tag to avoid that.
71 |Please, share ideas on Github repository toutjavascript/foooxus-fooocus-extender
74 |Also discover my Github repository toutjavascript/Fooocus-Log-Viewer to easily view all your generated images.
75 |Made by @toutjavascript / toutjavascript.com
76 |