Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
AUTOMATIC1111
GitHub Repository: AUTOMATIC1111/stable-diffusion-webui
Path: blob/master/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py
2310 views
1
import numpy as np
2
import gradio as gr
3
import math
4
from modules.ui_components import InputAccordion
5
import modules.scripts as scripts
6
from modules.torch_utils import float64
7
8
9
class SoftInpaintingSettings:
10
def __init__(self,
11
mask_blend_power,
12
mask_blend_scale,
13
inpaint_detail_preservation,
14
composite_mask_influence,
15
composite_difference_threshold,
16
composite_difference_contrast):
17
self.mask_blend_power = mask_blend_power
18
self.mask_blend_scale = mask_blend_scale
19
self.inpaint_detail_preservation = inpaint_detail_preservation
20
self.composite_mask_influence = composite_mask_influence
21
self.composite_difference_threshold = composite_difference_threshold
22
self.composite_difference_contrast = composite_difference_contrast
23
24
def add_generation_params(self, dest):
25
dest[enabled_gen_param_label] = True
26
dest[gen_param_labels.mask_blend_power] = self.mask_blend_power
27
dest[gen_param_labels.mask_blend_scale] = self.mask_blend_scale
28
dest[gen_param_labels.inpaint_detail_preservation] = self.inpaint_detail_preservation
29
dest[gen_param_labels.composite_mask_influence] = self.composite_mask_influence
30
dest[gen_param_labels.composite_difference_threshold] = self.composite_difference_threshold
31
dest[gen_param_labels.composite_difference_contrast] = self.composite_difference_contrast
32
33
34
# ------------------- Methods -------------------
35
36
def processing_uses_inpainting(p):
37
# TODO: Figure out a better way to determine if inpainting is being used by p
38
if getattr(p, "image_mask", None) is not None:
39
return True
40
41
if getattr(p, "mask", None) is not None:
42
return True
43
44
if getattr(p, "nmask", None) is not None:
45
return True
46
47
return False
48
49
50
def latent_blend(settings, a, b, t):
51
"""
52
Interpolates two latent image representations according to the parameter t,
53
where the interpolated vectors' magnitudes are also interpolated separately.
54
The "detail_preservation" factor biases the magnitude interpolation towards
55
the larger of the two magnitudes.
56
"""
57
import torch
58
59
# NOTE: We use inplace operations wherever possible.
60
61
if len(t.shape) == 3:
62
# [4][w][h] to [1][4][w][h]
63
t2 = t.unsqueeze(0)
64
# [4][w][h] to [1][1][w][h] - the [4] seem redundant.
65
t3 = t[0].unsqueeze(0).unsqueeze(0)
66
else:
67
t2 = t
68
t3 = t[:, 0][:, None]
69
70
one_minus_t2 = 1 - t2
71
one_minus_t3 = 1 - t3
72
73
# Linearly interpolate the image vectors.
74
a_scaled = a * one_minus_t2
75
b_scaled = b * t2
76
image_interp = a_scaled
77
image_interp.add_(b_scaled)
78
result_type = image_interp.dtype
79
del a_scaled, b_scaled, t2, one_minus_t2
80
81
# Calculate the magnitude of the interpolated vectors. (We will remove this magnitude.)
82
# 64-bit operations are used here to allow large exponents.
83
current_magnitude = torch.norm(image_interp, p=2, dim=1, keepdim=True).to(float64(image_interp)).add_(0.00001)
84
85
# Interpolate the powered magnitudes, then un-power them (bring them back to a power of 1).
86
a_magnitude = torch.norm(a, p=2, dim=1, keepdim=True).to(float64(a)).pow_(settings.inpaint_detail_preservation) * one_minus_t3
87
b_magnitude = torch.norm(b, p=2, dim=1, keepdim=True).to(float64(b)).pow_(settings.inpaint_detail_preservation) * t3
88
desired_magnitude = a_magnitude
89
desired_magnitude.add_(b_magnitude).pow_(1 / settings.inpaint_detail_preservation)
90
del a_magnitude, b_magnitude, t3, one_minus_t3
91
92
# Change the linearly interpolated image vectors' magnitudes to the value we want.
93
# This is the last 64-bit operation.
94
image_interp_scaling_factor = desired_magnitude
95
image_interp_scaling_factor.div_(current_magnitude)
96
image_interp_scaling_factor = image_interp_scaling_factor.to(result_type)
97
image_interp_scaled = image_interp
98
image_interp_scaled.mul_(image_interp_scaling_factor)
99
del current_magnitude
100
del desired_magnitude
101
del image_interp
102
del image_interp_scaling_factor
103
del result_type
104
105
return image_interp_scaled
106
107
108
def get_modified_nmask(settings, nmask, sigma):
109
"""
110
Converts a negative mask representing the transparency of the original latent vectors being overlaid
111
to a mask that is scaled according to the denoising strength for this step.
112
113
Where:
114
0 = fully opaque, infinite density, fully masked
115
1 = fully transparent, zero density, fully unmasked
116
117
We bring this transparency to a power, as this allows one to simulate N number of blending operations
118
where N can be any positive real value. Using this one can control the balance of influence between
119
the denoiser and the original latents according to the sigma value.
120
121
NOTE: "mask" is not used
122
"""
123
import torch
124
return torch.pow(nmask, (sigma ** settings.mask_blend_power) * settings.mask_blend_scale)
125
126
127
def apply_adaptive_masks(
128
settings: SoftInpaintingSettings,
129
nmask,
130
latent_orig,
131
latent_processed,
132
overlay_images,
133
width, height,
134
paste_to):
135
import torch
136
import modules.processing as proc
137
import modules.images as images
138
from PIL import Image, ImageOps, ImageFilter
139
140
# TODO: Bias the blending according to the latent mask, add adjustable parameter for bias control.
141
if len(nmask.shape) == 3:
142
latent_mask = nmask[0].float()
143
else:
144
latent_mask = nmask[:, 0].float()
145
# convert the original mask into a form we use to scale distances for thresholding
146
mask_scalar = 1 - (torch.clamp(latent_mask, min=0, max=1) ** (settings.mask_blend_scale / 2))
147
mask_scalar = (0.5 * (1 - settings.composite_mask_influence)
148
+ mask_scalar * settings.composite_mask_influence)
149
mask_scalar = mask_scalar / (1.00001 - mask_scalar)
150
mask_scalar = mask_scalar.cpu().numpy()
151
152
latent_distance = torch.norm(latent_processed - latent_orig, p=2, dim=1)
153
154
kernel, kernel_center = get_gaussian_kernel(stddev_radius=1.5, max_radius=2)
155
156
masks_for_overlay = []
157
158
for i, (distance_map, overlay_image) in enumerate(zip(latent_distance, overlay_images)):
159
converted_mask = distance_map.float().cpu().numpy()
160
converted_mask = weighted_histogram_filter(converted_mask, kernel, kernel_center,
161
percentile_min=0.9, percentile_max=1, min_width=1)
162
converted_mask = weighted_histogram_filter(converted_mask, kernel, kernel_center,
163
percentile_min=0.25, percentile_max=0.75, min_width=1)
164
165
# The distance at which opacity of original decreases to 50%
166
if len(mask_scalar.shape) == 3:
167
if mask_scalar.shape[0] > i:
168
half_weighted_distance = settings.composite_difference_threshold * mask_scalar[i]
169
else:
170
half_weighted_distance = settings.composite_difference_threshold * mask_scalar[0]
171
else:
172
half_weighted_distance = settings.composite_difference_threshold * mask_scalar
173
174
converted_mask = converted_mask / half_weighted_distance
175
176
converted_mask = 1 / (1 + converted_mask ** settings.composite_difference_contrast)
177
converted_mask = smootherstep(converted_mask)
178
converted_mask = 1 - converted_mask
179
converted_mask = 255. * converted_mask
180
converted_mask = converted_mask.astype(np.uint8)
181
converted_mask = Image.fromarray(converted_mask)
182
converted_mask = images.resize_image(2, converted_mask, width, height)
183
converted_mask = proc.create_binary_mask(converted_mask, round=False)
184
185
# Remove aliasing artifacts using a gaussian blur.
186
converted_mask = converted_mask.filter(ImageFilter.GaussianBlur(radius=4))
187
188
# Expand the mask to fit the whole image if needed.
189
if paste_to is not None:
190
converted_mask = proc.uncrop(converted_mask,
191
(overlay_image.width, overlay_image.height),
192
paste_to)
193
194
masks_for_overlay.append(converted_mask)
195
196
image_masked = Image.new('RGBa', (overlay_image.width, overlay_image.height))
197
image_masked.paste(overlay_image.convert("RGBA").convert("RGBa"),
198
mask=ImageOps.invert(converted_mask.convert('L')))
199
200
overlay_images[i] = image_masked.convert('RGBA')
201
202
return masks_for_overlay
203
204
205
def apply_masks(
206
settings,
207
nmask,
208
overlay_images,
209
width, height,
210
paste_to):
211
import torch
212
import modules.processing as proc
213
import modules.images as images
214
from PIL import Image, ImageOps, ImageFilter
215
216
converted_mask = nmask[0].float()
217
converted_mask = torch.clamp(converted_mask, min=0, max=1).pow_(settings.mask_blend_scale / 2)
218
converted_mask = 255. * converted_mask
219
converted_mask = converted_mask.cpu().numpy().astype(np.uint8)
220
converted_mask = Image.fromarray(converted_mask)
221
converted_mask = images.resize_image(2, converted_mask, width, height)
222
converted_mask = proc.create_binary_mask(converted_mask, round=False)
223
224
# Remove aliasing artifacts using a gaussian blur.
225
converted_mask = converted_mask.filter(ImageFilter.GaussianBlur(radius=4))
226
227
# Expand the mask to fit the whole image if needed.
228
if paste_to is not None:
229
converted_mask = proc.uncrop(converted_mask,
230
(width, height),
231
paste_to)
232
233
masks_for_overlay = []
234
235
for i, overlay_image in enumerate(overlay_images):
236
masks_for_overlay[i] = converted_mask
237
238
image_masked = Image.new('RGBa', (overlay_image.width, overlay_image.height))
239
image_masked.paste(overlay_image.convert("RGBA").convert("RGBa"),
240
mask=ImageOps.invert(converted_mask.convert('L')))
241
242
overlay_images[i] = image_masked.convert('RGBA')
243
244
return masks_for_overlay
245
246
247
def weighted_histogram_filter(img, kernel, kernel_center, percentile_min=0.0, percentile_max=1.0, min_width=1.0):
248
"""
249
Generalization convolution filter capable of applying
250
weighted mean, median, maximum, and minimum filters
251
parametrically using an arbitrary kernel.
252
253
Args:
254
img (nparray):
255
The image, a 2-D array of floats, to which the filter is being applied.
256
kernel (nparray):
257
The kernel, a 2-D array of floats.
258
kernel_center (nparray):
259
The kernel center coordinate, a 1-D array with two elements.
260
percentile_min (float):
261
The lower bound of the histogram window used by the filter,
262
from 0 to 1.
263
percentile_max (float):
264
The upper bound of the histogram window used by the filter,
265
from 0 to 1.
266
min_width (float):
267
The minimum size of the histogram window bounds, in weight units.
268
Must be greater than 0.
269
270
Returns:
271
(nparray): A filtered copy of the input image "img", a 2-D array of floats.
272
"""
273
274
# Converts an index tuple into a vector.
275
def vec(x):
276
return np.array(x)
277
278
kernel_min = -kernel_center
279
kernel_max = vec(kernel.shape) - kernel_center
280
281
def weighted_histogram_filter_single(idx):
282
idx = vec(idx)
283
min_index = np.maximum(0, idx + kernel_min)
284
max_index = np.minimum(vec(img.shape), idx + kernel_max)
285
window_shape = max_index - min_index
286
287
class WeightedElement:
288
"""
289
An element of the histogram, its weight
290
and bounds.
291
"""
292
293
def __init__(self, value, weight):
294
self.value: float = value
295
self.weight: float = weight
296
self.window_min: float = 0.0
297
self.window_max: float = 1.0
298
299
# Collect the values in the image as WeightedElements,
300
# weighted by their corresponding kernel values.
301
values = []
302
for window_tup in np.ndindex(tuple(window_shape)):
303
window_index = vec(window_tup)
304
image_index = window_index + min_index
305
centered_kernel_index = image_index - idx
306
kernel_index = centered_kernel_index + kernel_center
307
element = WeightedElement(img[tuple(image_index)], kernel[tuple(kernel_index)])
308
values.append(element)
309
310
def sort_key(x: WeightedElement):
311
return x.value
312
313
values.sort(key=sort_key)
314
315
# Calculate the height of the stack (sum)
316
# and each sample's range they occupy in the stack
317
sum = 0
318
for i in range(len(values)):
319
values[i].window_min = sum
320
sum += values[i].weight
321
values[i].window_max = sum
322
323
# Calculate what range of this stack ("window")
324
# we want to get the weighted average across.
325
window_min = sum * percentile_min
326
window_max = sum * percentile_max
327
window_width = window_max - window_min
328
329
# Ensure the window is within the stack and at least a certain size.
330
if window_width < min_width:
331
window_center = (window_min + window_max) / 2
332
window_min = window_center - min_width / 2
333
window_max = window_center + min_width / 2
334
335
if window_max > sum:
336
window_max = sum
337
window_min = sum - min_width
338
339
if window_min < 0:
340
window_min = 0
341
window_max = min_width
342
343
value = 0
344
value_weight = 0
345
346
# Get the weighted average of all the samples
347
# that overlap with the window, weighted
348
# by the size of their overlap.
349
for i in range(len(values)):
350
if window_min >= values[i].window_max:
351
continue
352
if window_max <= values[i].window_min:
353
break
354
355
s = max(window_min, values[i].window_min)
356
e = min(window_max, values[i].window_max)
357
w = e - s
358
359
value += values[i].value * w
360
value_weight += w
361
362
return value / value_weight if value_weight != 0 else 0
363
364
img_out = img.copy()
365
366
# Apply the kernel operation over each pixel.
367
for index in np.ndindex(img.shape):
368
img_out[index] = weighted_histogram_filter_single(index)
369
370
return img_out
371
372
373
def smoothstep(x):
374
"""
375
The smoothstep function, input should be clamped to 0-1 range.
376
Turns a diagonal line (f(x) = x) into a sigmoid-like curve.
377
"""
378
return x * x * (3 - 2 * x)
379
380
381
def smootherstep(x):
382
"""
383
The smootherstep function, input should be clamped to 0-1 range.
384
Turns a diagonal line (f(x) = x) into a sigmoid-like curve.
385
"""
386
return x * x * x * (x * (6 * x - 15) + 10)
387
388
389
def get_gaussian_kernel(stddev_radius=1.0, max_radius=2):
390
"""
391
Creates a Gaussian kernel with thresholded edges.
392
393
Args:
394
stddev_radius (float):
395
Standard deviation of the gaussian kernel, in pixels.
396
max_radius (int):
397
The size of the filter kernel. The number of pixels is (max_radius*2+1) ** 2.
398
The kernel is thresholded so that any values one pixel beyond this radius
399
is weighted at 0.
400
401
Returns:
402
(nparray, nparray): A kernel array (shape: (N, N)), its center coordinate (shape: (2))
403
"""
404
405
# Evaluates a 0-1 normalized gaussian function for a given square distance from the mean.
406
def gaussian(sqr_mag):
407
return math.exp(-sqr_mag / (stddev_radius * stddev_radius))
408
409
# Helper function for converting a tuple to an array.
410
def vec(x):
411
return np.array(x)
412
413
"""
414
Since a gaussian is unbounded, we need to limit ourselves
415
to a finite range.
416
We taper the ends off at the end of that range so they equal zero
417
while preserving the maximum value of 1 at the mean.
418
"""
419
zero_radius = max_radius + 1.0
420
gauss_zero = gaussian(zero_radius * zero_radius)
421
gauss_kernel_scale = 1 / (1 - gauss_zero)
422
423
def gaussian_kernel_func(coordinate):
424
x = coordinate[0] ** 2.0 + coordinate[1] ** 2.0
425
x = gaussian(x)
426
x -= gauss_zero
427
x *= gauss_kernel_scale
428
x = max(0.0, x)
429
return x
430
431
size = max_radius * 2 + 1
432
kernel_center = max_radius
433
kernel = np.zeros((size, size))
434
435
for index in np.ndindex(kernel.shape):
436
kernel[index] = gaussian_kernel_func(vec(index) - kernel_center)
437
438
return kernel, kernel_center
439
440
441
# ------------------- Constants -------------------
442
443
444
default = SoftInpaintingSettings(1, 0.5, 4, 0, 0.5, 2)
445
446
enabled_ui_label = "Soft inpainting"
447
enabled_gen_param_label = "Soft inpainting enabled"
448
enabled_el_id = "soft_inpainting_enabled"
449
450
ui_labels = SoftInpaintingSettings(
451
"Schedule bias",
452
"Preservation strength",
453
"Transition contrast boost",
454
"Mask influence",
455
"Difference threshold",
456
"Difference contrast")
457
458
ui_info = SoftInpaintingSettings(
459
"Shifts when preservation of original content occurs during denoising.",
460
"How strongly partially masked content should be preserved.",
461
"Amplifies the contrast that may be lost in partially masked regions.",
462
"How strongly the original mask should bias the difference threshold.",
463
"How much an image region can change before the original pixels are not blended in anymore.",
464
"How sharp the transition should be between blended and not blended.")
465
466
gen_param_labels = SoftInpaintingSettings(
467
"Soft inpainting schedule bias",
468
"Soft inpainting preservation strength",
469
"Soft inpainting transition contrast boost",
470
"Soft inpainting mask influence",
471
"Soft inpainting difference threshold",
472
"Soft inpainting difference contrast")
473
474
el_ids = SoftInpaintingSettings(
475
"mask_blend_power",
476
"mask_blend_scale",
477
"inpaint_detail_preservation",
478
"composite_mask_influence",
479
"composite_difference_threshold",
480
"composite_difference_contrast")
481
482
483
# ------------------- Script -------------------
484
485
486
class Script(scripts.Script):
487
def __init__(self):
488
self.section = "inpaint"
489
self.masks_for_overlay = None
490
self.overlay_images = None
491
492
def title(self):
493
return "Soft Inpainting"
494
495
def show(self, is_img2img):
496
return scripts.AlwaysVisible if is_img2img else False
497
498
def ui(self, is_img2img):
499
if not is_img2img:
500
return
501
502
with InputAccordion(False, label=enabled_ui_label, elem_id=enabled_el_id) as soft_inpainting_enabled:
503
with gr.Group():
504
gr.Markdown(
505
"""
506
Soft inpainting allows you to **seamlessly blend original content with inpainted content** according to the mask opacity.
507
**High _Mask blur_** values are recommended!
508
""")
509
510
power = \
511
gr.Slider(label=ui_labels.mask_blend_power,
512
info=ui_info.mask_blend_power,
513
minimum=0,
514
maximum=8,
515
step=0.1,
516
value=default.mask_blend_power,
517
elem_id=el_ids.mask_blend_power)
518
scale = \
519
gr.Slider(label=ui_labels.mask_blend_scale,
520
info=ui_info.mask_blend_scale,
521
minimum=0,
522
maximum=8,
523
step=0.05,
524
value=default.mask_blend_scale,
525
elem_id=el_ids.mask_blend_scale)
526
detail = \
527
gr.Slider(label=ui_labels.inpaint_detail_preservation,
528
info=ui_info.inpaint_detail_preservation,
529
minimum=1,
530
maximum=32,
531
step=0.5,
532
value=default.inpaint_detail_preservation,
533
elem_id=el_ids.inpaint_detail_preservation)
534
535
gr.Markdown(
536
"""
537
### Pixel Composite Settings
538
""")
539
540
mask_inf = \
541
gr.Slider(label=ui_labels.composite_mask_influence,
542
info=ui_info.composite_mask_influence,
543
minimum=0,
544
maximum=1,
545
step=0.05,
546
value=default.composite_mask_influence,
547
elem_id=el_ids.composite_mask_influence)
548
549
dif_thresh = \
550
gr.Slider(label=ui_labels.composite_difference_threshold,
551
info=ui_info.composite_difference_threshold,
552
minimum=0,
553
maximum=8,
554
step=0.25,
555
value=default.composite_difference_threshold,
556
elem_id=el_ids.composite_difference_threshold)
557
558
dif_contr = \
559
gr.Slider(label=ui_labels.composite_difference_contrast,
560
info=ui_info.composite_difference_contrast,
561
minimum=0,
562
maximum=8,
563
step=0.25,
564
value=default.composite_difference_contrast,
565
elem_id=el_ids.composite_difference_contrast)
566
567
with gr.Accordion("Help", open=False):
568
gr.Markdown(
569
f"""
570
### {ui_labels.mask_blend_power}
571
572
The blending strength of original content is scaled proportionally with the decreasing noise level values at each step (sigmas).
573
This ensures that the influence of the denoiser and original content preservation is roughly balanced at each step.
574
This balance can be shifted using this parameter, controlling whether earlier or later steps have stronger preservation.
575
576
- **Below 1**: Stronger preservation near the end (with low sigma)
577
- **1**: Balanced (proportional to sigma)
578
- **Above 1**: Stronger preservation in the beginning (with high sigma)
579
""")
580
gr.Markdown(
581
f"""
582
### {ui_labels.mask_blend_scale}
583
584
Skews whether partially masked image regions should be more likely to preserve the original content or favor inpainted content.
585
This may need to be adjusted depending on the {ui_labels.mask_blend_power}, CFG Scale, prompt and Denoising strength.
586
587
- **Low values**: Favors generated content.
588
- **High values**: Favors original content.
589
""")
590
gr.Markdown(
591
f"""
592
### {ui_labels.inpaint_detail_preservation}
593
594
This parameter controls how the original latent vectors and denoised latent vectors are interpolated.
595
With higher values, the magnitude of the resulting blended vector will be closer to the maximum of the two interpolated vectors.
596
This can prevent the loss of contrast that occurs with linear interpolation.
597
598
- **Low values**: Softer blending, details may fade.
599
- **High values**: Stronger contrast, may over-saturate colors.
600
""")
601
602
gr.Markdown(
603
"""
604
## Pixel Composite Settings
605
606
Masks are generated based on how much a part of the image changed after denoising.
607
These masks are used to blend the original and final images together.
608
If the difference is low, the original pixels are used instead of the pixels returned by the inpainting process.
609
""")
610
611
gr.Markdown(
612
f"""
613
### {ui_labels.composite_mask_influence}
614
615
This parameter controls how much the mask should bias this sensitivity to difference.
616
617
- **0**: Ignore the mask, only consider differences in image content.
618
- **1**: Follow the mask closely despite image content changes.
619
""")
620
621
gr.Markdown(
622
f"""
623
### {ui_labels.composite_difference_threshold}
624
625
This value represents the difference at which the original pixels will have less than 50% opacity.
626
627
- **Low values**: Two images patches must be almost the same in order to retain original pixels.
628
- **High values**: Two images patches can be very different and still retain original pixels.
629
""")
630
631
gr.Markdown(
632
f"""
633
### {ui_labels.composite_difference_contrast}
634
635
This value represents the contrast between the opacity of the original and inpainted content.
636
637
- **Low values**: The blend will be more gradual and have longer transitions, but may cause ghosting.
638
- **High values**: Ghosting will be less common, but transitions may be very sudden.
639
""")
640
641
self.infotext_fields = [(soft_inpainting_enabled, enabled_gen_param_label),
642
(power, gen_param_labels.mask_blend_power),
643
(scale, gen_param_labels.mask_blend_scale),
644
(detail, gen_param_labels.inpaint_detail_preservation),
645
(mask_inf, gen_param_labels.composite_mask_influence),
646
(dif_thresh, gen_param_labels.composite_difference_threshold),
647
(dif_contr, gen_param_labels.composite_difference_contrast)]
648
649
self.paste_field_names = []
650
for _, field_name in self.infotext_fields:
651
self.paste_field_names.append(field_name)
652
653
return [soft_inpainting_enabled,
654
power,
655
scale,
656
detail,
657
mask_inf,
658
dif_thresh,
659
dif_contr]
660
661
def process(self, p, enabled, power, scale, detail_preservation, mask_inf, dif_thresh, dif_contr):
662
if not enabled:
663
return
664
665
if not processing_uses_inpainting(p):
666
return
667
668
# Shut off the rounding it normally does.
669
p.mask_round = False
670
671
settings = SoftInpaintingSettings(power, scale, detail_preservation, mask_inf, dif_thresh, dif_contr)
672
673
# p.extra_generation_params["Mask rounding"] = False
674
settings.add_generation_params(p.extra_generation_params)
675
676
def on_mask_blend(self, p, mba: scripts.MaskBlendArgs, enabled, power, scale, detail_preservation, mask_inf,
677
dif_thresh, dif_contr):
678
if not enabled:
679
return
680
681
if not processing_uses_inpainting(p):
682
return
683
684
if mba.is_final_blend:
685
mba.blended_latent = mba.current_latent
686
return
687
688
settings = SoftInpaintingSettings(power, scale, detail_preservation, mask_inf, dif_thresh, dif_contr)
689
690
# todo: Why is sigma 2D? Both values are the same.
691
mba.blended_latent = latent_blend(settings,
692
mba.init_latent,
693
mba.current_latent,
694
get_modified_nmask(settings, mba.nmask, mba.sigma[0]))
695
696
def post_sample(self, p, ps: scripts.PostSampleArgs, enabled, power, scale, detail_preservation, mask_inf,
697
dif_thresh, dif_contr):
698
if not enabled:
699
return
700
701
if not processing_uses_inpainting(p):
702
return
703
704
nmask = getattr(p, "nmask", None)
705
if nmask is None:
706
return
707
708
from modules import images
709
from modules.shared import opts
710
711
settings = SoftInpaintingSettings(power, scale, detail_preservation, mask_inf, dif_thresh, dif_contr)
712
713
# since the original code puts holes in the existing overlay images,
714
# we have to rebuild them.
715
self.overlay_images = []
716
for img in p.init_images:
717
718
image = images.flatten(img, opts.img2img_background_color)
719
720
if p.paste_to is None and p.resize_mode != 3:
721
image = images.resize_image(p.resize_mode, image, p.width, p.height)
722
723
self.overlay_images.append(image.convert('RGBA'))
724
725
if len(p.init_images) == 1:
726
self.overlay_images = self.overlay_images * p.batch_size
727
728
if getattr(ps.samples, 'already_decoded', False):
729
self.masks_for_overlay = apply_masks(settings=settings,
730
nmask=nmask,
731
overlay_images=self.overlay_images,
732
width=p.width,
733
height=p.height,
734
paste_to=p.paste_to)
735
else:
736
self.masks_for_overlay = apply_adaptive_masks(settings=settings,
737
nmask=nmask,
738
latent_orig=p.init_latent,
739
latent_processed=ps.samples,
740
overlay_images=self.overlay_images,
741
width=p.width,
742
height=p.height,
743
paste_to=p.paste_to)
744
745
def postprocess_maskoverlay(self, p, ppmo: scripts.PostProcessMaskOverlayArgs, enabled, power, scale,
746
detail_preservation, mask_inf, dif_thresh, dif_contr):
747
if not enabled:
748
return
749
750
if not processing_uses_inpainting(p):
751
return
752
753
if self.masks_for_overlay is None:
754
return
755
756
if self.overlay_images is None:
757
return
758
759
ppmo.mask_for_overlay = self.masks_for_overlay[ppmo.index]
760
ppmo.overlay_image = self.overlay_images[ppmo.index]
761
762