FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
diffusers/src/diffusers/image_processor.py at main · hlky/diffusers · GitHub
hlky
/
diffusers
Public
forked from
huggingface/diffusers
Notifications
You must be signed in to change notification settings
Fork
1
Star
3
Code
Pull requests
0
Actions
Projects
Security and quality
0
Insights
Additional navigation options
Code
Pull requests
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
diffusers
/
src
/
diffusers
/
image_processor.py
Copy path
More file actions
More file actions
Latest commit
History
History
History
1468 lines (1238 loc) · 57 KB
Breadcrumbs
diffusers
/
src
/
diffusers
/
image_processor.py
Copy path
File metadata and controls
1468 lines (1238 loc) · 57 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright 2025 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import
math
import
warnings
import
numpy
as
np
import
PIL
.
Image
import
torch
import
torch
.
nn
.
functional
as
F
from
PIL
import
Image
,
ImageFilter
,
ImageOps
from
.
configuration_utils
import
ConfigMixin
,
register_to_config
from
.
utils
import
CONFIG_NAME
,
PIL_INTERPOLATION
,
deprecate
PipelineImageInput
=
(
PIL
.
Image
.
Image
|
np
.
ndarray
|
torch
.
Tensor
|
list
[
PIL
.
Image
.
Image
]
|
list
[
np
.
ndarray
]
|
list
[
torch
.
Tensor
]
)
PipelineDepthInput
=
PipelineImageInput
def
is_valid_image
(
image
)
->
bool
:
r"""
Checks if the input is a valid image.
A valid image can be:
- A `PIL.Image.Image`.
- A 2D or 3D `np.ndarray` or `torch.Tensor` (grayscale or color image).
Args:
image (`PIL.Image.Image | np.ndarray | torch.Tensor`):
The image to validate. It can be a PIL image, a NumPy array, or a torch tensor.
Returns:
`bool`:
`True` if the input is a valid image, `False` otherwise.
"""
return
isinstance
(
image
,
PIL
.
Image
.
Image
)
or
isinstance
(
image
, (
np
.
ndarray
,
torch
.
Tensor
))
and
image
.
ndim
in
(
2
,
3
)
def
is_valid_image_imagelist
(
images
):
r"""
Checks if the input is a valid image or list of images.
The input can be one of the following formats:
- A 4D tensor or numpy array (batch of images).
- A valid single image: `PIL.Image.Image`, 2D `np.ndarray` or `torch.Tensor` (grayscale image), 3D `np.ndarray` or
`torch.Tensor`.
- A list of valid images.
Args:
images (`np.ndarray | torch.Tensor | PIL.Image.Image | list`):
The image(s) to check. Can be a batch of images (4D tensor/array), a single image, or a list of valid
images.
Returns:
`bool`:
`True` if the input is valid, `False` otherwise.
"""
if
isinstance
(
images
, (
np
.
ndarray
,
torch
.
Tensor
))
and
images
.
ndim
==
4
:
return
True
elif
is_valid_image
(
images
):
return
True
elif
isinstance
(
images
,
list
):
return
all
(
is_valid_image
(
image
)
for
image
in
images
)
return
False
class
VaeImageProcessor
(
ConfigMixin
):
"""
Image processor for VAE.
Args:
do_resize (`bool`, *optional*, defaults to `True`):
Whether to downscale the image's (height, width) dimensions to multiples of `vae_scale_factor`. Can accept
`height` and `width` arguments from [`image_processor.VaeImageProcessor.preprocess`] method.
vae_scale_factor (`int`, *optional*, defaults to `8`):
VAE scale factor. If `do_resize` is `True`, the image is automatically resized to multiples of this factor.
resample (`str`, *optional*, defaults to `lanczos`):
Resampling filter to use when resizing the image.
do_normalize (`bool`, *optional*, defaults to `True`):
Whether to normalize the image to [-1,1].
do_binarize (`bool`, *optional*, defaults to `False`):
Whether to binarize the image to 0/1.
do_convert_rgb (`bool`, *optional*, defaults to be `False`):
Whether to convert the images to RGB format.
do_convert_grayscale (`bool`, *optional*, defaults to be `False`):
Whether to convert the images to grayscale format.
"""
config_name
=
CONFIG_NAME
@
register_to_config
def
__init__
(
self
,
do_resize
:
bool
=
True
,
vae_scale_factor
:
int
=
8
,
vae_latent_channels
:
int
=
4
,
resample
:
str
=
"lanczos"
,
reducing_gap
:
int
|
None
=
None
,
do_normalize
:
bool
=
True
,
do_binarize
:
bool
=
False
,
do_convert_rgb
:
bool
=
False
,
do_convert_grayscale
:
bool
=
False
,
):
super
().
__init__
()
if
do_convert_rgb
and
do_convert_grayscale
:
raise
ValueError
(
"`do_convert_rgb` and `do_convert_grayscale` can not both be set to `True`,"
" if you intended to convert the image into RGB format, please set `do_convert_grayscale = False`."
,
" if you intended to convert the image into grayscale format, please set `do_convert_rgb = False`"
,
)
@
staticmethod
def
numpy_to_pil
(
images
:
np
.
ndarray
)
->
list
[
PIL
.
Image
.
Image
]:
r"""
Convert a numpy image or a batch of images to a PIL image.
Args:
images (`np.ndarray`):
The image array to convert to PIL format.
Returns:
`list[PIL.Image.Image]`:
A list of PIL images.
"""
if
images
.
ndim
==
3
:
images
=
images
[
None
, ...]
images
=
(
images
*
255
).
round
().
astype
(
"uint8"
)
if
images
.
shape
[
-
1
]
==
1
:
# special case for grayscale (single channel) images
pil_images
=
[
Image
.
fromarray
(
image
.
squeeze
(),
mode
=
"L"
)
for
image
in
images
]
else
:
pil_images
=
[
Image
.
fromarray
(
image
)
for
image
in
images
]
return
pil_images
@
staticmethod
def
pil_to_numpy
(
images
:
list
[
PIL
.
Image
.
Image
]
|
PIL
.
Image
.
Image
)
->
np
.
ndarray
:
r"""
Convert a PIL image or a list of PIL images to NumPy arrays.
Args:
images (`PIL.Image.Image` or `list[PIL.Image.Image]`):
The PIL image or list of images to convert to NumPy format.
Returns:
`np.ndarray`:
A NumPy array representation of the images.
"""
if
not
isinstance
(
images
,
list
):
images
=
[
images
]
images
=
[
np
.
array
(
image
).
astype
(
np
.
float32
)
/
255.0
for
image
in
images
]
images
=
np
.
stack
(
images
,
axis
=
0
)
return
images
@
staticmethod
def
numpy_to_pt
(
images
:
np
.
ndarray
)
->
torch
.
Tensor
:
r"""
Convert a NumPy image to a PyTorch tensor.
Args:
images (`np.ndarray`):
The NumPy image array to convert to PyTorch format.
Returns:
`torch.Tensor`:
A PyTorch tensor representation of the images.
"""
if
images
.
ndim
==
3
:
images
=
images
[...,
None
]
images
=
torch
.
from_numpy
(
images
.
transpose
(
0
,
3
,
1
,
2
))
return
images
@
staticmethod
def
pt_to_numpy
(
images
:
torch
.
Tensor
)
->
np
.
ndarray
:
r"""
Convert a PyTorch tensor to a NumPy image.
Args:
images (`torch.Tensor`):
The PyTorch tensor to convert to NumPy format.
Returns:
`np.ndarray`:
A NumPy array representation of the images.
"""
images
=
images
.
cpu
().
permute
(
0
,
2
,
3
,
1
).
float
().
numpy
()
return
images
@
staticmethod
def
normalize
(
images
:
np
.
ndarray
|
torch
.
Tensor
)
->
np
.
ndarray
|
torch
.
Tensor
:
r"""
Normalize an image array to [-1,1].
Args:
images (`np.ndarray` or `torch.Tensor`):
The image array to normalize.
Returns:
`np.ndarray` or `torch.Tensor`:
The normalized image array.
"""
return
2.0
*
images
-
1.0
@
staticmethod
def
denormalize
(
images
:
np
.
ndarray
|
torch
.
Tensor
)
->
np
.
ndarray
|
torch
.
Tensor
:
r"""
Denormalize an image array to [0,1].
Args:
images (`np.ndarray` or `torch.Tensor`):
The image array to denormalize.
Returns:
`np.ndarray` or `torch.Tensor`:
The denormalized image array.
"""
return
(
images
*
0.5
+
0.5
).
clamp
(
0
,
1
)
@
staticmethod
def
convert_to_rgb
(
image
:
PIL
.
Image
.
Image
)
->
PIL
.
Image
.
Image
:
r"""
Converts a PIL image to RGB format.
Args:
image (`PIL.Image.Image`):
The PIL image to convert to RGB.
Returns:
`PIL.Image.Image`:
The RGB-converted PIL image.
"""
image
=
image
.
convert
(
"RGB"
)
return
image
@
staticmethod
def
convert_to_grayscale
(
image
:
PIL
.
Image
.
Image
)
->
PIL
.
Image
.
Image
:
r"""
Converts a given PIL image to grayscale.
Args:
image (`PIL.Image.Image`):
The input image to convert.
Returns:
`PIL.Image.Image`:
The image converted to grayscale.
"""
image
=
image
.
convert
(
"L"
)
return
image
@
staticmethod
def
blur
(
image
:
PIL
.
Image
.
Image
,
blur_factor
:
int
=
4
)
->
PIL
.
Image
.
Image
:
r"""
Applies Gaussian blur to an image.
Args:
image (`PIL.Image.Image`):
The PIL image to convert to grayscale.
Returns:
`PIL.Image.Image`:
The grayscale-converted PIL image.
"""
image
=
image
.
filter
(
ImageFilter
.
GaussianBlur
(
blur_factor
))
return
image
@
staticmethod
def
get_crop_region
(
mask_image
:
PIL
.
Image
.
Image
,
width
:
int
,
height
:
int
,
pad
=
0
):
r"""
Finds a rectangular region that contains all masked ares in an image, and expands region to match the aspect
ratio of the original image; for example, if user drew mask in a 128x32 region, and the dimensions for
processing are 512x512, the region will be expanded to 128x128.
Args:
mask_image (PIL.Image.Image): Mask image.
width (int): Width of the image to be processed.
height (int): Height of the image to be processed.
pad (int, optional): Padding to be added to the crop region. Defaults to 0.
Returns:
tuple: (x1, y1, x2, y2) represent a rectangular region that contains all masked ares in an image and
matches the original aspect ratio.
"""
mask_image
=
mask_image
.
convert
(
"L"
)
mask
=
np
.
array
(
mask_image
)
# 1. find a rectangular region that contains all masked ares in an image
h
,
w
=
mask
.
shape
crop_left
=
0
for
i
in
range
(
w
):
if
not
(
mask
[:,
i
]
==
0
).
all
():
break
crop_left
+=
1
crop_right
=
0
for
i
in
reversed
(
range
(
w
)):
if
not
(
mask
[:,
i
]
==
0
).
all
():
break
crop_right
+=
1
crop_top
=
0
for
i
in
range
(
h
):
if
not
(
mask
[
i
]
==
0
).
all
():
break
crop_top
+=
1
crop_bottom
=
0
for
i
in
reversed
(
range
(
h
)):
if
not
(
mask
[
i
]
==
0
).
all
():
break
crop_bottom
+=
1
# 2. add padding to the crop region
x1
,
y1
,
x2
,
y2
=
(
int
(
max
(
crop_left
-
pad
,
0
)),
int
(
max
(
crop_top
-
pad
,
0
)),
int
(
min
(
w
-
crop_right
+
pad
,
w
)),
int
(
min
(
h
-
crop_bottom
+
pad
,
h
)),
)
# 3. expands crop region to match the aspect ratio of the image to be processed
ratio_crop_region
=
(
x2
-
x1
)
/
(
y2
-
y1
)
ratio_processing
=
width
/
height
if
ratio_crop_region
>
ratio_processing
:
desired_height
=
(
x2
-
x1
)
/
ratio_processing
desired_height_diff
=
int
(
desired_height
-
(
y2
-
y1
))
y1
-=
desired_height_diff
//
2
y2
+=
desired_height_diff
-
desired_height_diff
//
2
if
y2
>=
mask_image
.
height
:
diff
=
y2
-
mask_image
.
height
y2
-=
diff
y1
-=
diff
if
y1
<
0
:
y2
-=
y1
y1
-=
y1
if
y2
>=
mask_image
.
height
:
y2
=
mask_image
.
height
else
:
desired_width
=
(
y2
-
y1
)
*
ratio_processing
desired_width_diff
=
int
(
desired_width
-
(
x2
-
x1
))
x1
-=
desired_width_diff
//
2
x2
+=
desired_width_diff
-
desired_width_diff
//
2
if
x2
>=
mask_image
.
width
:
diff
=
x2
-
mask_image
.
width
x2
-=
diff
x1
-=
diff
if
x1
<
0
:
x2
-=
x1
x1
-=
x1
if
x2
>=
mask_image
.
width
:
x2
=
mask_image
.
width
return
x1
,
y1
,
x2
,
y2
def
_resize_and_fill
(
self
,
image
:
PIL
.
Image
.
Image
,
width
:
int
,
height
:
int
,
)
->
PIL
.
Image
.
Image
:
r"""
Resize the image to fit within the specified width and height, maintaining the aspect ratio, and then center
the image within the dimensions, filling empty with data from image.
Args:
image (`PIL.Image.Image`):
The image to resize and fill.
width (`int`):
The width to resize the image to.
height (`int`):
The height to resize the image to.
Returns:
`PIL.Image.Image`:
The resized and filled image.
"""
ratio
=
width
/
height
src_ratio
=
image
.
width
/
image
.
height
src_w
=
width
if
ratio
<
src_ratio
else
image
.
width
*
height
//
image
.
height
src_h
=
height
if
ratio
>=
src_ratio
else
image
.
height
*
width
//
image
.
width
resized
=
image
.
resize
((
src_w
,
src_h
),
resample
=
PIL_INTERPOLATION
[
self
.
config
.
resample
])
res
=
Image
.
new
(
"RGB"
, (
width
,
height
))
res
.
paste
(
resized
,
box
=
(
width
//
2
-
src_w
//
2
,
height
//
2
-
src_h
//
2
))
if
ratio
<
src_ratio
:
fill_height
=
height
//
2
-
src_h
//
2
if
fill_height
>
0
:
res
.
paste
(
resized
.
resize
((
width
,
fill_height
),
box
=
(
0
,
0
,
width
,
0
)),
box
=
(
0
,
0
))
res
.
paste
(
resized
.
resize
((
width
,
fill_height
),
box
=
(
0
,
resized
.
height
,
width
,
resized
.
height
)),
box
=
(
0
,
fill_height
+
src_h
),
)
elif
ratio
>
src_ratio
:
fill_width
=
width
//
2
-
src_w
//
2
if
fill_width
>
0
:
res
.
paste
(
resized
.
resize
((
fill_width
,
height
),
box
=
(
0
,
0
,
0
,
height
)),
box
=
(
0
,
0
))
res
.
paste
(
resized
.
resize
((
fill_width
,
height
),
box
=
(
resized
.
width
,
0
,
resized
.
width
,
height
)),
box
=
(
fill_width
+
src_w
,
0
),
)
return
res
def
_resize_and_crop
(
self
,
image
:
PIL
.
Image
.
Image
,
width
:
int
,
height
:
int
,
)
->
PIL
.
Image
.
Image
:
r"""
Resize the image to fit within the specified width and height, maintaining the aspect ratio, and then center
the image within the dimensions, cropping the excess.
Args:
image (`PIL.Image.Image`):
The image to resize and crop.
width (`int`):
The width to resize the image to.
height (`int`):
The height to resize the image to.
Returns:
`PIL.Image.Image`:
The resized and cropped image.
"""
ratio
=
width
/
height
src_ratio
=
image
.
width
/
image
.
height
src_w
=
width
if
ratio
>
src_ratio
else
image
.
width
*
height
//
image
.
height
src_h
=
height
if
ratio
<=
src_ratio
else
image
.
height
*
width
//
image
.
width
resized
=
image
.
resize
((
src_w
,
src_h
),
resample
=
PIL_INTERPOLATION
[
self
.
config
.
resample
])
res
=
Image
.
new
(
"RGB"
, (
width
,
height
))
res
.
paste
(
resized
,
box
=
(
width
//
2
-
src_w
//
2
,
height
//
2
-
src_h
//
2
))
return
res
def
resize
(
self
,
image
:
PIL
.
Image
.
Image
|
np
.
ndarray
|
torch
.
Tensor
,
height
:
int
,
width
:
int
,
resize_mode
:
str
=
"default"
,
# "default", "fill", "crop"
)
->
PIL
.
Image
.
Image
|
np
.
ndarray
|
torch
.
Tensor
:
"""
Resize image.
Args:
image (`PIL.Image.Image`, `np.ndarray` or `torch.Tensor`):
The image input, can be a PIL image, numpy array or pytorch tensor.
height (`int`):
The height to resize to.
width (`int`):
The width to resize to.
resize_mode (`str`, *optional*, defaults to `default`):
The resize mode to use, can be one of `default` or `fill`. If `default`, will resize the image to fit
within the specified width and height, and it may not maintaining the original aspect ratio. If `fill`,
will resize the image to fit within the specified width and height, maintaining the aspect ratio, and
then center the image within the dimensions, filling empty with data from image. If `crop`, will resize
the image to fit within the specified width and height, maintaining the aspect ratio, and then center
the image within the dimensions, cropping the excess. Note that resize_mode `fill` and `crop` are only
supported for PIL image input.
Returns:
`PIL.Image.Image`, `np.ndarray` or `torch.Tensor`:
The resized image.
"""
if
resize_mode
!=
"default"
and
not
isinstance
(
image
,
PIL
.
Image
.
Image
):
raise
ValueError
(
f"Only PIL image input is supported for resize_mode
{
resize_mode
}
"
)
if
isinstance
(
image
,
PIL
.
Image
.
Image
):
if
resize_mode
==
"default"
:
image
=
image
.
resize
(
(
width
,
height
),
resample
=
PIL_INTERPOLATION
[
self
.
config
.
resample
],
reducing_gap
=
self
.
config
.
reducing_gap
,
)
elif
resize_mode
==
"fill"
:
image
=
self
.
_resize_and_fill
(
image
,
width
,
height
)
elif
resize_mode
==
"crop"
:
image
=
self
.
_resize_and_crop
(
image
,
width
,
height
)
else
:
raise
ValueError
(
f"resize_mode
{
resize_mode
}
is not supported"
)
elif
isinstance
(
image
,
torch
.
Tensor
):
image
=
torch
.
nn
.
functional
.
interpolate
(
image
,
size
=
(
height
,
width
),
)
elif
isinstance
(
image
,
np
.
ndarray
):
image
=
self
.
numpy_to_pt
(
image
)
image
=
torch
.
nn
.
functional
.
interpolate
(
image
,
size
=
(
height
,
width
),
)
image
=
self
.
pt_to_numpy
(
image
)
return
image
def
binarize
(
self
,
image
:
PIL
.
Image
.
Image
)
->
PIL
.
Image
.
Image
:
"""
Create a mask.
Args:
image (`PIL.Image.Image`):
The image input, should be a PIL image.
Returns:
`PIL.Image.Image`:
The binarized image. Values less than 0.5 are set to 0, values greater than 0.5 are set to 1.
"""
image
[
image
<
0.5
]
=
0
image
[
image
>=
0.5
]
=
1
return
image
def
_denormalize_conditionally
(
self
,
images
:
torch
.
Tensor
,
do_denormalize
:
list
[
bool
]
|
None
=
None
)
->
torch
.
Tensor
:
r"""
Denormalize a batch of images based on a condition list.
Args:
images (`torch.Tensor`):
The input image tensor.
do_denormalize (`Optional[list[bool]`, *optional*, defaults to `None`):
A list of booleans indicating whether to denormalize each image in the batch. If `None`, will use the
value of `do_normalize` in the `VaeImageProcessor` config.
"""
if
do_denormalize
is
None
:
return
self
.
denormalize
(
images
)
if
self
.
config
.
do_normalize
else
images
return
torch
.
stack
(
[
self
.
denormalize
(
images
[
i
])
if
do_denormalize
[
i
]
else
images
[
i
]
for
i
in
range
(
images
.
shape
[
0
])]
)
def
get_default_height_width
(
self
,
image
:
PIL
.
Image
.
Image
|
np
.
ndarray
|
torch
.
Tensor
,
height
:
int
|
None
=
None
,
width
:
int
|
None
=
None
,
)
->
tuple
[
int
,
int
]:
r"""
Returns the height and width of the image, downscaled to the next integer multiple of `vae_scale_factor`.
Args:
image (`PIL.Image.Image | np.ndarray | torch.Tensor`):
The image input, which can be a PIL image, NumPy array, or PyTorch tensor. If it is a NumPy array, it
should have shape `[batch, height, width]` or `[batch, height, width, channels]`. If it is a PyTorch
tensor, it should have shape `[batch, channels, height, width]`.
height (`int | None`, *optional*, defaults to `None`):
The height of the preprocessed image. If `None`, the height of the `image` input will be used.
width (`int | None`, *optional*, defaults to `None`):
The width of the preprocessed image. If `None`, the width of the `image` input will be used.
Returns:
`tuple[int, int]`:
A tuple containing the height and width, both resized to the nearest integer multiple of
`vae_scale_factor`.
"""
if
height
is
None
:
if
isinstance
(
image
,
PIL
.
Image
.
Image
):
height
=
image
.
height
elif
isinstance
(
image
,
torch
.
Tensor
):
height
=
image
.
shape
[
2
]
else
:
height
=
image
.
shape
[
1
]
if
width
is
None
:
if
isinstance
(
image
,
PIL
.
Image
.
Image
):
width
=
image
.
width
elif
isinstance
(
image
,
torch
.
Tensor
):
width
=
image
.
shape
[
3
]
else
:
width
=
image
.
shape
[
2
]
width
,
height
=
(
x
-
x
%
self
.
config
.
vae_scale_factor
for
x
in
(
width
,
height
)
)
# resize to integer multiple of vae_scale_factor
return
height
,
width
def
preprocess
(
self
,
image
:
PipelineImageInput
,
height
:
int
|
None
=
None
,
width
:
int
|
None
=
None
,
resize_mode
:
str
=
"default"
,
# "default", "fill", "crop"
crops_coords
:
tuple
[
int
,
int
,
int
,
int
]
|
None
=
None
,
)
->
torch
.
Tensor
:
"""
Preprocess the image input.
Args:
image (`PipelineImageInput`):
The image input, accepted formats are PIL images, NumPy arrays, PyTorch tensors; Also accept list of
supported formats.
height (`int`, *optional*):
The height in preprocessed image. If `None`, will use the `get_default_height_width()` to get default
height.
width (`int`, *optional*):
The width in preprocessed. If `None`, will use get_default_height_width()` to get the default width.
resize_mode (`str`, *optional*, defaults to `default`):
The resize mode, can be one of `default` or `fill`. If `default`, will resize the image to fit within
the specified width and height, and it may not maintaining the original aspect ratio. If `fill`, will
resize the image to fit within the specified width and height, maintaining the aspect ratio, and then
center the image within the dimensions, filling empty with data from image. If `crop`, will resize the
image to fit within the specified width and height, maintaining the aspect ratio, and then center the
image within the dimensions, cropping the excess. Note that resize_mode `fill` and `crop` are only
supported for PIL image input.
crops_coords (`list[tuple[int, int, int, int]]`, *optional*, defaults to `None`):
The crop coordinates for each image in the batch. If `None`, will not crop the image.
Returns:
`torch.Tensor`:
The preprocessed image.
"""
supported_formats
=
(
PIL
.
Image
.
Image
,
np
.
ndarray
,
torch
.
Tensor
)
# Expand the missing dimension for 3-dimensional pytorch tensor or numpy array that represents grayscale image
if
self
.
config
.
do_convert_grayscale
and
isinstance
(
image
, (
torch
.
Tensor
,
np
.
ndarray
))
and
image
.
ndim
==
3
:
if
isinstance
(
image
,
torch
.
Tensor
):
# if image is a pytorch tensor could have 2 possible shapes:
# 1. batch x height x width: we should insert the channel dimension at position 1
# 2. channel x height x width: we should insert batch dimension at position 0,
# however, since both channel and batch dimension has same size 1, it is same to insert at position 1
# for simplicity, we insert a dimension of size 1 at position 1 for both cases
image
=
image
.
unsqueeze
(
1
)
else
:
# if it is a numpy array, it could have 2 possible shapes:
# 1. batch x height x width: insert channel dimension on last position
# 2. height x width x channel: insert batch dimension on first position
if
image
.
shape
[
-
1
]
==
1
:
image
=
np
.
expand_dims
(
image
,
axis
=
0
)
else
:
image
=
np
.
expand_dims
(
image
,
axis
=
-
1
)
if
isinstance
(
image
,
list
)
and
isinstance
(
image
[
0
],
np
.
ndarray
)
and
image
[
0
].
ndim
==
4
:
warnings
.
warn
(
"Passing `image` as a list of 4d np.ndarray is deprecated."
"Please concatenate the list along the batch dimension and pass it as a single 4d np.ndarray"
,
FutureWarning
,
)
image
=
np
.
concatenate
(
image
,
axis
=
0
)
if
isinstance
(
image
,
list
)
and
isinstance
(
image
[
0
],
torch
.
Tensor
)
and
image
[
0
].
ndim
==
4
:
warnings
.
warn
(
"Passing `image` as a list of 4d torch.Tensor is deprecated."
"Please concatenate the list along the batch dimension and pass it as a single 4d torch.Tensor"
,
FutureWarning
,
)
image
=
torch
.
cat
(
image
,
axis
=
0
)
if
not
is_valid_image_imagelist
(
image
):
raise
ValueError
(
f"Input is in incorrect format. Currently, we only support
{
', '
.
join
(
str
(
x
)
for
x
in
supported_formats
)
}
"
)
if
not
isinstance
(
image
,
list
):
image
=
[
image
]
if
isinstance
(
image
[
0
],
PIL
.
Image
.
Image
):
if
crops_coords
is
not
None
:
image
=
[
i
.
crop
(
crops_coords
)
for
i
in
image
]
if
self
.
config
.
do_resize
:
height
,
width
=
self
.
get_default_height_width
(
image
[
0
],
height
,
width
)
image
=
[
self
.
resize
(
i
,
height
,
width
,
resize_mode
=
resize_mode
)
for
i
in
image
]
if
self
.
config
.
do_convert_rgb
:
image
=
[
self
.
convert_to_rgb
(
i
)
for
i
in
image
]
elif
self
.
config
.
do_convert_grayscale
:
image
=
[
self
.
convert_to_grayscale
(
i
)
for
i
in
image
]
image
=
self
.
pil_to_numpy
(
image
)
# to np
image
=
self
.
numpy_to_pt
(
image
)
# to pt
elif
isinstance
(
image
[
0
],
np
.
ndarray
):
image
=
np
.
concatenate
(
image
,
axis
=
0
)
if
image
[
0
].
ndim
==
4
else
np
.
stack
(
image
,
axis
=
0
)
image
=
self
.
numpy_to_pt
(
image
)
height
,
width
=
self
.
get_default_height_width
(
image
,
height
,
width
)
if
self
.
config
.
do_resize
:
image
=
self
.
resize
(
image
,
height
,
width
)
elif
isinstance
(
image
[
0
],
torch
.
Tensor
):
image
=
torch
.
cat
(
image
,
axis
=
0
)
if
image
[
0
].
ndim
==
4
else
torch
.
stack
(
image
,
axis
=
0
)
if
self
.
config
.
do_convert_grayscale
and
image
.
ndim
==
3
:
image
=
image
.
unsqueeze
(
1
)
channel
=
image
.
shape
[
1
]
# don't need any preprocess if the image is latents
if
channel
==
self
.
config
.
vae_latent_channels
:
return
image
height
,
width
=
self
.
get_default_height_width
(
image
,
height
,
width
)
if
self
.
config
.
do_resize
:
image
=
self
.
resize
(
image
,
height
,
width
)
# expected range [0,1], normalize to [-1,1]
do_normalize
=
self
.
config
.
do_normalize
if
do_normalize
and
image
.
min
()
<
0
:
warnings
.
warn
(
"Passing `image` as torch tensor with value range in [-1,1] is deprecated. The expected value range for image tensor is [0,1] "
f"when passing as pytorch tensor or numpy Array. You passed `image` with value range [
{
image
.
min
()
}
,
{
image
.
max
()
}
]"
,
FutureWarning
,
)
do_normalize
=
False
if
do_normalize
:
image
=
self
.
normalize
(
image
)
if
self
.
config
.
do_binarize
:
image
=
self
.
binarize
(
image
)
return
image
def
postprocess
(
self
,
image
:
torch
.
Tensor
,
output_type
:
str
=
"pil"
,
do_denormalize
:
list
[
bool
]
|
None
=
None
,
)
->
PIL
.
Image
.
Image
|
np
.
ndarray
|
torch
.
Tensor
:
"""
Postprocess the image output from tensor to `output_type`.
Args:
image (`torch.Tensor`):
The image input, should be a pytorch tensor with shape `B x C x H x W`.
output_type (`str`, *optional*, defaults to `pil`):
The output type of the image, can be one of `pil`, `np`, `pt`, `latent`.
do_denormalize (`list[bool]`, *optional*, defaults to `None`):
Whether to denormalize the image to [0,1]. If `None`, will use the value of `do_normalize` in the
`VaeImageProcessor` config.
Returns:
`PIL.Image.Image`, `np.ndarray` or `torch.Tensor`:
The postprocessed image.
"""
if
not
isinstance
(
image
,
torch
.
Tensor
):
raise
ValueError
(
f"Input for postprocessing is in incorrect format:
{
type
(
image
)
}
. We only support pytorch tensor"
)
if
output_type
not
in
[
"latent"
,
"pt"
,
"np"
,
"pil"
]:
deprecation_message
=
(
f"the output_type
{
output_type
}
is outdated and has been set to `np`. Please make sure to set it to one of these instead: "
"`pil`, `np`, `pt`, `latent`"
)
deprecate
(
"Unsupported output_type"
,
"1.0.0"
,
deprecation_message
,
standard_warn
=
False
)
output_type
=
"np"
if
output_type
==
"latent"
:
return
image
image
=
self
.
_denormalize_conditionally
(
image
,
do_denormalize
)
if
output_type
==
"pt"
:
return
image
image
=
self
.
pt_to_numpy
(
image
)
if
output_type
==
"np"
:
return
image
if
output_type
==
"pil"
:
return
self
.
numpy_to_pil
(
image
)
def
apply_overlay
(
self
,
mask
:
PIL
.
Image
.
Image
,
init_image
:
PIL
.
Image
.
Image
,
image
:
PIL
.
Image
.
Image
,
crop_coords
:
tuple
[
int
,
int
,
int
,
int
]
|
None
=
None
,
)
->
PIL
.
Image
.
Image
:
r"""
Applies an overlay of the mask and the inpainted image on the original image.
Args:
mask (`PIL.Image.Image`):
The mask image that highlights regions to overlay.
init_image (`PIL.Image.Image`):
The original image to which the overlay is applied.
image (`PIL.Image.Image`):
The image to overlay onto the original.
crop_coords (`tuple[int, int, int, int]`, *optional*):
Coordinates to crop the image. If provided, the image will be cropped accordingly.
Returns:
`PIL.Image.Image`:
The final image with the overlay applied.
"""
width
,
height
=
init_image
.
width
,
init_image
.
height
init_image_masked
=
PIL
.
Image
.
new
(
"RGBa"
, (
width
,
height
))
init_image_masked
.
paste
(
init_image
.
convert
(
"RGBA"
).
convert
(
"RGBa"
),
mask
=
ImageOps
.
invert
(
mask
.
convert
(
"L"
)))
init_image_masked
=
init_image_masked
.
convert
(
"RGBA"
)
if
crop_coords
is
not
None
:
x
,
y
,
x2
,
y2
=
crop_coords
w
=
x2
-
x
h
=
y2
-
y
base_image
=
PIL
.
Image
.
new
(
"RGBA"
, (
width
,
height
))
image
=
self
.
resize
(
image
,
height
=
h
,
width
=
w
,
resize_mode
=
"crop"
)
base_image
.
paste
(
image
, (
x
,
y
))
image
=
base_image
.
convert
(
"RGB"
)
image
=
image
.
convert
(
"RGBA"
)
image
.
alpha_composite
(
init_image_masked
)
image
=
image
.
convert
(
"RGB"
)
return
image
class
InpaintProcessor
(
ConfigMixin
):
"""
Image processor for inpainting image and mask.
"""
config_name
=
CONFIG_NAME
@
register_to_config
def
__init__
(
self
,
do_resize
:
bool
=
True
,
vae_scale_factor
:
int
=
8
,
vae_latent_channels
:
int
=
4
,
resample
:
str
=
"lanczos"
,
reducing_gap
:
int
|
None
=
None
,
do_normalize
:
bool
=
True
,
do_binarize
:
bool
=
False
,
do_convert_grayscale
:
bool
=
False
,
mask_do_normalize
:
bool
=
False
,
mask_do_binarize
:
bool
=
True
,
mask_do_convert_grayscale
:
bool
=
True
,
):
super
().
__init__
()
self
.
_image_processor
=
VaeImageProcessor
(
do_resize
=
do_resize
,
vae_scale_factor
=
vae_scale_factor
,
vae_latent_channels
=
vae_latent_channels
,
resample
=
resample
,
reducing_gap
=
reducing_gap
,
do_normalize
=
do_normalize
,
do_binarize
=
do_binarize
,
do_convert_grayscale
=
do_convert_grayscale
,
)
self
.
_mask_processor
=
VaeImageProcessor
(
do_resize
=
do_resize
,
vae_scale_factor
=
vae_scale_factor
,
vae_latent_channels
=
vae_latent_channels
,
resample
=
resample
,
reducing_gap
=
reducing_gap
,
do_normalize
=
mask_do_normalize
,
do_binarize
=
mask_do_binarize
,
do_convert_grayscale
=
mask_do_convert_grayscale
,
)
def
preprocess
(
self
,
image
:
PIL
.
Image
.
Image
,
mask
:
PIL
.
Image
.
Image
|
None
=
None
,
height
:
int
|
None
=
None
,
width
:
int
|
None
=
None
,
padding_mask_crop
:
int
|
None
=
None
,
)
->
tuple
[
torch
.
Tensor
,
torch
.
Tensor
]:
"""
Preprocess the image and mask.
"""
if
mask
is
None
and
padding_mask_crop
is
not
None
:
raise
ValueError
(
"mask must be provided if padding_mask_crop is provided"
)
# if mask is None, same behavior as regular image processor
if
mask
is
None
:
return
self
.
_image_processor
.
preprocess
(
image
,
height
=
height
,
width
=
width
)
if
padding_mask_crop
is
not
None
:
crops_coords
=
self
.
_image_processor
.
get_crop_region
(
mask
,
width
,
height
,
pad
=
padding_mask_crop
)
resize_mode
=
"fill"
else
:
crops_coords
=
None
resize_mode
=
"default"
processed_image
=
self
.
_image_processor
.
preprocess
(
image
,
height
=
height
,
width
=
width
,
crops_coords
=
crops_coords
,
resize_mode
=
resize_mode
,
)
processed_mask
=
self
.
_mask_processor
.
preprocess
(
mask
,
height
=
height
,
width
=
width
,
resize_mode
=
resize_mode
,
crops_coords
=
crops_coords
,
)
if
crops_coords
is
not
None
:
postprocessing_kwargs
=
{
"crops_coords"
:
crops_coords
,
"original_image"
:
image
,
"original_mask"
:
mask
,
}
else
:
postprocessing_kwargs
=
{
"crops_coords"
:
None
,
"original_image"
:
None
,
"original_mask"
:
None
,
}
return
processed_image
,
processed_mask
,
postprocessing_kwargs
def
postprocess
(
self
,
image
:
torch
.
Tensor
,
output_type
:
str
=
"pil"
,
original_image
:
PIL
.
Image
.
Image
|
None
=
None
,
original_mask
:
PIL
.
Image
.
Image
|
None
=
None
,
crops_coords
:
tuple
[
int
,
int
,
int
,
int
]
|
None
=
None
,
)
->
tuple
[
PIL
.
Image
.
Image
,
PIL
.
Image
.
Image
]:
"""
Postprocess the image, optionally apply mask overlay
"""
image
=
self
.
_image_processor
.
postprocess
(
image
,
output_type
=
output_type
,
)
# optionally apply the mask overlay
if
crops_coords
is
not
None
and
(
original_image
is
None
or
original_mask
is
None
):
raise
ValueError
(
"original_image and original_mask must be provided if crops_coords is provided"
)
elif
crops_coords
is
not
None
and
output_type
!=
"pil"
:
raise
ValueError
(
"output_type must be 'pil' if crops_coords is provided"
)
elif
crops_coords
is
not
None
:
image
=
[
self
.
_image_processor
.
apply_overlay
(
original_mask
,
original_image
,
i
,
crops_coords
)
for
i
in
image
]
return
image
class
VaeImageProcessorLDM3D
(
VaeImageProcessor
):
"""
Image processor for VAE LDM3D.
Args:
do_resize (`bool`, *optional*, defaults to `True`):
Whether to downscale the image's (height, width) dimensions to multiples of `vae_scale_factor`.
vae_scale_factor (`int`, *optional*, defaults to `8`):
VAE scale factor. If `do_resize` is `True`, the image is automatically resized to multiples of this factor.
resample (`str`, *optional*, defaults to `lanczos`):
Resampling filter to use when resizing the image.
do_normalize (`bool`, *optional*, defaults to `True`):
Whether to normalize the image to [-1,1].
"""
config_name
=
CONFIG_NAME
@
register_to_config
def
__init__
(
self
,
do_resize
:
bool
=
True
,
vae_scale_factor
:
int
=
8
,
resample
:
str
=
"lanczos"
,
do_normalize
:
bool
=
True
,
):
super
().
__init__
()
@
staticmethod
def
numpy_to_pil
(
images
:
np
.
ndarray
)
->
list
[
PIL
.
Image
.
Image
]:
r"""
Convert a NumPy image or a batch of images to a list of PIL images.
Args:
images (`np.ndarray`):
View remainder of file in raw view
Back
|
FazBrowse Home
|
New Git URL