Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

ControlNet implementation suggestion #139

Open
wants to merge 70 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
fc966ce
UI work in progress
Jorge0998 Mar 30, 2023
3dccbec
UI work in progress
Mar 30, 2023
7c1d26b
Merge branch 'personal-controlnet-implementation' of https://github.c…
JasonS09 Mar 30, 2023
c1f337a
Added controlnet preprocessors settings
Jorge0998 Mar 31, 2023
75ead57
Added controlnet preprocessors settings
JasonS09 Mar 31, 2023
4a2f7d1
Merge branch 'personal-controlnet-implementation' of https://github.c…
Jorge0998 Mar 31, 2023
78c216e
Merge branch 'personal-controlnet-implementation' of https://github.c…
JasonS09 Mar 31, 2023
c7c4e33
Merge branch 'personal-controlnet-implementation' of https://github.c…
JasonS09 Mar 31, 2023
a9984e0
Added controlnet base layout
JasonS09 Apr 1, 2023
44a473a
Added the ability to paste copied image to the image loader layout.
JasonS09 Apr 1, 2023
0ab4579
Get the models and modules from the backend
JasonS09 Apr 2, 2023
f87f803
Added the ability to preview annotators
JasonS09 Apr 2, 2023
a0a3f55
txt2img work in progress
JasonS09 Apr 3, 2023
8dccecd
Working txt2img
JasonS09 Apr 4, 2023
ac41be9
Img2img work in progress
JasonS09 Apr 4, 2023
3a031ed
Working im2img
JasonS09 Apr 5, 2023
6e64766
Remove QMessageBox for debugging
JasonS09 Apr 5, 2023
b6f7bc7
Merge pull request #1 from Interpause/main
JasonS09 Apr 17, 2023
0768ccd
Fixed bug that restarted threshold values every 3 seconds.
JasonS09 Apr 17, 2023
b7a58ad
Merge pull request #2 from Interpause/main
JasonS09 Apr 17, 2023
efbbc87
Merge branch 'personal-controlnet-implementation' of https://github.c…
JasonS09 Apr 17, 2023
8eb2736
Fixed bug that prevented to run controlnet with img2img
JasonS09 Apr 17, 2023
de2156b
Fixed preprocessor preview bug
JasonS09 May 1, 2023
a0c7c4f
Merge pull request #3 from Interpause/main
JasonS09 May 5, 2023
285b0da
Merge pull request #4 from Interpause/main
JasonS09 May 5, 2023
b3acf84
UI work in progress
Mar 30, 2023
eb36174
Added controlnet preprocessors settings
JasonS09 Mar 31, 2023
3204891
Added controlnet base layout
JasonS09 Apr 1, 2023
da1507f
Added the ability to paste copied image to the image loader layout.
JasonS09 Apr 1, 2023
6d4ef4a
Get the models and modules from the backend
JasonS09 Apr 2, 2023
e223b86
Added the ability to preview annotators
JasonS09 Apr 2, 2023
91d08cb
txt2img work in progress
JasonS09 Apr 3, 2023
c906cde
Working txt2img
JasonS09 Apr 4, 2023
e00f8d7
Img2img work in progress
JasonS09 Apr 4, 2023
2e0d344
Working im2img
JasonS09 Apr 5, 2023
f1c7e0a
Remove QMessageBox for debugging
JasonS09 Apr 5, 2023
54e262b
Fixed bug that restarted threshold values every 3 seconds.
JasonS09 Apr 17, 2023
50277eb
Fixed bug that prevented to run controlnet with img2img
JasonS09 Apr 17, 2023
3af1846
Fixed preprocessor preview bug
JasonS09 May 1, 2023
90d1344
Transparency mask for inpainting WIP
JasonS09 May 1, 2023
b9accbb
Update controlnet
JasonS09 May 15, 2023
e8d7e90
Merge branch 'personal-controlnet-implementation' of https://github.c…
JasonS09 May 15, 2023
0bce1b8
Fixed errors
JasonS09 May 15, 2023
8520418
Fixed issue with controlnet threshold spin boxes
JasonS09 May 16, 2023
94fd0dc
More bug fixes
JasonS09 May 16, 2023
f2eef08
horrible, broken transparency mask for inpainting
drhead Jun 6, 2023
4895381
Fixed preprocessor options bug at start.
JasonS09 Jun 6, 2023
9105bbe
Switch controlnet unit at start.
JasonS09 Jun 6, 2023
fb7fabe
Merge pull request #5 from drhead/drhead-patch-1
JasonS09 Jun 6, 2023
14f839d
Working implementation of transparency mask
drhead Jun 6, 2023
d670f7c
Better race condition handling
drhead Jun 7, 2023
1f1a291
Merge pull request #6 from drhead/personal-controlnet-implementation
JasonS09 Jun 7, 2023
e7e4b25
Implement img2img upscale and update mask converter
drhead Jun 7, 2023
cdc2029
Implement upscale postprocess API call function
drhead Jun 7, 2023
eafff05
fix inpainting without selection
drhead Jun 8, 2023
d1a0826
Regression bug fix
JasonS09 Jun 8, 2023
bd7c878
Merge branch 'JasonS09:personal-controlnet-implementation' into perso…
drhead Jun 8, 2023
a99774b
Bypass upscaling call when it is not needed
drhead Jun 8, 2023
14e776b
changed mask generation to only affect group (may break old api)
drhead Jun 9, 2023
2859a75
fix txt2img and simplify glayer creation
drhead Jun 9, 2023
bc6fa51
desperation
drhead Jun 9, 2023
5391954
QTimer-based approach to avoiding mask race condition
drhead Jun 13, 2023
91f5ad8
Fixed mask shredding bug.
drhead Jun 13, 2023
c72b9a8
Regression bug fix
JasonS09 Jun 8, 2023
d8ac876
Regression bug fix
JasonS09 Jun 8, 2023
72a4af5
Fix handling of no selection in inpaint
drhead Jun 13, 2023
cc142c2
Fixed pixel perfect bug
JasonS09 Jun 15, 2023
0d0e476
fixed mask positioning bug
drhead Jun 21, 2023
bfa6e90
Merge branch 'personal-controlnet-implementation' into personal-contr…
JasonS09 Jul 4, 2023
c3fae53
Merge pull request #7 from drhead/personal-controlnet-implementation
JasonS09 Jul 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions frontends/krita/krita_diff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
TAB_SDCOMMON,
TAB_TXT2IMG,
TAB_UPSCALE,
TAB_CONTROLNET
)
from .docker import create_docker
from .extension import SDPluginExtension
Expand All @@ -18,6 +19,7 @@
SDCommonPage,
Txt2ImgPage,
UpscalePage,
ControlNetPage
)
from .pages.preview import PreviewPage
from .script import script
Expand Down Expand Up @@ -60,6 +62,13 @@
create_docker(UpscalePage),
)
)
instance.addDockWidgetFactory(
DockWidgetFactory(
TAB_CONTROLNET,
DockWidgetFactoryBase.DockLeft,
create_docker(ControlNetPage),
)
)
instance.addDockWidgetFactory(
DockWidgetFactory(
TAB_CONFIG,
Expand Down
259 changes: 257 additions & 2 deletions frontends/krita/krita_diff/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,21 @@
LONG_TIMEOUT,
OFFICIAL_ROUTE_PREFIX,
ROUTE_PREFIX,
CONTROLNET_ROUTE_PREFIX,
SHORT_TIMEOUT,
STATE_DONE,
STATE_READY,
STATE_URLERROR,
THREADED,
)
from .utils import bytewise_xor, fix_prompt, get_ext_args, get_ext_key, img_to_b64
from .utils import (
bytewise_xor,
fix_prompt,
get_ext_args,
get_ext_key,
img_to_b64,
calculate_resized_image_dimensions
)

# NOTE: backend queues up responses, so no explicit need to block multiple requests
# except to prevent user from spamming themselves
Expand Down Expand Up @@ -238,6 +246,74 @@ def common_params(self, has_selection):
save_samples=self.cfg("save_temp_images", bool),
)
return params

def options_params(self):
"""Parameters that are specific for the official API options endpoint
or overriding settings."""
params = dict(
sd_model_checkpoint=self.cfg("sd_model", str),
sd_vae=self.cfg("sd_vae", str),
CLIP_stop_at_last_layers=self.cfg("clip_skip", int),
upscaler_for_img2img=self.cfg("upscaler_name", str),
face_restoration_model=self.cfg("face_restorer_model", str),
code_former_weight=self.cfg("codeformer_weight", float),
#Couldn't find filter_nsfw option for official API.
img2img_fix_steps=self.cfg("do_exact_steps", bool), #Not sure if this is matched correctly.
img2img_color_correction=self.cfg("img2img_color_correct", bool),
return_grid=self.cfg("include_grid", bool)
)
return params

def official_api_common_params(self, has_selection, width, height,
controlnet_src_imgs):
"""Parameters used by most official API endpoints."""
tiling = self.cfg("sd_tiling", bool) and not (
self.cfg("only_full_img_tiling", bool) and has_selection
)

params = dict(
batch_size=self.cfg("sd_batch_size", int),
width=width,
height=height,
tiling=tiling,
restore_faces=self.cfg("face_restorer_model", str) != "None",
override_settings=self.options_params(),
override_settings_restore_afterwards=False,
alwayson_scripts={}
)

if controlnet_src_imgs:
controlnet_units_param = list()

for i in range(len(self.cfg("controlnet_unit_list", "QStringList"))):
if self.cfg(f"controlnet{i}_enable", bool):
controlnet_units_param.append(
self.controlnet_unit_params(img_to_b64(controlnet_src_imgs[str(i)]), i)
)

params["alwayson_scripts"].update({
"controlnet": {
"args": controlnet_units_param
}
})

return params

def controlnet_unit_params(self, image: str, unit: int):
params = dict(
input_image=image,
module=self.cfg(f"controlnet{unit}_preprocessor", str),
model=self.cfg(f"controlnet{unit}_model", str),
weight=self.cfg(f"controlnet{unit}_weight", float),
lowvram=self.cfg(f"controlnet{unit}_low_vram", bool),
processor_res=self.cfg(f"controlnet{unit}_preprocessor_resolution", int),
threshold_a=self.cfg(f"controlnet{unit}_threshold_a", float),
threshold_b=self.cfg(f"controlnet{unit}_threshold_b", float),
guidance_start=self.cfg(f"controlnet{unit}_guidance_start", float),
guidance_end=self.cfg(f"controlnet{unit}_guidance_end", float),
guessmode=self.cfg(f"controlnet{unit}_guess_mode", bool)
)
return params

def get_config(self):
def cb(obj):
Expand Down Expand Up @@ -294,6 +370,42 @@ def cb(obj):

self.get("config", cb, ignore_no_connection=True)

def get_controlnet_config(self):
'''Get models and modules for ControlNet'''
def check_response(obj, key: str):
try:
assert key in obj
except:
self.status.emit(
f"{STATE_URLERROR}: incompatible response, are you running the right API?"
)
print("Invalid Response:\n", obj)
return

def set_model_list(obj):
key = "model_list"
check_response(obj, key)
self.cfg.set("controlnet_model_list", ["None"] + obj[key])

def set_preprocessor_list(obj):
key = "module_list"
check_response(obj, key)
self.cfg.set("controlnet_preprocessor_list", obj[key])

#Get controlnet API url
url = get_url(self.cfg, prefix=CONTROLNET_ROUTE_PREFIX)
self.get("model_list", set_model_list, base_url=url)
self.get("module_list", set_preprocessor_list, base_url=url)

# def post_options(self):
# """Sets the options for the backend, using the official API"""
# def cb(response):
# assert response is not None, "Backend Error, check terminal"

# params = self.options_params()
# url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX)
# self.post("options", params, cb, base_url=url)

def post_txt2img(self, cb, width, height, has_selection):
params = dict(orig_width=width, orig_height=height)
if not self.cfg("just_use_yaml", bool):
Expand All @@ -320,8 +432,48 @@ def post_txt2img(self, cb, width, height, has_selection):

self.post("txt2img", params, cb)

def post_official_api_txt2img(self, cb, width, height, has_selection,
controlnet_src_imgs: dict = {}):
"""Uses official API. Leave controlnet_src_imgs empty to not use controlnet."""
if not self.cfg("just_use_yaml", bool):
seed = (
int(self.cfg("txt2img_seed", str)) # Qt casts int as 32-bit int
if not self.cfg("txt2img_seed", str).strip() == ""
else -1
)
ext_name = self.cfg("txt2img_script", str)
ext_args = get_ext_args(self.ext_cfg, "scripts_txt2img", ext_name)
resized_width, resized_height = calculate_resized_image_dimensions(
self.cfg("sd_base_size", int), self.cfg("sd_max_size", int), width, height
)
disable_base_and_max_size = self.cfg("disable_sddebz_highres", bool)
params = self.official_api_common_params(
has_selection,
resized_width if not disable_base_and_max_size else width,
resized_height if not disable_base_and_max_size else height,
controlnet_src_imgs
)
params.update(
prompt=fix_prompt(self.cfg("txt2img_prompt", str)),
negative_prompt=fix_prompt(self.cfg("txt2img_negative_prompt", str)),
sampler_name=self.cfg("txt2img_sampler", str),
steps=self.cfg("txt2img_steps", int),
cfg_scale=self.cfg("txt2img_cfg_scale", float),
seed=seed,
enable_hr=self.cfg("txt2img_highres", bool),
hr_upscaler=self.cfg("upscaler_name", str),
hr_resize_x=width,
hr_resize_y=height,
denoising_strength=self.cfg("txt2img_denoising_strength", float),
script_name=ext_name if ext_name != "None" else None, #Prevent unrecognized "None" script from backend
script_args=ext_args if ext_name != "None" else []
)

url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX)
self.post("txt2img", params, cb, base_url=url)

def post_img2img(self, cb, src_img, mask_img, has_selection):
params = dict(is_inpaint=False, src_img=img_to_b64(src_img))
params = dict(is_inpaint=False, src_img=img_to_b64(src_img))
if not self.cfg("just_use_yaml", bool):
seed = (
int(self.cfg("img2img_seed", str)) # Qt casts int as 32-bit int
Expand All @@ -346,11 +498,49 @@ def post_img2img(self, cb, src_img, mask_img, has_selection):

self.post("img2img", params, cb)

def post_official_api_img2img(self, cb, src_img, width, height, has_selection,
controlnet_src_imgs: dict = {}):
"""Uses official API. Leave controlnet_src_imgs empty to not use controlnet."""
params = dict(init_images=[img_to_b64(src_img)])
if not self.cfg("just_use_yaml", bool):
seed = (
int(self.cfg("img2img_seed", str)) # Qt casts int as 32-bit int
if not self.cfg("img2img_seed", str).strip() == ""
else -1
)
ext_name = self.cfg("img2img_script", str)
ext_args = get_ext_args(self.ext_cfg, "scripts_img2img", ext_name)
resized_width, resized_height = calculate_resized_image_dimensions(
self.cfg("sd_base_size", int), self.cfg("sd_max_size", int), width, height
)
disable_base_and_max_size = self.cfg("disable_sddebz_highres", bool)
params.update(self.official_api_common_params(
has_selection,
resized_width if not disable_base_and_max_size else width,
resized_height if not disable_base_and_max_size else height,
controlnet_src_imgs
))
params.update(
prompt=fix_prompt(self.cfg("img2img_prompt", str)),
negative_prompt=fix_prompt(self.cfg("img2img_negative_prompt", str)),
sampler_name=self.cfg("img2img_sampler", str),
steps=self.cfg("img2img_steps", int),
cfg_scale=self.cfg("img2img_cfg_scale", float),
seed=seed,
denoising_strength=self.cfg("img2img_denoising_strength", float),
script_name=ext_name if ext_name != "None" else None,
script_args=ext_args if ext_name != "None" else []
)

url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX)
self.post("img2img", params, cb, base_url=url)

def post_inpaint(self, cb, src_img, mask_img, has_selection):
assert mask_img, "Inpaint layer is needed for inpainting!"
params = dict(
is_inpaint=True, src_img=img_to_b64(src_img), mask_img=img_to_b64(mask_img)
)

if not self.cfg("just_use_yaml", bool):
seed = (
int(self.cfg("inpaint_seed", str)) # Qt casts int as 32-bit int
Expand Down Expand Up @@ -385,6 +575,57 @@ def post_inpaint(self, cb, src_img, mask_img, has_selection):

self.post("img2img", params, cb)

def post_official_api_inpaint(self, cb, src_img, mask_img, width, height, has_selection,
controlnet_src_imgs: dict = {}):
"""Uses official API. Leave controlnet_src_imgs empty to not use controlnet."""
assert mask_img, "Inpaint layer is needed for inpainting!"
params = dict(
init_images=[img_to_b64(src_img)], mask=img_to_b64(mask_img)
)
if not self.cfg("just_use_yaml", bool):
seed = (
int(self.cfg("inpaint_seed", str)) # Qt casts int as 32-bit int
if not self.cfg("inpaint_seed", str).strip() == ""
else -1
)
fill = self.cfg("inpaint_fill_list", "QStringList").index(
self.cfg("inpaint_fill", str)
)
ext_name = self.cfg("inpaint_script", str)
ext_args = get_ext_args(self.ext_cfg, "scripts_inpaint", ext_name)
resized_width, resized_height = calculate_resized_image_dimensions(
self.cfg("sd_base_size", int), self.cfg("sd_max_size", int), width, height
)
invert_mask = self.cfg("inpaint_invert_mask", bool)
disable_base_and_max_size = self.cfg("disable_sddebz_highres", bool)
params.update(self.official_api_common_params(
has_selection,
resized_width if not disable_base_and_max_size else width,
resized_height if not disable_base_and_max_size else height,
controlnet_src_imgs
))
params.update(
prompt=fix_prompt(self.cfg("inpaint_prompt", str)),
negative_prompt=fix_prompt(self.cfg("inpaint_negative_prompt", str)),
sampler_name=self.cfg("inpaint_sampler", str),
steps=self.cfg("inpaint_steps", int),
cfg_scale=self.cfg("inpaint_cfg_scale", float),
seed=seed,
denoising_strength=self.cfg("inpaint_denoising_strength", float),
script_name=ext_name if ext_name != "None" else None,
script_args=ext_args if ext_name != "None" else [],
inpainting_mask_invert=0 if not invert_mask else 1,
inpainting_fill=fill,
mask_blur=0,
inpaint_full_res=False
#not sure what's the equivalent of mask weight for official API
)

params["override_settings"]["return_grid"] = False

url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX)
self.post("img2img", params, cb, base_url=url)

def post_upscale(self, cb, src_img):
params = (
{
Expand All @@ -397,6 +638,20 @@ def post_upscale(self, cb, src_img):
)
self.post("upscale", params, cb)

def post_controlnet_preview(self, cb, src_img):
unit = self.cfg("controlnet_unit")
params = (
{
"controlnet_module": self.cfg(f"controlnet{unit}_preprocessor"),
"controlnet_input_images": [img_to_b64(src_img)],
"controlnet_processor_res": self.cfg(f"controlnet{unit}_preprocessor_resolution"),
"controlnet_threshold_a": self.cfg(f"controlnet{unit}_threshold_a"),
"controlnet_threshold_b": self.cfg(f"controlnet{unit}_threshold_b")
} #Not sure if it's necessary to make the just_use_yaml validation here
)
url = get_url(self.cfg, prefix=CONTROLNET_ROUTE_PREFIX)
self.post("detect", params, cb, url)

def post_interrupt(self, cb):
# get official API url
url = get_url(self.cfg, prefix=OFFICIAL_ROUTE_PREFIX)
Expand Down
Loading