├── .gitignore ├── LICENSE.txt ├── README.md ├── ReleaseNote.md ├── docs ├── en │ ├── bmab.md │ ├── manual.md │ ├── postprocess.md │ └── preprocess.md ├── kr │ ├── api.md │ ├── bmab.md │ ├── manual.md │ ├── postprocess.md │ └── preprocess.md └── masking.md ├── filter ├── Put filter file here.txt ├── basic.py └── vintage.py ├── install.py ├── models └── Put model file here.txt ├── requirements.txt ├── resources └── preset │ ├── 3girls.json │ ├── Put config file here.txt │ ├── example.json │ ├── example2.json │ ├── hand.json │ └── hand2.json ├── scripts └── sd_webui_bmab.py └── sd_bmab ├── __init__.py ├── base ├── __init__.py ├── cache.py ├── common.py ├── context.py ├── detectorbase.py ├── filter.py ├── maskbase.py ├── process.py ├── processorbase.py └── sam.py ├── bmab.py ├── compat.py ├── constants.py ├── controlnet.py ├── detectors ├── __init__.py ├── anything.py ├── detector.py ├── face.py ├── hand.py └── person.py ├── external ├── __init__.py ├── groundingdino │ └── grdino.py ├── iclight │ ├── __init__.py │ ├── bmabiclight.py │ ├── briarmbg.py │ ├── iclightbg.py │ └── iclightnm.py ├── kohyahiresfix │ └── __init__.py └── lama │ ├── __init__.py │ ├── config.yaml │ └── saicinpainting │ ├── __init__.py │ ├── training │ ├── __init__.py │ ├── data │ │ ├── __init__.py │ │ └── masks.py │ ├── losses │ │ ├── __init__.py │ │ ├── adversarial.py │ │ ├── constants.py │ │ ├── distance_weighting.py │ │ ├── feature_matching.py │ │ ├── perceptual.py │ │ ├── segmentation.py │ │ └── style_loss.py │ ├── modules │ │ ├── __init__.py │ │ ├── base.py │ │ ├── depthwise_sep_conv.py │ │ ├── fake_fakes.py │ │ ├── ffc.py │ │ ├── multidilated_conv.py │ │ ├── multiscale.py │ │ ├── pix2pixhd.py │ │ ├── spatial_transform.py │ │ └── squeeze_excitation.py │ ├── trainers │ │ ├── __init__.py │ │ ├── base.py │ │ └── default.py │ └── visualizers │ │ ├── __init__.py │ │ ├── base.py │ │ ├── colors.py │ │ ├── directory.py │ │ └── noop.py │ └── utils.py ├── masking ├── __init__.py ├── sam.py └── sam_hq.py ├── parameters.py ├── pipeline ├── __init__.py ├── internal │ ├── __init__.py │ ├── intermediate.py │ └── internalpipe.py └── post │ ├── __init__.py │ └── mainpipe.py ├── processors ├── __init__.py ├── basic │ ├── __init__.py │ ├── blend.py │ ├── edge.py │ ├── final.py │ ├── iclight.py │ ├── img2imgmasking.py │ ├── intermediate.py │ └── preprocessfilter.py ├── controlnet │ ├── __init__.py │ ├── ipadapter.py │ ├── noise.py │ └── pose.py ├── detailer │ ├── __init__.py │ ├── face.py │ ├── hand.py │ └── person.py ├── postprocess │ ├── __init__.py │ ├── finalfilter.py │ ├── inpaint.py │ ├── inpaintlama.py │ ├── upscaleafterprocess.py │ ├── upscalebeforeprocess.py │ └── watermark.py ├── preprocess │ ├── __init__.py │ ├── preprocessfilter.py │ ├── pretraining.py │ ├── refiner.py │ ├── resample.py │ └── resize.py └── utils │ ├── __init__.py │ ├── checkpoint.py │ ├── filesave.py │ └── modelswitch.py ├── sd_override ├── __init__.py ├── img2img.py ├── samper.py ├── sd_models.py └── txt2img.py ├── ui.py └── util ├── __init__.py └── installhelper.py /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__/ 2 | cache/ 3 | -------------------------------------------------------------------------------- /ReleaseNote.md: -------------------------------------------------------------------------------- 1 | ### v24.05.12 2 | 3 | * New Feature 4 | * ICLight 5 | * Style 추가했습니다. 6 | * intensive : IC-Light original process 7 | * less intensive : 과도한 적용을 줄임 8 | * normal : 배경을 최대한 살리면서 lighting 적용 9 | * soft : 배경을 최대한 살림 10 | * Face : 얼굴 부분을 강조하도록 검출해서 조명으로 강조 11 | * Person : 사람을 찾아 조명으로 강조 12 | * Cache 13 | * 디렉토리가 존재하지 않으면 생성 14 | * Pretraining 15 | * 입력된 폭, 높이가 아니라 실제 이미지의 크기를 사용하도록 수정 16 | 17 | ### v24.05.12 18 | 19 | * New Feature 20 | * ICLight : https://github.com/lllyasviel/IC-Light 21 | * ICLight 기능을 제어할 수 있도록 통합했습니다. 22 | * 메모리 사용량이 많습니다. 23 | * Enable ICLight before upscale 끄면 1200*1800을 4090으로도 어렵습니다. 24 | * Process 작업전 수행되는 preprocess filter가 추가되었습니다. 25 | * Code Fix 26 | * Pose Default 값을 변경했습니다. 27 | * cache를 통합 제어하도록 변경했습니다. 28 | 29 | ### v24.05.07 30 | 31 | * New Feature 32 | * Installer : groudingdino for CUDA support. 33 | * Resample : fix FakeControlNet 34 | * Refactoring : multiple controlnet support 35 | 36 | 37 | ### v24.05.01 38 | 39 | * New Feature 40 | * ControlNet IpAdapter에서 하위 디렉토리를 만들면 하위 디렉토리 안에서 이미지를 적용할 수 있도록 개선했습니다. 41 | * Random으로 지정하면 전체 이미지에서 선택합니다. 42 | * 저장된 정보를 불러왔을때 Pose, IpAdapter 이미지가 표시됩니다. 43 | 44 | * ### v24.05.01 45 | 46 | * New Feature 47 | * ControlNet IpAdapter 기능을 제공합니다. 48 | * sd-webui-controlnet을 이용하여 제공하며, 랜덤하게 이미지를 적용할 수 있도록 개선했습니다. 49 | * Setting > BMAB에 현재 ControlNet IpAdapter의 model을 적어주셔야 합니다. 50 | * 기본적으로 모델은 default로 적용해두었습니다. 51 | 52 | * Code Fix 53 | * Setting에 Additional Checkpoint Path이 sd-webui 1.8에서 동작하지 않아, 이런 경우 빈칸으로 두면 오류가 발생하지 않도록 수정했습니다. 54 | * Person, Face 의 prompt가 hires.fix가 켜져있다면 hires.fix 것을 사용함. 55 | 56 | ### v24.04.30 57 | 58 | * New Feature 59 | * Person Detailer에 checkpoint, vae, sampler, scheduler 및 steps 추가 60 | * Setting에 Additional Checkpoint Path 디렉토리를 입력하면 두 디렉토리 내용을 병합하여 전체적 적용됨. 61 | 62 | * Code Fix 63 | * Person, Face 의 prompt가 hires.fix가 켜져있다면 hires.fix 것을 사용함. 64 | 65 | ### v24.04.26 66 | 67 | * Code Fix 68 | * Pretraining 에 checkpoint, vae 선택 추가 69 | * checkpoint, vae, filter, pose 등에 대한 fresh 버튼을 삭제하고 하나로 통합 70 | * vintage filter 추가 71 | 72 | 73 | ### v24.04.20 74 | 75 | * New Features 76 | * Pose 77 | * Face only 옵션 추가 78 | * Pose를 선택할 수 있도록 추가 79 | 80 | ### v24.04.17 81 | 82 | * New Features 83 | * Pose 84 | * sd-webui-bmab/pose 디렉토리에 있는 이미지를 이용하여 openpose를 동작한다. 85 | * seed에 맞춰서 임의이 순서로 동작한다. 86 | * Face 87 | * CheckPoint를 선택할 수 있도록 추가 88 | * Pretraining 89 | * Filter를 선택할 수 있도록 추가 90 | * Refiner 91 | * VAE를 선택할 수 있도록 추가 92 | * Noise 93 | * Both, Low res fix, High res fix 추가. 94 | 95 | ### v24.04.16 96 | 97 | * Code Fix 98 | * 1.9.0 지원 99 | * 1.8.0 하위 호환 100 | * CheckPoint 관련 전반 수정 101 | * basicsr 설치 오류 수정 102 | 103 | ### v24.04.05 104 | 105 | * Code Fix 106 | * 1.8.0 설치시에 동작하지 않는 버그를 수정했습니다. 107 | * preprocess filter 분리 108 | * hires.fix - upscale 시에 필터가 정확하게 적용되지 않는 버그 수정 109 | * ControlNet Noise 사용시에 그레이 이미지를 사용하도록 수정 110 | * 그 외 몇가지 코드 리펙토링 111 | 112 | ### v23.11.30.0 113 | 114 | * Code Fix 115 | * Support BMAB DR 116 | * Img2img rollback 117 | 118 | 119 | ### v23.11.28.0 120 | 121 | * New Feature 122 | * Kohya Hires.fix 123 | * Preprocess에서 Kohya Hires.fix기능을 추가하였습니다. 124 | * 이 기능을 사용할때 sd 1.5 기준 1024x1025, 1536x1536, 1024x1536, 1536x1024일 경우가 가장 잘나옵니다. 125 | * 이 기능은 원작자가 SDXL을 위해서 만든 기능입니다. 굳이 sd 1.5에서 사용할 필요는 없습니다. 126 | 127 | 128 | ### v23.11.27.0 129 | 130 | * New Feature 131 | * Stop generating gracefully 132 | * BMAB 프로세스가 완료되면 batch가 남아있더라도 종료하는 기능. 133 | * 이미지 생성 중간에 Interrupt 를 눌러서 종료가 아니라, 이미지 생성이 완료되면 종료된다. 134 | * 'Enable BMAB' 오른쪽에 작게 Stop이 있다. 135 | * FinalFilter 136 | * 최종 이미지에 수정을 가할 수 있도록 필터를 적용할 수 있도록 하는 기능. 137 | * 필터는 구현해서 filter에 넣으면 확인할 수 있다. 138 | * BugFix 139 | * Img2Img와 openpose 사용시에 inpaint area 적용되지 않는 문제 수정. 140 | * 약간의 코드 리펙토링 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /docs/en/manual.md: -------------------------------------------------------------------------------- 1 | 2 | ## Quick Test 3 | 4 | Enable을 체크하고 Config Tab에서 Preset "example"을 선택합니다. 5 | 6 | contrast: 1.2 7 | brightness: 0.9 8 | sharpeness: 1.5 9 | 10 | Edge enhancement 적용 11 | Face Detailing 적용 12 | Resize by person 적용 13 | 14 | 15 | 16 | ## 기본 옵션 17 | 18 | Enabled (VERSION): 기능을 켜고 끌 수 있습니다. 19 | 20 | ### Resize and fill override 21 | 22 | Img2Img를 수행하는 경우 "Resize and fill" 을 선택하게 되면 23 | 통상 좌우, 상하로 늘어나거나 비율이 같다면 그대로 크기만 변경됩니다. 24 | 25 | Enabled 된 상태에서는 항상 이미지가 아래에 위치하고, 26 | 왼쪽, 오른쪽, 윗쪽으로 비율에 맞게 늘어납니다. 27 | 28 | 인물의 윗쪽으로 여백이 없는 경우에 적용하면 효과적입니다. 29 | 너무 크게 늘리게 되면 좋은 결과를 얻기 힘듭니다. 30 | 대략 1.1, 1.2 정도 스케일에서 사용하시길 권장합니다. 31 | 32 |

33 | 34 | 35 |

36 | 37 |
38 |
39 |
40 | 41 | # Preprocess 42 | 43 | 본 이미지 변경을 하기 전에 사전 처리 과정을 수행합니다. 44 | 조건에 따라 hires.fix 과정에 개입할 수도 있습니다. 45 | 46 | Preprocess 47 | 48 | # BMAB 49 | 50 | Person, Hand, Face detailer를 수행하거나, 이미지 합성 혹은 노이즈 추가등의 기능을 수행합니다. 51 | 52 | bmab 53 | 54 | # Postprocess 55 | 56 | 이미지 처리가 완료된 이후, 인물의 크기에 따라 배경을 확장하거나 upscale을 할 수 있습니다. 57 | 58 | Postprocess 59 | -------------------------------------------------------------------------------- /docs/en/postprocess.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # PostProcessing 4 | 5 | ## Resize 6 | 7 | ### Resize by person 8 | 9 | 그림 속 인물중 가장 신장이 큰 사람의 길이와 그림 높이의 비율이 설정값을 넘어가면 비율을 설정값로 맞추는 기능입니다. 10 | 설정값이 0.90이고 인물의 전체 길이: 그림 높이의 비율이 0.95라고 한다면 11 | 배경을 늘려서 인물의 비율이 0.90이 되도록 합니다. 12 | 배경은 왼쪽, 오른쪽, 위쪽으로 늘어납니다. 13 | 14 | 15 | 이 기능은 2가지 방법을 제공하는데 다음과 같습니다. 16 | 17 | #### Inpaint 18 | 19 | Face Detailing과 같은 방법으로 이미지가 완전히 생성되고 난 후에 주변부를 확장합니다. 20 | 이때 이미 생성된 이미지를 훼손하지 않고 주변부만 확장하기 때문에 직관적으로 확인이 가능합니다. 21 | 가장 빠르고 효과적으로 추천합니다. 22 | 23 | #### Inpaint + lama 24 | 25 | Inpaint와 같은 방법인데 BMAB에서 ControlNet을 불러서 Inpaint+lama를 이용해서 동작합니다. 26 | 이미지가 생성되고나서 디테일링 시작하기 전에 img2img를 이용하여 배경을 확장하여 전체적으로 인물을 작게 만드는 효과가 있습니다. 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 | 43 | 이 두가지 방법은 생성된 이미지를 축소하기만 할뿐 훼손하지 않습니다. 44 | 이것이 Resize intermediate와 다른점입니다. 45 | 46 | 47 | ## Upscaler 48 | 49 | 최종적으로 이미지가 완성되고 난 이후에 이미지를 upscaler를 이용하여 크게 만듭니다. 50 | 51 | 52 | #### Enable upscale at final stage 53 | 54 | 이미지 생성이 완료되고 난 이후에 Upscale을 수행합니다. 55 | 960x540으로 생성하고 hires.fix를 x2로 하면 1920x1080 이미지가 나오는데 56 | 여기서 Upscale을 x2를 하면 4K 이미지가 나오게 됩니다. 57 | 58 | #### Detailing after upscale 59 | 60 | 이 옵션을 설정하면 위에서 언급한 Person, Face, Hand 에 대한 detailing을 upscale 이후에 수행합니다. 61 | 62 | #### Upscale Ratio 63 | 64 | 이미지를 얼마나 upscale할지 결정합니다. 65 | 66 | 67 | -------------------------------------------------------------------------------- /docs/kr/api.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # API 5 | 6 | Stable diffusion webui의 API를 이용하여 이미지를 생성하는 경우 아래와 같이 BMAB를 사용하여 API Call을 할 수 있습니다. 7 | 8 | 9 | ```python 10 | import requests 11 | import json 12 | import base64 13 | 14 | 15 | prompt = ''' 16 | 1girl 17 | ''' 18 | negative_prompt = '(worst quality, low quality:1.4),' 19 | 20 | txt2img = { 21 | 'prompt': prompt, 22 | 'negative_prompt': negative_prompt, 23 | 'steps': 20, 24 | 'width': 512, 25 | 'height': 768, 26 | 'cfg_scale': 7, 27 | 'seed': -1, 28 | 'sampler_index': 'DPM++ SDE Karras', 29 | 'script_name': None, 30 | 'alwayson_scripts': { 31 | 'BMAB': { 32 | 'args': [ 33 | { 34 | 'enabled': True, 35 | 'face_detailing_enabled': True, 36 | } 37 | ] 38 | } 39 | } 40 | } 41 | 42 | response = requests.post('http://localhost:7860/sdapi/v1/txt2img', data=json.dumps(txt2img)) 43 | print(response) 44 | j = response.json() 45 | b64_image = j['images'][0] 46 | 47 | 48 | with open('test.png', 'wb') as image_file: 49 | image_file.write(base64.b64decode(b64_image)) 50 | 51 | ``` 52 | 53 | BAMB의 Argument는 저장된 설정 파일과 동일하며, 54 | 이것을 기반으로 모든 설정을 사용할 수 있습니다. 설정에 없는 경우 기본값을 사용합니다. 55 | 56 | 아래는 json 형태의 기본 설정 값입니다. 57 | 58 | ```json 59 | { 60 | "enabled": false, 61 | "preprocess_checkpoint": "Use same checkpoint", 62 | "preprocess_vae": "Use same vae", 63 | "txt2img_noise_multiplier": 1, 64 | "txt2img_extra_noise_multiplier": 0, 65 | "txt2img_filter_hresfix_before_upscale": "None", 66 | "txt2img_filter_hresfix_after_upscale": "None", 67 | "resample_enabled": false, 68 | "module_config": { 69 | "resample_opt": { 70 | "save_image": false, 71 | "hiresfix_enabled": false, 72 | "checkpoint": "Use same checkpoint", 73 | "vae": "Use same vae", 74 | "method": "txt2img-1pass", 75 | "filter": "None", 76 | "prompt": "", 77 | "negative_prompt": "", 78 | "sampler": "Use same sampler", 79 | "upscaler": "BMAB fast", 80 | "steps": 20, 81 | "cfg_scale": 7, 82 | "denoising_strength": 0.75, 83 | "strength": 0.5, 84 | "begin": 0.1, 85 | "end": 0.9 86 | }, 87 | "pretraining_opt": { 88 | "hiresfix_enabled": false, 89 | "pretraining_model": "Select Model", 90 | "prompt": "", 91 | "negative_prompt": "", 92 | "sampler": "Use same sampler", 93 | "steps": 20, 94 | "cfg_scale": 7, 95 | "denoising_strength": 0.75, 96 | "dilation": 4, 97 | "box_threshold": 0.35 98 | }, 99 | "resize_intermediate_opt": { 100 | "resize_by_person": true, 101 | "method": "stretching", 102 | "alignment": "bottom", 103 | "filter": "None", 104 | "scale": 0.85, 105 | "denoising_strength": 0.75 106 | }, 107 | "refiner_opt": { 108 | "checkpoint": "Use same checkpoint", 109 | "keep_checkpoint": true, 110 | "prompt": "", 111 | "negative_prompt": "", 112 | "sampler": "Use same sampler", 113 | "upscaler": "BMAB fast", 114 | "steps": 20, 115 | "cfg_scale": 7, 116 | "denoising_strength": 0.75, 117 | "scale": 1, 118 | "width": 0, 119 | "height": 0 120 | }, 121 | "person_detailing_opt": { 122 | "best_quality": false, 123 | "force_1:1": false, 124 | "block_overscaled_image": true, 125 | "auto_upscale": true, 126 | "scale": 4, 127 | "dilation": 3, 128 | "area_ratio": 0.1, 129 | "limit": 1, 130 | "background_color": 1, 131 | "background_blur": 0 132 | }, 133 | "person_detailing": { 134 | "denoising_strength": 0.4, 135 | "cfg_scale": 7 136 | }, 137 | "face_detailing_opt": { 138 | "best_quality": false, 139 | "sort_by": "Score", 140 | "limit": 1, 141 | "prompt0": "", 142 | "negative_prompt0": "", 143 | "prompt1": "", 144 | "negative_prompt1": "", 145 | "prompt2": "", 146 | "negative_prompt2": "", 147 | "prompt3": "", 148 | "negative_prompt3": "", 149 | "prompt4": "", 150 | "negative_prompt4": "", 151 | "override_parameter": false, 152 | "sampler": "Use same sampler", 153 | "detection_model": "BMAB Face(Normal)", 154 | "dilation": 4, 155 | "box_threshold": 0.35, 156 | "skip_large_face": false, 157 | "large_face_pixels": 0.26 158 | }, 159 | "face_detailing": { 160 | "width": 512, 161 | "height": 512, 162 | "cfg_scale": 7, 163 | "steps": 20, 164 | "mask_blur": 4, 165 | "inpaint_full_res": "Only masked", 166 | "inpaint_full_res_padding": 32, 167 | "denoising_strength": 0.4 168 | }, 169 | "hand_detailing_opt": { 170 | "block_overscaled_image": true, 171 | "best_quality": false, 172 | "detailing_method": "subframe", 173 | "auto_upscale": true, 174 | "scale": 4, 175 | "box_threshold": 0.3, 176 | "dilation": 0.1, 177 | "additional_parameter": "" 178 | }, 179 | "hand_detailing": { 180 | "prompt": "", 181 | "negative_prompt": "", 182 | "denoising_strength": 0.4, 183 | "cfg_scale": 7, 184 | "inpaint_full_res": "Only masked", 185 | "inpaint_full_res_padding": 32 186 | }, 187 | "controlnet": { 188 | "enabled": false, 189 | "with_refiner": false, 190 | "noise": false, 191 | "noise_strength": 0.4, 192 | "noise_begin": 0.1, 193 | "noise_end": 0.9 194 | }, 195 | "resize_by_person_opt": { 196 | "mode": "Inpaint", 197 | "scale": 0.85, 198 | "denoising_strength": 0.6, 199 | "dilation": 30 200 | } 201 | }, 202 | "pretraining_enabled": false, 203 | "edge_flavor_enabled": false, 204 | "edge_low_threadhold": 50, 205 | "edge_high_threadhold": 200, 206 | "edge_strength": 0.5, 207 | "resize_intermediate_enabled": false, 208 | "refiner_enabled": false, 209 | "contrast": 1, 210 | "brightness": 1, 211 | "sharpeness": 1, 212 | "color_saturation": 1, 213 | "color_temperature": 0, 214 | "noise_alpha": 0, 215 | "noise_alpha_final": 0, 216 | "input_image": null, 217 | "blend_enabled": false, 218 | "blend_alpha": 1, 219 | "detect_enabled": false, 220 | "masking_prompt": "", 221 | "person_detailing_enabled": false, 222 | "face_detailing_enabled": false, 223 | "face_detailing_before_hiresfix_enabled": false, 224 | "hand_detailing_enabled": false, 225 | "resize_by_person_enabled": false, 226 | "upscale_enabled": false, 227 | "detailing_after_upscale": true, 228 | "upscaler_name": "None", 229 | "upscale_ratio": 1.5, 230 | "config_file": "test", 231 | "preset": "None" 232 | } 233 | ``` 234 | 235 | -------------------------------------------------------------------------------- /docs/kr/manual.md: -------------------------------------------------------------------------------- 1 | 2 | ## Quick Test 3 | 4 | Enable을 체크하고 Config Tab에서 Preset "example"을 선택합니다. 5 | 6 | contrast: 1.2 7 | brightness: 0.9 8 | sharpeness: 1.5 9 | 10 | Edge enhancement 적용 11 | Face Detailing 적용 12 | Resize by person 적용 13 | 14 | 15 | 16 | ## 기본 옵션 17 | 18 | Enabled (VERSION): 기능을 켜고 끌 수 있습니다. 19 | 20 | ### Resize and fill override 21 | 22 | Img2Img를 수행하는 경우 "Resize and fill" 을 선택하게 되면 23 | 통상 좌우, 상하로 늘어나거나 비율이 같다면 그대로 크기만 변경됩니다. 24 | 25 | Enabled 된 상태에서는 항상 이미지가 아래에 위치하고, 26 | 왼쪽, 오른쪽, 윗쪽으로 비율에 맞게 늘어납니다. 27 | 28 | 인물의 윗쪽으로 여백이 없는 경우에 적용하면 효과적입니다. 29 | 너무 크게 늘리게 되면 좋은 결과를 얻기 힘듭니다. 30 | 대략 1.1, 1.2 정도 스케일에서 사용하시길 권장합니다. 31 | 32 |

33 | 34 | 35 |

36 | 37 |
38 |
39 |
40 | 41 | # Preprocess 42 | 43 | 본 이미지 변경을 하기 전에 사전 처리 과정을 수행합니다. 44 | 조건에 따라 hires.fix 과정에 개입할 수도 있습니다. 45 | 46 | Preprocess 47 | 48 | # BMAB 49 | 50 | Person, Hand, Face detailer를 수행하거나, 이미지 합성 혹은 노이즈 추가등의 기능을 수행합니다. 51 | 52 | bmab 53 | 54 | # Postprocess 55 | 56 | 이미지 처리가 완료된 이후, 인물의 크기에 따라 배경을 확장하거나 upscale을 할 수 있습니다. 57 | 58 | Postprocess 59 | 60 | 61 | # API 62 | 63 | stable diffusion webui의 API 기능을 호출할때 BMAB extension을 사용할 수 있습니다. 64 | 65 | API Guide 66 | -------------------------------------------------------------------------------- /docs/kr/postprocess.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # PostProcessing 4 | 5 | ## Resize 6 | 7 | ### Resize by person 8 | 9 | 그림 속 인물중 가장 신장이 큰 사람의 길이와 그림 높이의 비율이 설정값을 넘어가면 비율을 설정값로 맞추는 기능입니다. 10 | 설정값이 0.90이고 인물의 전체 길이: 그림 높이의 비율이 0.95라고 한다면 11 | 배경을 늘려서 인물의 비율이 0.90이 되도록 합니다. 12 | 배경은 왼쪽, 오른쪽, 위쪽으로 늘어납니다. 13 | 14 | 15 | 이 기능은 2가지 방법을 제공하는데 다음과 같습니다. 16 | 17 | #### Inpaint 18 | 19 | Face Detailing과 같은 방법으로 이미지가 완전히 생성되고 난 후에 주변부를 확장합니다. 20 | 이때 이미 생성된 이미지를 훼손하지 않고 주변부만 확장하기 때문에 직관적으로 확인이 가능합니다. 21 | 가장 빠르고 효과적으로 추천합니다. 22 | 23 | #### Inpaint + lama 24 | 25 | Inpaint와 같은 방법인데 BMAB에서 ControlNet을 불러서 Inpaint+lama를 이용해서 동작합니다. 26 | 이미지가 생성되고나서 디테일링 시작하기 전에 img2img를 이용하여 배경을 확장하여 전체적으로 인물을 작게 만드는 효과가 있습니다. 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 | 43 | 이 두가지 방법은 생성된 이미지를 축소하기만 할뿐 훼손하지 않습니다. 44 | 이것이 Resize intermediate와 다른점입니다. 45 | 46 | 47 | ## Upscaler 48 | 49 | 최종적으로 이미지가 완성되고 난 이후에 이미지를 upscaler를 이용하여 크게 만듭니다. 50 | 51 | 52 | #### Enable upscale at final stage 53 | 54 | 이미지 생성이 완료되고 난 이후에 Upscale을 수행합니다. 55 | 960x540으로 생성하고 hires.fix를 x2로 하면 1920x1080 이미지가 나오는데 56 | 여기서 Upscale을 x2를 하면 4K 이미지가 나오게 됩니다. 57 | 58 | #### Detailing after upscale 59 | 60 | 이 옵션을 설정하면 위에서 언급한 Person, Face, Hand 에 대한 detailing을 upscale 이후에 수행합니다. 61 | 62 | #### Upscale Ratio 63 | 64 | 이미지를 얼마나 upscale할지 결정합니다. 65 | 66 | 67 | -------------------------------------------------------------------------------- /docs/masking.md: -------------------------------------------------------------------------------- 1 | 2 | # Auto Masking 3 | 4 | ## Detect 5 | 6 | * Img2Img 인물 사진을 추가한다. 7 | * Just resize 선택 8 | * Resize를 하면서 크기를 키워야 좋은 결과를 얻을 수 있다. 9 | 예제의 경우는 512x768 --> 800x1200 으로 resize한 것이다. 10 | * Denoising Strength는 0.4~0.6 정도를 설정한다. 11 | 모델별로 다르지만, 배경까지 심하게 바뀌는 경우 숫자를 줄인다. 12 | 13 |

14 | 15 |

16 | 17 | * BMAB를 Enable 한다. 18 | * 반드시 Process before img2img를 체크한다. 19 | * Detect enabled 체크하고 prompt에 person, 1girl, human...뭐든.. 20 | 21 |

22 | 23 |

24 | 25 | * 최종적으로 좋은 결과를 내기 위해서 합쳐진 이미지를 프롬프트로 잘 표현해야 한다. 26 | * 프롬프트가 없거나 적당히 적으면 원하는 이미지에서 멀어지기 때문에 프롬프트가 매우 중요하다. 27 | 28 |

29 | 30 |

31 | 32 | -------------------------------------------------------------------------------- /filter/Put filter file here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portu-sim/sd-webui-bmab/7a81441a72cef95cd9fdb732ee933bb04799dab3/filter/Put filter file here.txt -------------------------------------------------------------------------------- /filter/basic.py: -------------------------------------------------------------------------------- 1 | import os 2 | from PIL import Image 3 | from PIL import ImageEnhance 4 | 5 | import sd_bmab 6 | from sd_bmab import util 7 | from sd_bmab.base import filter 8 | from sd_bmab.base import cache 9 | 10 | 11 | class Filter(filter.BaseFilter): 12 | 13 | def preprocess(self, context, image, *args, **kwargs): 14 | pass 15 | 16 | def basic_process(self, image: Image): 17 | enhancer = ImageEnhance.Brightness(image) 18 | image = enhancer.enhance(0.8) 19 | enhancer = ImageEnhance.Contrast(image) 20 | image = enhancer.enhance(1.2) 21 | return image 22 | 23 | def basic_process_with_noise(self, processed: Image): 24 | noise = cache.get_noise_from_cache(0, processed.width, processed.height).convert('LA') 25 | noise = noise.convert('RGBA') 26 | blended = Image.blend(processed.convert('RGBA'), noise, alpha=0.1) 27 | return self.basic_process(blended.convert('RGB')) 28 | 29 | def process(self, context, image: Image, processed: Image, *args, **kwargs): 30 | print('-----FILTER BASIC-----') 31 | return self.basic_process(processed) 32 | 33 | def postprocess(self, context, *args, **kwargs): 34 | pass 35 | -------------------------------------------------------------------------------- /filter/vintage.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from PIL import ImageEnhance 3 | 4 | from sd_bmab.base import filter 5 | from sd_bmab.base import cache 6 | from sd_bmab.processors.basic import final 7 | 8 | 9 | CONTRAST = 0.8 10 | BRIGHTNESS = 0.9 11 | SHARPNESS = 0.5 12 | COLOR = 0.85 13 | COLOR_TEMPERATURE = 5240 14 | NOISE = 0.05 15 | 16 | 17 | class Filter(filter.BaseFilter): 18 | 19 | def preprocess(self, context, image, *args, **kwargs): 20 | pass 21 | 22 | def basic_process(self, image: Image): 23 | enhancer = ImageEnhance.Contrast(image) 24 | image = enhancer.enhance(CONTRAST) 25 | enhancer = ImageEnhance.Brightness(image) 26 | image = enhancer.enhance(BRIGHTNESS) 27 | enhancer = ImageEnhance.Sharpness(image) 28 | image = enhancer.enhance(SHARPNESS) 29 | enhancer = ImageEnhance.Color(image) 30 | image = enhancer.enhance(COLOR) 31 | temp = final.calc_color_temperature(COLOR_TEMPERATURE) 32 | az = [] 33 | for d in image.getdata(): 34 | az.append((int(d[0] * temp[0]), int(d[1] * temp[1]), int(d[2] * temp[2]))) 35 | image = Image.new('RGB', image.size) 36 | image.putdata(az) 37 | noise = cache.get_noise_from_cache(0, image.size[0], image.size[1]) 38 | image = Image.blend(image, noise, alpha=NOISE) 39 | return image 40 | 41 | def process(self, context, image: Image, processed: Image, *args, **kwargs): 42 | print('-----FILTER VINTAGE-----') 43 | return self.basic_process(processed) 44 | 45 | def postprocess(self, context, *args, **kwargs): 46 | pass 47 | -------------------------------------------------------------------------------- /install.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import launch 4 | 5 | 6 | def install_segmentanything(): 7 | launch.run_pip('install segment_anything') 8 | 9 | 10 | def install_segmentanything_hq(): 11 | launch.run_pip('install segment_anything_hq') 12 | 13 | 14 | def install_ultralytics(): 15 | launch.run_pip('install ultralytics') 16 | 17 | 18 | def install_diffusers(): 19 | launch.run_pip('install diffusers==0.27.2') 20 | 21 | 22 | try: 23 | from basicsr.utils.download_util import load_file_from_url 24 | except: 25 | launch.run_pip('install basicsr==1.4.2') 26 | 27 | required = { 28 | ('segment_anything', install_segmentanything), 29 | ('segment_anything_hq', install_segmentanything_hq), 30 | ('ultralytics', install_ultralytics), 31 | ('diffusers', install_diffusers) 32 | } 33 | 34 | for pack_name, func in required: 35 | if not launch.is_installed(pack_name): 36 | func() 37 | 38 | ''' 39 | with open('ui-config.json', 'rt', encoding='UTF8') as f: 40 | j = json.load(f) 41 | print(json.dumps(j, indent=2)) 42 | ''' 43 | -------------------------------------------------------------------------------- /models/Put model file here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portu-sim/sd-webui-bmab/7a81441a72cef95cd9fdb732ee933bb04799dab3/models/Put model file here.txt -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | segment_anything 2 | segment_anything_hq 3 | ultralytics 4 | pillow 5 | 6 | basicsr==1.4.2 7 | kornia 8 | omegaconf 9 | numpy 10 | -------------------------------------------------------------------------------- /resources/preset/3girls.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "contrast": 1.2, 4 | "brightness": 0.9, 5 | "sharpeness": 1.5, 6 | "execute_before_img2img": true, 7 | "edge_flavor_enabled": true, 8 | "edge_low_threadhold": 50, 9 | "edge_high_threadhold": 200, 10 | "edge_strength": 0.5, 11 | "resize_by_person_enabled": true, 12 | "resize_by_person": 0.85, 13 | "face_detailing_enabled": true, 14 | "module_config": { 15 | "multiple_face": [ 16 | { 17 | "denoising_strength": 0.40, 18 | "prompt": "smile, #!org!#", 19 | "inpaint_full_res": true, 20 | "inpaint_full_res_padding": 32, 21 | "cfg_scale": 7 22 | }, 23 | { 24 | "denoising_strength": 0.40, 25 | "prompt": "sad, #!org!#", 26 | "inpaint_full_res": true, 27 | "inpaint_full_res_padding": 32, 28 | "cfg_scale": 7 29 | }, 30 | { 31 | "denoising_strength": 0.40, 32 | "prompt": "sad, #!org!#", 33 | "width": 512, 34 | "height": 512, 35 | "inpaint_full_res": true, 36 | "inpaint_full_res_padding": 32, 37 | "cfg_scale": 7 38 | } 39 | ], 40 | "multiple_face_opt": { 41 | "mask dilation": 4, 42 | "limit": -1, 43 | "order": "left" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /resources/preset/Put config file here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portu-sim/sd-webui-bmab/7a81441a72cef95cd9fdb732ee933bb04799dab3/resources/preset/Put config file here.txt -------------------------------------------------------------------------------- /resources/preset/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "contrast": 1.2, 4 | "brightness": 0.9, 5 | "sharpeness": 1.5, 6 | "edge_flavor_enabled": true, 7 | "edge_low_threadhold": 50, 8 | "edge_high_threadhold": 200, 9 | "edge_strength": 0.5, 10 | "resize_by_person_enabled": true, 11 | "resize_by_person": 0.85, 12 | "face_detailing_enabled": true, 13 | "module_config": { 14 | "face_detailing": { 15 | "denoising_strength": 0.40, 16 | "prompt": "smile, #!org!#", 17 | "inpaint_full_res": true, 18 | "inpaint_full_res_padding": 32, 19 | "cfg_scale": 7 20 | }, 21 | "face_detailing_opt": { 22 | "mask dilation": 4 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /resources/preset/example2.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "contrast": 1.2, 4 | "brightness": 0.9, 5 | "sharpeness": 1.5, 6 | "edge_flavor_enabled": true, 7 | "edge_low_threadhold": 50, 8 | "edge_high_threadhold": 200, 9 | "edge_strength": 0.5, 10 | "resize_by_person_enabled": true, 11 | "resize_by_person": 0.85, 12 | "face_detailing_enabled": true, 13 | "module_config": { 14 | "face_detailing": { 15 | "denoising_strength": 0.40, 16 | "prompt": ", (ulzzang-6500:0.4), #!org!#", 17 | "width": 512, 18 | "height": 512, 19 | "inpaint_full_res": true, 20 | "inpaint_full_res_padding": 32, 21 | "cfg_scale": 7 22 | }, 23 | "face_detailing_opt": { 24 | "mask dilation": 4 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /resources/preset/hand.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "hand_detailing_enabled": true, 4 | "module_config": { 5 | "hand_detailing": { 6 | "denoising_strength": 0.4, 7 | "steps": 20, 8 | "cfg_scale": 7, 9 | "inpaint_full_res": 0, 10 | "inpaint_full_res_padding": 32 11 | }, 12 | "hand_detailing_opt": { 13 | "scale": 2, 14 | "mode": "inpaint", 15 | "detailing method": "subframe" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /resources/preset/hand2.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "hand_detailing_enabled": true, 4 | "module_config": { 5 | "hand_detailing": { 6 | "denoising_strength": 0.5, 7 | "prompt": "(good anatomy:1.2), (five fingers:1.3), pretty hands, detail hands, detail fingers, detail nails", 8 | "negative_prompt": "(bad anatomy:1.2), (wrong anatomy:1.2), mutation, amputation, extra fingers, missing fingers, disconnected fingers", 9 | "steps": 20, 10 | "cfg_scale": 7, 11 | "inpaint_full_res": 0, 12 | "inpaint_full_res_padding": 32 13 | }, 14 | "hand_detailing_opt": { 15 | "scale": 4, 16 | "mode": "inpaint", 17 | "detailing method": "each hand" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /scripts/sd_webui_bmab.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.bmab import BmabExtScript 2 | -------------------------------------------------------------------------------- /sd_bmab/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portu-sim/sd-webui-bmab/7a81441a72cef95cd9fdb732ee933bb04799dab3/sd_bmab/__init__.py -------------------------------------------------------------------------------- /sd_bmab/base/__init__.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.base.common import VAEMethodOverride 2 | from sd_bmab.base.context import Context 3 | from sd_bmab.base.detectorbase import DetectorBase 4 | from sd_bmab.base.processorbase import ProcessorBase 5 | from sd_bmab.base.process import process_img2img, process_txt2img, build_img2img, apply_extensions, process_img2img_with_controlnet 6 | from sd_bmab.base.maskbase import MaskBase 7 | 8 | -------------------------------------------------------------------------------- /sd_bmab/base/cache.py: -------------------------------------------------------------------------------- 1 | import os 2 | from PIL import Image 3 | 4 | import sd_bmab 5 | from sd_bmab import util 6 | 7 | 8 | def check_cache_dir(path): 9 | if not os.path.exists(path): 10 | os.mkdir(path) 11 | 12 | 13 | def get_noise_from_cache(seed, width, height): 14 | path = os.path.dirname(sd_bmab.__file__) 15 | path = os.path.normpath(os.path.join(path, '../resources/cache')) 16 | check_cache_dir(path) 17 | cache_file = f'{path}/noise_{width}_{height}.png' 18 | if os.path.isfile(cache_file): 19 | return Image.open(cache_file) 20 | img = util.generate_noise(seed, width, height) 21 | img.save(cache_file) 22 | return img 23 | 24 | 25 | def get_image_from_cache(filename): 26 | path = os.path.dirname(sd_bmab.__file__) 27 | path = os.path.normpath(os.path.join(path, '../resources/cache')) 28 | check_cache_dir(path) 29 | full_path = os.path.join(path, filename) 30 | if os.path.exists(full_path): 31 | return Image.open(full_path) 32 | return None 33 | 34 | 35 | def put_image_to_cache(filename, image): 36 | path = os.path.dirname(sd_bmab.__file__) 37 | path = os.path.normpath(os.path.join(path, '../resources/cache')) 38 | check_cache_dir(path) 39 | full_path = os.path.join(path, filename) 40 | image.save(full_path) 41 | -------------------------------------------------------------------------------- /sd_bmab/base/common.py: -------------------------------------------------------------------------------- 1 | from modules import shared 2 | 3 | 4 | class VAEMethodOverride: 5 | 6 | def __init__(self, hiresfix=False) -> None: 7 | super().__init__() 8 | self.org_encode_method = None 9 | self.org_decode_method = None 10 | self.img2img_fix_steps = None 11 | self.hiresfix = hiresfix 12 | 13 | def __enter__(self): 14 | if ('sd_vae_encode_method' in shared.opts.data) and shared.opts.bmab_detail_full: 15 | self.encode_method = shared.opts.sd_vae_encode_method 16 | self.decode_method = shared.opts.sd_vae_decode_method 17 | shared.opts.sd_vae_encode_method = 'Full' 18 | shared.opts.sd_vae_decode_method = 'Full' 19 | if self.hiresfix and not shared.opts.img2img_fix_steps: 20 | self.img2img_fix_steps = shared.opts.img2img_fix_steps 21 | shared.opts.img2img_fix_steps = True 22 | 23 | def __exit__(self, *args, **kwargs): 24 | if ('sd_vae_encode_method' in shared.opts.data) and shared.opts.bmab_detail_full: 25 | shared.opts.sd_vae_encode_method = self.encode_method 26 | shared.opts.sd_vae_decode_method = self.decode_method 27 | if self.img2img_fix_steps is not None: 28 | shared.opts.img2img_fix_steps = self.img2img_fix_steps 29 | 30 | 31 | class StopGeneration: 32 | 33 | def __init__(self) -> None: 34 | super().__init__() 35 | if not hasattr(shared.state, 'stopping_generation'): 36 | return 37 | self.stopping_generation = shared.state.stopping_generation 38 | 39 | def __enter__(self): 40 | if not hasattr(shared.state, 'stopping_generation'): 41 | return 42 | shared.state.stopping_generation = False 43 | 44 | def __exit__(self, *args, **kwargs): 45 | if not hasattr(shared.state, 'stopping_generation'): 46 | return 47 | shared.state.stopping_generation = self.stopping_generation 48 | -------------------------------------------------------------------------------- /sd_bmab/base/context.py: -------------------------------------------------------------------------------- 1 | from modules import shared 2 | from modules.processing import StableDiffusionProcessingImg2Img 3 | 4 | from sd_bmab.sd_override import StableDiffusionProcessingTxt2ImgOv, StableDiffusionProcessingImg2ImgOv 5 | from sd_bmab import constants 6 | 7 | 8 | class Context(object): 9 | def __init__(self, s, p, a, idx, **kwargs) -> None: 10 | super().__init__() 11 | 12 | self.script = s 13 | self.sdprocessing = p 14 | self.args = a 15 | self.index = idx 16 | self.refiner = None 17 | self.sd_model_name = None 18 | self.sd_vae_name = None 19 | self.container = {} 20 | 21 | @staticmethod 22 | def newContext(s, p, a, idx, **kwargs): 23 | return Context(s, p, a, idx, **kwargs) 24 | 25 | def get_current_prompt(self): 26 | return self.sdprocessing.prompt 27 | 28 | def get_prompt_by_index(self): 29 | if self.sdprocessing.all_prompts is None or len(self.sdprocessing.all_prompts) <= self.index: 30 | return self.sdprocessing.prompt 31 | return self.sdprocessing.all_prompts[self.index] 32 | 33 | def get_negative_prompt_by_index(self): 34 | if self.sdprocessing.all_negative_prompts is None or len(self.sdprocessing.all_negative_prompts) <= self.index: 35 | return self.sdprocessing.negative_prompt 36 | return self.sdprocessing.all_negative_prompts[self.index] 37 | 38 | def get_hires_prompt_by_index(self): 39 | if self.sdprocessing.all_hr_prompts is None or len(self.sdprocessing.all_hr_prompts) <= self.index: 40 | return self.sdprocessing.hr_prompt 41 | return self.sdprocessing.all_hr_prompts[self.index] 42 | 43 | def get_hires_negative_prompt_by_index(self): 44 | if self.sdprocessing.all_hr_negative_prompts is None or len(self.sdprocessing.all_hr_negative_prompts) <= self.index: 45 | return self.sdprocessing.hr_negative_prompt 46 | return self.sdprocessing.all_hr_negative_prompts[self.index] 47 | 48 | def get_seeds(self): 49 | if self.sdprocessing.all_seeds is None or self.sdprocessing.all_subseeds is None: 50 | return self.sdprocessing.seed, self.sdprocessing.subseed 51 | if len(self.sdprocessing.all_seeds) <= self.index or len(self.sdprocessing.all_subseeds) <= self.index: 52 | return self.sdprocessing.seed, self.sdprocessing.subseed 53 | return self.sdprocessing.all_seeds[self.index], self.sdprocessing.all_subseeds[self.index] 54 | 55 | def get_max_area(self): 56 | if shared.opts.bmab_optimize_vram == 'low vram': 57 | return 512 * 768 58 | elif shared.opts.bmab_optimize_vram == 'med vram': 59 | return self.sdprocessing.width * self.sdprocessing.height 60 | if isinstance(self.sdprocessing, StableDiffusionProcessingTxt2ImgOv) and self.sdprocessing.enable_hr: 61 | return self.sdprocessing.hr_upscale_to_x * self.sdprocessing.hr_upscale_to_y 62 | return self.sdprocessing.width * self.sdprocessing.height 63 | 64 | def add_generation_param(self, key: object, value: object) -> object: 65 | self.sdprocessing.extra_generation_params[key] = value 66 | 67 | def add_extra_image(self, image): 68 | self.script.extra_image.append(image) 69 | 70 | def with_refiner(self): 71 | return self.args.get('refiner_enabled', False) 72 | 73 | def is_refiner_context(self): 74 | return self.refiner is not None 75 | 76 | def is_hires_fix(self): 77 | if isinstance(self.sdprocessing, StableDiffusionProcessingTxt2ImgOv) and self.sdprocessing.enable_hr: 78 | return True 79 | return False 80 | 81 | def add_job(self, count=1): 82 | shared.state.job_count += count 83 | shared.state.sampling_step = 0 84 | shared.state.current_image_sampling_step = 0 85 | 86 | def is_img2img(self): 87 | return isinstance(self.sdprocessing, StableDiffusionProcessingImg2ImgOv) or isinstance(self.sdprocessing, StableDiffusionProcessingImg2Img) 88 | 89 | def is_txtimg(self): 90 | return isinstance(self.sdprocessing, StableDiffusionProcessingTxt2ImgOv) 91 | 92 | def save_and_apply_checkpoint(self, checkpoint, vae): 93 | self.sd_model_name = checkpoint 94 | self.sd_vae_name = vae 95 | 96 | def restore_checkpoint(self): 97 | self.sd_model_name = None 98 | self.sd_vae_name = None 99 | 100 | def apply_checkpoint(self, options): 101 | if self.sd_model_name is not None and self.sd_model_name != constants.checkpoint_default: 102 | override_settings = options.get('override_settings', {}) 103 | override_settings['sd_model_checkpoint'] = self.sd_model_name 104 | options['override_settings'] = override_settings 105 | if self.sd_vae_name is not None and self.sd_vae_name != constants.vae_default: 106 | override_settings = options.get('override_settings', {}) 107 | override_settings['sd_vae'] = self.sd_vae_name 108 | options['override_settings'] = override_settings 109 | 110 | def save(self, key, value): 111 | self.container[key] = value 112 | 113 | def load(self, key): 114 | return self.container.get(key) 115 | -------------------------------------------------------------------------------- /sd_bmab/base/detectorbase.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from sd_bmab.base.context import Context 3 | 4 | 5 | class DetectorBase(object): 6 | def __init__(self, **kwargs) -> None: 7 | super().__init__() 8 | 9 | def target(self): 10 | pass 11 | 12 | def description(self): 13 | pass 14 | 15 | def predict(self, context: Context, image: Image): 16 | pass 17 | -------------------------------------------------------------------------------- /sd_bmab/base/filter.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import glob 4 | import importlib.util 5 | 6 | from PIL import Image 7 | 8 | import sd_bmab 9 | from sd_bmab import constants 10 | from sd_bmab.util import debug_print 11 | from sd_bmab import controlnet 12 | 13 | 14 | filters = [constants.filter_default] 15 | 16 | 17 | class BaseFilter(object): 18 | 19 | def __init__(self) -> None: 20 | super().__init__() 21 | 22 | def configurations(self): 23 | return {} 24 | 25 | def is_controlnet_required(self): 26 | return False 27 | 28 | def preprocess(self, context, image, *args, **kwargs): 29 | pass 30 | 31 | def process(self, context, base: Image, processed: Image, *args, **kwargs): 32 | return processed 33 | 34 | def postprocess(self, context, *args, **kwargs): 35 | pass 36 | 37 | def finalprocess(self, context, *args, **kwargs): 38 | pass 39 | 40 | 41 | class NoneFilter(BaseFilter): 42 | 43 | def process_filter(self, context, base: Image, processed: Image, *args, **kwargs): 44 | return processed 45 | 46 | 47 | def reload_filters(): 48 | global filters 49 | filters = [constants.filter_default] 50 | 51 | path = os.path.dirname(sd_bmab.__file__) 52 | path = os.path.normpath(os.path.join(path, '../filter')) 53 | files = sorted(glob.glob(f'{path}/*.py')) 54 | for file in files: 55 | fname = os.path.splitext(os.path.basename(file))[0] 56 | filters.append(fname) 57 | 58 | 59 | def get_filter(name): 60 | if name == 'None': 61 | return NoneFilter() 62 | debug_print('Filter', name) 63 | path = os.path.dirname(sd_bmab.__file__) 64 | path = os.path.normpath(os.path.join(path, '../filter')) 65 | filter_path = f'{path}/{name}.py' 66 | mod = load_module(filter_path, 'filter') 67 | return eval(f'mod.Filter()') 68 | 69 | 70 | def load_module(file_name, module_name): 71 | spec = importlib.util.spec_from_file_location(module_name, file_name) 72 | module = importlib.util.module_from_spec(spec) 73 | sys.modules[module_name] = module 74 | spec.loader.exec_module(module) 75 | return module 76 | 77 | 78 | def preprocess_filter(bmab_filter, context, image, *args, **kwargs): 79 | bmab_filter.preprocess(context, image, *args, **kwargs) 80 | 81 | 82 | def process_filter(bmab_filter, context, base: Image, processed: Image, *args, **kwargs): 83 | return bmab_filter.process(context, base, processed, *args, **kwargs) 84 | 85 | 86 | def postprocess_filter(bmab_filter, context, *args, **kwargs): 87 | bmab_filter.postprocess(context, *args, **kwargs) 88 | 89 | 90 | def finalprocess_filter(bmab_filter, context, *args, **kwargs): 91 | bmab_filter.finalprocess(context, *args, **kwargs) 92 | 93 | -------------------------------------------------------------------------------- /sd_bmab/base/maskbase.py: -------------------------------------------------------------------------------- 1 | 2 | class MaskBase(object): 3 | def __init__(self) -> None: 4 | super().__init__() 5 | 6 | @property 7 | def name(self): 8 | pass 9 | 10 | @classmethod 11 | def init(cls, *args, **kwargs): 12 | pass 13 | 14 | def predict(self, image, box): 15 | pass 16 | 17 | def predict_multiple(self, image, points, labels, boxes=None): 18 | pass 19 | 20 | @classmethod 21 | def release(cls): 22 | pass 23 | -------------------------------------------------------------------------------- /sd_bmab/base/processorbase.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from sd_bmab.base.context import Context 3 | 4 | 5 | class ProcessorBase(object): 6 | def __init__(self) -> None: 7 | super().__init__() 8 | 9 | def use_controlnet(self, context: Context): 10 | return False 11 | 12 | def preprocess(self, context: Context, image: Image): 13 | pass 14 | 15 | def process(self, context: Context, image: Image): 16 | pass 17 | 18 | def postprocess(self, context: Context, image: Image): 19 | pass 20 | 21 | def finalprocess(self, context: Context, image: Image): 22 | pass 23 | -------------------------------------------------------------------------------- /sd_bmab/base/sam.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import os 3 | import numpy as np 4 | 5 | import torch 6 | 7 | from PIL import Image 8 | from modules.paths import models_path 9 | from modules.safe import unsafe_torch_load, load 10 | from modules.devices import device, torch_gc 11 | 12 | from segment_anything import SamPredictor 13 | from segment_anything import sam_model_registry 14 | 15 | bmab_model_path = os.path.join(models_path, "bmab") 16 | 17 | sam_model = None 18 | 19 | 20 | def sam_init(): 21 | MODEL_TYPE = 'vit_b' 22 | 23 | global sam_model 24 | if not sam_model: 25 | torch.load = unsafe_torch_load 26 | sam_model = sam_model_registry[MODEL_TYPE](checkpoint='%s/sam_vit_b_01ec64.pth' % bmab_model_path) 27 | sam_model.to(device=device) 28 | sam_model.eval() 29 | torch.load = load 30 | 31 | return sam_model 32 | 33 | 34 | def sam_predict(pilimg, boxes): 35 | sam = sam_init() 36 | 37 | mask_predictor = SamPredictor(sam) 38 | 39 | numpy_image = np.array(pilimg) 40 | opencv_image = cv2.cvtColor(numpy_image, cv2.COLOR_RGB2BGR) 41 | mask_predictor.set_image(opencv_image) 42 | 43 | result = Image.new('L', pilimg.size, 0) 44 | for box in boxes: 45 | x1, y1, x2, y2 = box 46 | 47 | box = np.array([int(x1), int(y1), int(x2), int(y2)]) 48 | masks, scores, logits = mask_predictor.predict( 49 | box=box, 50 | multimask_output=False 51 | ) 52 | 53 | mask = Image.fromarray(masks[0]) 54 | result.paste(mask, mask=mask) 55 | 56 | return result 57 | 58 | 59 | def sam_predict_box(pilimg, box): 60 | sam = sam_init() 61 | 62 | mask_predictor = SamPredictor(sam) 63 | 64 | numpy_image = np.array(pilimg) 65 | opencv_image = cv2.cvtColor(numpy_image, cv2.COLOR_RGB2BGR) 66 | mask_predictor.set_image(opencv_image) 67 | 68 | x1, y1, x2, y2 = box 69 | box = np.array([int(x1), int(y1), int(x2), int(y2)]) 70 | 71 | masks, scores, logits = mask_predictor.predict( 72 | box=box, 73 | multimask_output=False 74 | ) 75 | 76 | return Image.fromarray(masks[0]) 77 | 78 | 79 | def release(): 80 | global sam_model 81 | sam_model = None 82 | torch_gc() 83 | -------------------------------------------------------------------------------- /sd_bmab/bmab.py: -------------------------------------------------------------------------------- 1 | from modules import scripts 2 | from modules import shared 3 | from modules import script_callbacks 4 | from modules import images 5 | 6 | from sd_bmab import parameters 7 | from sd_bmab.base import context, filter 8 | 9 | from sd_bmab.pipeline import post 10 | from sd_bmab.pipeline import internal 11 | from sd_bmab import masking 12 | from sd_bmab import ui 13 | from sd_bmab import util 14 | from sd_bmab import controlnet 15 | from sd_bmab.sd_override import override_sd_webui, StableDiffusionProcessingTxt2ImgOv 16 | from sd_bmab.sd_override import sd_models 17 | from sd_bmab.compat import check_directory 18 | from sd_bmab.processors.basic import preprocessfilter 19 | 20 | 21 | check_directory() 22 | override_sd_webui() 23 | filter.reload_filters() 24 | 25 | if not shared.opts.data.get('bmab_for_developer', False): 26 | util.check_models() 27 | 28 | if shared.opts.data.get('bmab_additional_checkpoint_path', '') != '': 29 | sd_models.override() 30 | 31 | 32 | class BmabExtScript(scripts.Script): 33 | 34 | def __init__(self) -> None: 35 | super().__init__() 36 | self.extra_image = [] 37 | self.config = {} 38 | self.index = 0 39 | self.stop_generation = False 40 | 41 | def title(self): 42 | return 'BMAB' 43 | 44 | def show(self, is_img2img): 45 | return scripts.AlwaysVisible 46 | 47 | def ui(self, is_img2img): 48 | return ui.create_ui(self, is_img2img) 49 | 50 | def before_process(self, p, *args): 51 | self.stop_generation = False 52 | self.extra_image = [] 53 | ui.final_images = [] 54 | ui.last_process = p 55 | ui.bmab_script = self 56 | self.index = 0 57 | self.config, a = parameters.parse_args(args) 58 | if not a['enabled']: 59 | return 60 | 61 | controlnet.update_controlnet_args(p) 62 | if not hasattr(p, 'context') or p.context is None: 63 | ctx = context.Context.newContext(self, p, a, 0, hiresfix=True) 64 | p.context = ctx 65 | preprocessfilter.run_preprocess_filter(ctx) 66 | post.process_controlnet(p.context) 67 | internal.process_img2img(p.context) 68 | if isinstance(p, StableDiffusionProcessingTxt2ImgOv): 69 | p.initial_noise_multiplier = a.get('txt2img_noise_multiplier', 1) 70 | p.extra_noise = a.get('txt2img_extra_noise_multiplier', 0) 71 | 72 | def postprocess_image(self, p, pp, *args): 73 | self.config, a = parameters.parse_args(args) 74 | if not a['enabled']: 75 | ui.final_images.append(pp.image) 76 | return 77 | 78 | if shared.state.interrupted or shared.state.skipped: 79 | return 80 | 81 | if hasattr(p, 'context') and p.context is not None: 82 | p.context.index = self.index 83 | with controlnet.PreventControlNet(p.context, cn_enabled=post.is_controlnet_required(p.context)): 84 | pp.image = post.process(p.context, pp.image) 85 | ui.final_images.append(pp.image) 86 | self.index += 1 87 | if self.stop_generation: 88 | shared.state.interrupted = True 89 | 90 | def postprocess(self, p, processed, *args): 91 | if shared.opts.bmab_show_extends: 92 | processed.images.extend(self.extra_image) 93 | 94 | post.release() 95 | masking.release() 96 | 97 | def describe(self): 98 | return 'This stuff is worth it, you can buy me a beer in return.' 99 | 100 | def resize_image(self, ctx: context.Context, resize_mode, idx, image, width, height, upscaler_name): 101 | if not ctx.args['enabled']: 102 | return images.resize_image(resize_mode, image, width, height, upscaler_name=upscaler_name) 103 | with controlnet.PreventControlNet(ctx, cn_enabled=internal.is_controlnet_required(ctx)): 104 | image = internal.process_intermediate_before_upscale(ctx, image) 105 | image = images.resize_image(resize_mode, image, width, height, upscaler_name=upscaler_name) 106 | image = internal.process_intermediate_after_upscale(ctx, image) 107 | return image 108 | 109 | 110 | script_callbacks.on_ui_settings(ui.on_ui_settings) 111 | -------------------------------------------------------------------------------- /sd_bmab/compat.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import sd_bmab 4 | 5 | 6 | def check_directory(): 7 | target = ['cache', 'ipadapter', 'pose', 'saved'] 8 | bmab_path = os.path.dirname(sd_bmab.__file__) 9 | dest_path = os.path.normpath(os.path.join(bmab_path, f'../resources')) 10 | for t in target: 11 | path = os.path.normpath(os.path.join(bmab_path, f'../{t}')) 12 | if os.path.exists(path) and os.path.isdir(path): 13 | shutil.move(path, dest_path) 14 | -------------------------------------------------------------------------------- /sd_bmab/constants.py: -------------------------------------------------------------------------------- 1 | sampler_default = 'Use same sampler' 2 | scheduler_default = 'Use same scheduler' 3 | resize_mode_default = 'Intermediate' 4 | checkpoint_default = 'Use same checkpoint' 5 | vae_default = 'Use same vae' 6 | fast_upscaler = 'BMAB fast' 7 | filter_default = 'None' 8 | face_detector_default = 'BMAB Face(Normal)' 9 | 10 | checkpoint_description = ''' 11 | 12 | Specify Checkpoint and VAE to be used in BMAB.
13 | It applies to all functions, and if you change it to Checkpoint and VAE that exist for each function,
14 | it will be applied to all subsequent processes.
15 | ''' 16 | 17 | 18 | resize_description = ''' 19 | 20 | 21 |
22 | txt2img --resize--> hires.fix --> BMAB Preprocess --> BMAB
23 | txt2img --resize--> BMAB Preprocess --> BMAB
24 |
25 | Methods
26 | stretching : Fast process. Please denoising strength should be over 0.6. (Only CPU).
27 | inpaint : Slow process but High quality. Repaint stretching image. (Use GPU).
28 | inpaint+lama : Very slow process but Very high quality. Repaint stretching image using ControlNet inpaint_only+lama (Use GPU with FIRE!!).
29 | inpaint_only : Very slow process but Very high quality. Repaint stretching image using ControlNet inpaint_only (Use GPU with FIRE!!).
30 |
31 | Please DO NOT SET Latent upscaler in hires.fix. 32 |
33 | ''' 34 | 35 | kohya_hiresfix_description = ''' 36 | 37 | This is EXPERIMENTAL function. 38 | 39 | ''' 40 | -------------------------------------------------------------------------------- /sd_bmab/controlnet.py: -------------------------------------------------------------------------------- 1 | from copy import copy 2 | 3 | from modules import shared 4 | from modules import processing 5 | from modules import img2img 6 | from modules.processing import Processed 7 | 8 | from sd_bmab.util import debug_print, get_cn_args 9 | 10 | 11 | controlnet_args = (0, 0) 12 | 13 | 14 | class FakeControlNet: 15 | def __init__(self, ctx, cn_enabled=False) -> None: 16 | super().__init__() 17 | self.context = ctx 18 | self.process = ctx.sdprocessing 19 | self.all_prompts = None 20 | self.all_negative_prompts = None 21 | self.extra_image = None 22 | self.enabled = self.is_controlnet_enabled() if cn_enabled else False 23 | self.control_index = [] 24 | debug_print('FakeControlNet', self.enabled, cn_enabled) 25 | 26 | def __enter__(self): 27 | if self.enabled: 28 | dummy = Processed(self.process, [], self.process.seed, "") 29 | self.all_prompts = copy(self.process.all_prompts) 30 | self.all_negative_prompts = copy(self.process.all_negative_prompts) 31 | self.extra_image = copy(self.context.script.extra_image) 32 | self.process.scripts.postprocess(copy(self.process), dummy) 33 | for idx, obj in enumerate(self.process.script_args): 34 | if 'controlnet' in obj.__class__.__name__.lower(): 35 | if hasattr(obj, 'enabled') and obj.enabled: 36 | obj.enabled = False 37 | self.control_index.append(idx) 38 | elif isinstance(obj, dict) and 'model' in obj and obj['enabled']: 39 | obj['enabled'] = False 40 | self.control_index.append(idx) 41 | 42 | def __exit__(self, *args, **kwargs): 43 | if self.enabled: 44 | copy_p = copy(self.process) 45 | self.process.all_prompts = self.all_prompts 46 | self.process.all_negative_prompts = self.all_negative_prompts 47 | self.extra_image.extend(self.context.script.extra_image) 48 | for idx in self.control_index: 49 | obj = self.process.script_args[idx] 50 | if 'controlnet' in obj.__class__.__name__.lower(): 51 | if hasattr(obj, 'enabled'): 52 | obj.enabled = True 53 | elif isinstance(obj, dict) and 'model' in obj: 54 | obj['enabled'] = True 55 | self.process.scripts.before_process(copy_p) 56 | self.process.scripts.process(copy_p) 57 | self.context.script.extra_image.extend(self.extra_image) 58 | 59 | def is_controlnet_enabled(self): 60 | global controlnet_args 61 | for idx in range(controlnet_args[0], controlnet_args[1]): 62 | obj = self.process.script_args[idx] 63 | if isinstance(obj, dict): 64 | return True 65 | if 'controlnet' in obj.__class__.__name__.lower(): 66 | if hasattr(obj, 'enabled'): 67 | return True 68 | return False 69 | 70 | 71 | class PreventControlNet(FakeControlNet): 72 | process_images_inner = processing.process_images_inner 73 | process_batch = img2img.process_batch 74 | 75 | def __init__(self, ctx, cn_enabled=False) -> None: 76 | super().__init__(ctx, cn_enabled) 77 | self._process_images_inner = processing.process_images_inner 78 | self._process_batch = img2img.process_batch 79 | self.allow_script_control = None 80 | self.p = ctx.sdprocessing 81 | self.all_prompts = copy(ctx.sdprocessing.all_prompts) 82 | self.all_negative_prompts = copy(ctx.sdprocessing.all_negative_prompts) 83 | 84 | def is_controlnet_used(self): 85 | if not self.p.script_args: 86 | return False 87 | 88 | for idx, obj in enumerate(self.p.script_args): 89 | if 'controlnet' in obj.__class__.__name__.lower(): 90 | if hasattr(obj, 'enabled') and obj.enabled: 91 | debug_print('Use controlnet True') 92 | return True 93 | elif isinstance(obj, dict) and 'module' in obj and obj['enabled']: 94 | debug_print('Use controlnet True') 95 | return True 96 | 97 | debug_print('Use controlnet False') 98 | return False 99 | 100 | def __enter__(self): 101 | model = self.p.sd_model.model.diffusion_model 102 | if hasattr(model, '_original_forward'): 103 | model._old_forward = self.p.sd_model.model.diffusion_model.forward 104 | model.forward = getattr(model, '_original_forward') 105 | 106 | processing.process_images_inner = PreventControlNet.process_images_inner 107 | img2img.process_batch = PreventControlNet.process_batch 108 | if 'control_net_allow_script_control' in shared.opts.data: 109 | self.allow_script_control = shared.opts.data['control_net_allow_script_control'] 110 | shared.opts.data['control_net_allow_script_control'] = True 111 | self.multiple_tqdm = shared.opts.data.get('multiple_tqdm', True) 112 | shared.opts.data['multiple_tqdm'] = False 113 | super().__enter__() 114 | 115 | def __exit__(self, *args, **kwargs): 116 | processing.process_images_inner = self._process_images_inner 117 | img2img.process_batch = self._process_batch 118 | if 'control_net_allow_script_control' in shared.opts.data: 119 | shared.opts.data['control_net_allow_script_control'] = self.allow_script_control 120 | shared.opts.data['multiple_tqdm'] = self.multiple_tqdm 121 | model = self.p.sd_model.model.diffusion_model 122 | if hasattr(model, '_original_forward') and hasattr(model, '_old_forward'): 123 | self.p.sd_model.model.diffusion_model.forward = model._old_forward 124 | super().__exit__(*args, **kwargs) 125 | 126 | 127 | def update_controlnet_args(p): 128 | cn_arg_index = [] 129 | for idx, obj in enumerate(p.script_args): 130 | if 'controlnet' in obj.__class__.__name__.lower(): 131 | cn_arg_index.append(idx) 132 | global controlnet_args 133 | controlnet_args = (cn_arg_index[0], cn_arg_index[-1]) 134 | 135 | 136 | def get_controlnet_index(p): 137 | cn_args = get_cn_args(p) 138 | controlnet_count = 0 139 | for num in range(*cn_args): 140 | obj = p.script_args[num] 141 | if hasattr(obj, 'enabled') and obj.enabled: 142 | controlnet_count += 1 143 | elif isinstance(obj, dict) and 'model' in obj and obj['enabled']: 144 | controlnet_count += 1 145 | else: 146 | break 147 | return cn_args[0] + controlnet_count 148 | -------------------------------------------------------------------------------- /sd_bmab/detectors/__init__.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.detectors.face import UltralyticsFaceDetector8s 2 | from sd_bmab.detectors.face import UltralyticsFaceDetector8nv2 3 | from sd_bmab.detectors.face import UltralyticsFaceDetector8n 4 | from sd_bmab.detectors.face import UltralyticsFaceDetector8m 5 | from sd_bmab.detectors.face import BmabFaceNormal 6 | from sd_bmab.detectors.face import BmabFaceSmall 7 | from sd_bmab.detectors.person import UltralyticsPersonDetector8m 8 | from sd_bmab.detectors.person import UltralyticsPersonDetector8n 9 | from sd_bmab.detectors.person import UltralyticsPersonDetector8s 10 | from sd_bmab.detectors.hand import UltralyticsHandDetector8s 11 | from sd_bmab.detectors.hand import UltralyticsHandDetector8n 12 | from sd_bmab.detectors.hand import BmabHandDetector 13 | from sd_bmab.detectors.detector import list_face_detectors, list_hand_detectors, list_person_detectors, get_detector 14 | 15 | 16 | -------------------------------------------------------------------------------- /sd_bmab/detectors/anything.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.base.detectorbase import DetectorBase 2 | 3 | 4 | class UltralyticsAnythingDetector(DetectorBase): 5 | def __init__(self) -> None: 6 | super().__init__() 7 | 8 | def target(self): 9 | pass 10 | 11 | def description(self): 12 | pass 13 | 14 | def detect(self, context, image): 15 | pass 16 | -------------------------------------------------------------------------------- /sd_bmab/detectors/detector.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.base.context import Context 2 | 3 | from sd_bmab.detectors.person import UltralyticsPersonDetector8m 4 | from sd_bmab.detectors.person import UltralyticsPersonDetector8n, UltralyticsPersonDetector8s 5 | from sd_bmab.detectors.face import UltralyticsFaceDetector8n, UltralyticsFaceDetector8s 6 | from sd_bmab.detectors.face import UltralyticsFaceDetector8nv2, UltralyticsFaceDetector8m 7 | from sd_bmab.detectors.face import BmabFaceSmall, BmabFaceNormal 8 | from sd_bmab.detectors.hand import UltralyticsHandDetector8n, UltralyticsHandDetector8s 9 | from sd_bmab.util import debug_print 10 | 11 | 12 | def get_detector(context: Context, model: str, **kwargs): 13 | 14 | debug_print('model', model) 15 | if model == 'face_yolov8n.pt': 16 | return UltralyticsFaceDetector8n(**kwargs) 17 | 18 | all_detectors = [ 19 | BmabFaceNormal(**kwargs), 20 | BmabFaceSmall(**kwargs), 21 | UltralyticsPersonDetector8m(**kwargs), 22 | UltralyticsPersonDetector8n(**kwargs), 23 | UltralyticsPersonDetector8s(**kwargs), 24 | UltralyticsFaceDetector8n(**kwargs), 25 | UltralyticsFaceDetector8nv2(**kwargs), 26 | UltralyticsFaceDetector8m(**kwargs), 27 | UltralyticsFaceDetector8s(**kwargs), 28 | UltralyticsHandDetector8n(**kwargs), 29 | UltralyticsHandDetector8s(**kwargs), 30 | ] 31 | 32 | targets = [x for x in all_detectors if model == x.target()] 33 | if len(targets) == 1: 34 | return targets[0] 35 | raise Exception(f'Not found or multiple detector {model}') 36 | 37 | 38 | def list_person_detectors(): 39 | kwargs = {} 40 | person_detectors = [ 41 | UltralyticsPersonDetector8m(**kwargs), 42 | UltralyticsPersonDetector8n(**kwargs), 43 | UltralyticsPersonDetector8s(**kwargs), 44 | ] 45 | return [x.target() for x in person_detectors] 46 | 47 | 48 | def list_face_detectors(): 49 | kwargs = {} 50 | face_detectors = [ 51 | BmabFaceNormal(**kwargs), 52 | BmabFaceSmall(**kwargs), 53 | UltralyticsFaceDetector8n(**kwargs), 54 | UltralyticsFaceDetector8nv2(**kwargs), 55 | UltralyticsFaceDetector8m(**kwargs), 56 | UltralyticsFaceDetector8s(**kwargs), 57 | ] 58 | return [x.target() for x in face_detectors] 59 | 60 | 61 | def list_hand_detectors(): 62 | kwargs = {} 63 | hand_detectors = [ 64 | UltralyticsHandDetector8n(**kwargs), 65 | UltralyticsHandDetector8s(**kwargs), 66 | ] 67 | return [x.target() for x in hand_detectors] 68 | -------------------------------------------------------------------------------- /sd_bmab/detectors/face.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from PIL import Image 3 | from ultralytics import YOLO 4 | 5 | import modules 6 | from modules import images 7 | from modules import shared 8 | 9 | from sd_bmab import util 10 | from sd_bmab.base.context import Context 11 | from sd_bmab.base.detectorbase import DetectorBase 12 | 13 | 14 | class FaceDetector(DetectorBase): 15 | 16 | def description(self): 17 | return f'Face detecting using {self.target()}' 18 | 19 | 20 | class UltralyticsFaceDetector(FaceDetector): 21 | def __init__(self, **kwargs) -> None: 22 | super().__init__(**kwargs) 23 | self.confidence = kwargs.get('box_threshold', 0.35) 24 | self.model = None 25 | 26 | def target(self): 27 | return f'Ultralytics({self.model})' 28 | 29 | def predict(self, context: Context, image: Image): 30 | yolo = util.lazy_loader(self.model) 31 | boxes = [] 32 | confs = [] 33 | load = torch.load 34 | torch.load = modules.safe.unsafe_torch_load 35 | try: 36 | model = YOLO(yolo) 37 | pred = model(image, conf=self.confidence, device='') 38 | boxes = pred[0].boxes.xyxy.cpu().numpy() 39 | boxes = boxes.tolist() 40 | confs = pred[0].boxes.conf.tolist() 41 | except: 42 | pass 43 | torch.load = load 44 | return boxes, confs 45 | 46 | 47 | class UltralyticsFaceDetector8n(UltralyticsFaceDetector): 48 | def __init__(self, **kwargs) -> None: 49 | super().__init__(**kwargs) 50 | self.model = 'face_yolov8n.pt' 51 | 52 | 53 | class UltralyticsFaceDetector8m(UltralyticsFaceDetector): 54 | def __init__(self, **kwargs) -> None: 55 | super().__init__(**kwargs) 56 | self.model = 'face_yolov8m.pt' 57 | 58 | 59 | class UltralyticsFaceDetector8nv2(UltralyticsFaceDetector): 60 | def __init__(self, **kwargs) -> None: 61 | super().__init__(**kwargs) 62 | self.model = 'face_yolov8n_v2.pt' 63 | 64 | 65 | class UltralyticsFaceDetector8s(UltralyticsFaceDetector): 66 | def __init__(self, **kwargs) -> None: 67 | super().__init__(**kwargs) 68 | self.model = 'face_yolov8s.pt' 69 | 70 | 71 | class BmabFaceSmall(UltralyticsFaceDetector): 72 | def __init__(self, **kwargs) -> None: 73 | super().__init__(**kwargs) 74 | self.model = 'bmab_face_sm_yolov8n.pt' 75 | 76 | def target(self): 77 | return 'BMAB Face(Small)' 78 | 79 | def predict(self, context: Context, image: Image): 80 | if shared.opts.bmab_debug_logging: 81 | boxes, logits = super().predict(context, image) 82 | if len(boxes) == 0: 83 | images.save_image( 84 | image, context.sdprocessing.outpath_samples, '', 85 | context.sdprocessing.all_seeds[context.index], context.sdprocessing.all_prompts[context.index], 86 | shared.opts.samples_format, p=context.sdprocessing, suffix="-debugging") 87 | det = UltralyticsFaceDetector8n() 88 | return det.predict(context, image) 89 | return boxes, logits 90 | else: 91 | return super().predict(context, image) 92 | 93 | 94 | class BmabFaceNormal(UltralyticsFaceDetector): 95 | def __init__(self, **kwargs) -> None: 96 | super().__init__(**kwargs) 97 | self.model = 'bmab_face_nm_yolov8n.pt' 98 | 99 | def target(self): 100 | return 'BMAB Face(Normal)' 101 | 102 | def predict(self, context: Context, image: Image): 103 | if shared.opts.bmab_debug_logging: 104 | boxes, logits = super().predict(context, image) 105 | if len(boxes) == 0: 106 | images.save_image( 107 | image, context.sdprocessing.outpath_samples, '', 108 | context.sdprocessing.all_seeds[context.index], context.sdprocessing.all_prompts[context.index], 109 | shared.opts.samples_format, p=context.sdprocessing, suffix="-debugging") 110 | det = UltralyticsFaceDetector8n() 111 | return det.predict(context, image) 112 | return boxes, logits 113 | else: 114 | return super().predict(context, image) 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /sd_bmab/detectors/hand.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from PIL import Image 3 | from ultralytics import YOLO 4 | 5 | import modules 6 | 7 | from sd_bmab import util 8 | from sd_bmab.base.context import Context 9 | from sd_bmab.base.detectorbase import DetectorBase 10 | 11 | 12 | class HandDetector(DetectorBase): 13 | 14 | def description(self): 15 | return f'Hand detecting using {self.target()}' 16 | 17 | 18 | class UltralyticsHandDetector(HandDetector): 19 | def __init__(self, **kwargs) -> None: 20 | super().__init__(**kwargs) 21 | self.confidence = kwargs.get('box_threshold', 0.35) 22 | self.model = None 23 | 24 | def target(self): 25 | return f'Ultralytics({self.model})' 26 | 27 | def predict(self, context: Context, image: Image): 28 | yolo = util.lazy_loader(self.model) 29 | boxes = [] 30 | confs = [] 31 | load = torch.load 32 | torch.load = modules.safe.unsafe_torch_load 33 | try: 34 | model = YOLO(yolo) 35 | pred = model(image, conf=self.confidence, device='') 36 | boxes = pred[0].boxes.xyxy.cpu().numpy() 37 | boxes = boxes.tolist() 38 | confs = pred[0].boxes.conf.tolist() 39 | except: 40 | pass 41 | torch.load = load 42 | return boxes, confs 43 | 44 | 45 | class UltralyticsHandDetector8n(UltralyticsHandDetector): 46 | def __init__(self, **kwargs) -> None: 47 | super().__init__(**kwargs) 48 | self.model = 'hand_yolov8n.pt' 49 | 50 | 51 | class UltralyticsHandDetector8s(UltralyticsHandDetector): 52 | def __init__(self, **kwargs) -> None: 53 | super().__init__(**kwargs) 54 | self.model = 'hand_yolov8s.pt' 55 | 56 | 57 | class BmabHandDetector(UltralyticsHandDetector): 58 | def __init__(self, **kwargs) -> None: 59 | super().__init__(**kwargs) 60 | self.model = 'bmab_hand_yolov8n.pt' 61 | 62 | def target(self): 63 | return 'BMAB Hand(Normal)' 64 | -------------------------------------------------------------------------------- /sd_bmab/detectors/person.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from PIL import Image 3 | from ultralytics import YOLO 4 | 5 | import modules 6 | 7 | from sd_bmab import util 8 | from sd_bmab.base.context import Context 9 | from sd_bmab.base.detectorbase import DetectorBase 10 | 11 | 12 | class PersonDetector(DetectorBase): 13 | 14 | def description(self): 15 | return f'Person detecting using {self.target()}' 16 | 17 | 18 | class UltralyticsPersonDetector(PersonDetector): 19 | def __init__(self, **kwargs) -> None: 20 | super().__init__(**kwargs) 21 | self.confidence = kwargs.get('box_threshold', 0.35) 22 | self.model = None 23 | 24 | def target(self): 25 | return f'Ultralytics({self.model})' 26 | 27 | def predict(self, context: Context, image: Image): 28 | yolo = util.lazy_loader(self.model) 29 | boxes = [] 30 | confs = [] 31 | load = torch.load 32 | torch.load = modules.safe.unsafe_torch_load 33 | try: 34 | model = YOLO(yolo) 35 | pred = model(image, conf=self.confidence, device='') 36 | boxes = pred[0].boxes.xyxy.cpu().numpy() 37 | boxes = boxes.tolist() 38 | confs = pred[0].boxes.conf.tolist() 39 | except: 40 | pass 41 | torch.load = load 42 | return boxes, confs 43 | 44 | 45 | class UltralyticsPersonDetector8n(UltralyticsPersonDetector): 46 | def __init__(self, **kwargs) -> None: 47 | super().__init__(**kwargs) 48 | self.model = 'person_yolov8n-seg.pt' 49 | 50 | 51 | class UltralyticsPersonDetector8m(UltralyticsPersonDetector): 52 | def __init__(self, **kwargs) -> None: 53 | super().__init__(**kwargs) 54 | self.model = 'person_yolov8m-seg.pt' 55 | 56 | 57 | class UltralyticsPersonDetector8s(UltralyticsPersonDetector): 58 | def __init__(self, **kwargs) -> None: 59 | super().__init__(**kwargs) 60 | self.model = 'person_yolov8s-seg.pt' 61 | -------------------------------------------------------------------------------- /sd_bmab/external/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import importlib.util 4 | 5 | 6 | def load_external_module(module, name): 7 | path = os.path.dirname(__file__) 8 | path = os.path.normpath(os.path.join(path, f'{module}/{name}.py')) 9 | return load_module(path, 'module') 10 | 11 | 12 | def load_module(file_name, module_name): 13 | spec = importlib.util.spec_from_file_location(module_name, file_name) 14 | module = importlib.util.module_from_spec(spec) 15 | sys.modules[module_name] = module 16 | spec.loader.exec_module(module) 17 | return module 18 | 19 | 20 | class ModuleAutoLoader(object): 21 | 22 | def __init__(self, module, name) -> None: 23 | super().__init__() 24 | self.mod = load_external_module(module, name) 25 | 26 | def __enter__(self): 27 | return self.mod 28 | 29 | def __exit__(self, *args, **kwargs): 30 | self.mod.release() 31 | -------------------------------------------------------------------------------- /sd_bmab/external/groundingdino/grdino.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import torch 4 | 5 | from groundingdino.util.inference import load_model, predict 6 | from modules.devices import device, torch_gc 7 | 8 | from torchvision.ops import box_convert 9 | import groundingdino.datasets.transforms as T 10 | 11 | from sd_bmab import util 12 | 13 | 14 | dino_model = None 15 | 16 | 17 | def dino_init(): 18 | global dino_model 19 | if not dino_model: 20 | util.debug_print('initialize grounding dino.') 21 | swint_ogc = util.lazy_loader('GroundingDINO_SwinT_OGC.py') 22 | swint_ogc_pth = util.lazy_loader('groundingdino_swint_ogc.pth') 23 | dino_model = load_model(swint_ogc, swint_ogc_pth) 24 | return dino_model 25 | 26 | 27 | def dino_predict(pilimg, prompt, box_threahold=0.35, text_threshold=0.25): 28 | transform = T.Compose( 29 | [ 30 | T.RandomResize([800], max_size=1333), 31 | T.ToTensor(), 32 | T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), 33 | ] 34 | ) 35 | img = pilimg.convert('RGB') 36 | image_source = np.asarray(img) 37 | image, _ = transform(img, None) 38 | 39 | model = dino_init() 40 | boxes, logits, phrases = predict( 41 | model=model, 42 | image=image, 43 | caption=prompt, 44 | box_threshold=box_threahold, 45 | text_threshold=text_threshold 46 | ) 47 | 48 | h, w, _ = image_source.shape 49 | boxes = boxes * torch.Tensor([w, h, w, h]) 50 | annotated_frame = box_convert(boxes=boxes, in_fmt='cxcywh', out_fmt='xyxy').numpy() 51 | 52 | return annotated_frame, logits, phrases 53 | 54 | 55 | def release(): 56 | global dino_model 57 | del dino_model 58 | torch_gc() 59 | -------------------------------------------------------------------------------- /sd_bmab/external/iclight/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portu-sim/sd-webui-bmab/7a81441a72cef95cd9fdb732ee933bb04799dab3/sd_bmab/external/iclight/__init__.py -------------------------------------------------------------------------------- /sd_bmab/external/iclight/bmabiclight.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | 4 | from ultralytics import YOLO 5 | 6 | from PIL import Image 7 | from PIL import ImageDraw 8 | from PIL import ImageFilter 9 | 10 | import modules 11 | from modules import devices 12 | 13 | from sd_bmab import util 14 | from sd_bmab.base import sam 15 | from sd_bmab.external import load_external_module 16 | 17 | 18 | def process_iclight(context, image, bg_image, prompt, blending, bg_source, arg1, arg2): 19 | np_image = np.array(image.convert('RGB')).astype("uint8") 20 | 21 | if bg_image is None: 22 | mod = load_external_module('iclight', 'iclightnm') 23 | input_fg, matting = mod.run_rmbg(np_image) 24 | seed, subseed = context.get_seeds() 25 | result = mod.process_relight(input_fg, prompt, image.width, image.height, 1, seed, 25, 26 | 'best quality', 'lowres, bad anatomy, bad hands, cropped, worst quality', 27 | arg1[0], arg1[1], arg1[2], arg1[3], bg_source) 28 | mod.release() 29 | fg = Image.fromarray(input_fg.astype('uint8'), 'RGB') 30 | context.add_extra_image(image) 31 | context.add_extra_image(fg) 32 | context.add_extra_image(result) 33 | else: 34 | mod = load_external_module('iclight', 'iclightbg') 35 | input_fg, matting = mod.run_rmbg(np_image) 36 | seed, subseed = context.get_seeds() 37 | result = mod.process_relight(input_fg, None, prompt, image.width, image.height, 1, seed, 20, 38 | 'best quality', 'lowres, bad anatomy, bad hands, cropped, worst quality', 39 | arg2[0], arg2[1], arg2[2], bg_source) 40 | mod.release() 41 | context.add_extra_image(image) 42 | context.add_extra_image(bg_image) 43 | context.add_extra_image(result) 44 | return result 45 | 46 | 47 | def process_bmab_relight(context, image, bg_image, prompt, blending, bg_source, arg1): 48 | mod = load_external_module('iclight', 'iclightbg') 49 | seed, subseed = context.get_seeds() 50 | img1 = image.convert('RGBA') 51 | if bg_image is None: 52 | print('BG Source', bg_source) 53 | if bg_source == 'Face' or bg_source == 'Person': 54 | img2 = generate_detection_gradient(image, bg_source) 55 | context.add_extra_image(img2) 56 | else: 57 | img2 = generate_gradient((32, 32, 32), (224, 224, 224), image.width, image.height, bg_source) 58 | img2 = img2.convert('RGBA') 59 | else: 60 | img2 = bg_image.resize(img1.size, Image.LANCZOS).convert('RGBA') 61 | 62 | blended = Image.blend(img1, img2, alpha=blending) 63 | np_image = np.array(image.convert('RGB')).astype("uint8") 64 | input_bg = np.array(blended.convert('RGB')).astype("uint8") 65 | input_fg, matting = mod.run_rmbg(np_image) 66 | result = mod.process_relight(input_fg, input_bg, prompt, image.width, image.height, 1, seed, 20, 67 | 'best quality', 'lowres, bad anatomy, bad hands, cropped, worst quality', 68 | arg1[0], arg1[1], arg1[2], 'Use Background Image') 69 | mod.release() 70 | return result 71 | 72 | 73 | def generate_gradient( 74 | colour1, colour2, width: int, height: int, d) -> Image: 75 | """Generate a vertical gradient.""" 76 | base = Image.new('RGB', (width, height), colour1) 77 | top = Image.new('RGB', (width, height), colour2) 78 | mask = Image.new('L', (width, height)) 79 | mask_data = [] 80 | if d == 'Left': 81 | for y in range(height): 82 | mask_data.extend([255 - int(255 * (x / width)) for x in range(width)]) 83 | if d == 'Right': 84 | for y in range(height): 85 | mask_data.extend([int(255 * (x / width)) for x in range(width)]) 86 | if d == 'Bottom': 87 | for y in range(height): 88 | mask_data.extend([int(255 * (y / height))] * width) 89 | if d == 'Top': 90 | for y in range(height): 91 | mask_data.extend([255 - int(255 * (y / height))] * width) 92 | mask.putdata(mask_data) 93 | base.paste(top, (0, 0), mask) 94 | return base 95 | 96 | 97 | def predict(image: Image, model, confidence): 98 | yolo = util.load_pretraining_model(model) 99 | boxes = [] 100 | confs = [] 101 | load = torch.load 102 | torch.load = modules.safe.unsafe_torch_load 103 | try: 104 | model = YOLO(yolo) 105 | pred = model(image, conf=confidence, device='') 106 | boxes = pred[0].boxes.xyxy.cpu().numpy() 107 | boxes = boxes.tolist() 108 | confs = pred[0].boxes.conf.tolist() 109 | except: 110 | pass 111 | torch.load = load 112 | devices.torch_gc() 113 | 114 | return boxes, confs 115 | 116 | 117 | def generate_detection_gradient(image, model): 118 | mask = Image.new('L', (512, 768), 32) 119 | dr = ImageDraw.Draw(mask, 'L') 120 | 121 | if model == 'Face': 122 | boxes, confs = predict(image, 'face_yolov8n.pt', 0.35) 123 | for box, conf in zip(boxes, confs): 124 | x1, y1, x2, y2 = tuple(int(x) for x in box) 125 | dx = int((x2-x1)) 126 | dy = int((y2-y1)) 127 | dr.ellipse((x1 - dx, y1 - dy, x2 + dx, y2 + dy), fill=225) 128 | blur = ImageFilter.GaussianBlur(10) 129 | elif model == 'Person': 130 | boxes, confs = predict(image, 'person_yolov8n-seg.pt', 0.35) 131 | for box, conf in zip(boxes, confs): 132 | x1, y1, x2, y2 = tuple(int(x) for x in box) 133 | m = sam.sam_predict_box(image, (x1, y1, x2, y2)) 134 | mask.paste(m, mask=m) 135 | blur = ImageFilter.GaussianBlur(30) 136 | else: 137 | return mask 138 | return mask.filter(blur) 139 | 140 | 141 | def bmab_relight(context, process_type, image, bg_image, prompt, blending, bg_source): 142 | if process_type == 'intensive': 143 | if bg_source == 'Face' or bg_source == 'Person': 144 | bg_source = 'None' 145 | return process_iclight(context, image, bg_image, prompt, blending, bg_source, (2, 1.0, 0.5, 0.9), (7, 1.0, 0.5)) 146 | elif process_type == 'less intensive': 147 | if bg_source == 'Face' or bg_source == 'Person': 148 | bg_source = 'None' 149 | return process_iclight(context, image, bg_image, prompt, blending, bg_source, (2, 1.0, 0.45, 0.85), (7, 1.0, 0.45)) 150 | elif process_type == 'normal': 151 | return process_bmab_relight(context, image, bg_image, prompt, blending, bg_source, (7, 1.0, 0.45)) 152 | elif process_type == 'soft': 153 | return process_bmab_relight(context, image, bg_image, prompt, blending, bg_source, (7, 1.0, 0.4)) 154 | 155 | 156 | def release(): 157 | pass 158 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/__init__.py: -------------------------------------------------------------------------------- 1 | # https://github.com/advimman/lama 2 | # https://github.com/Mikubill/sd-webui-controlnet 3 | 4 | import os 5 | import cv2 6 | import yaml 7 | import torch 8 | import numpy as np 9 | from PIL import Image 10 | from omegaconf import OmegaConf 11 | from einops import rearrange 12 | 13 | from modules import devices 14 | 15 | from sd_bmab.external.lama.saicinpainting.training.trainers import load_checkpoint 16 | from sd_bmab import util 17 | 18 | 19 | class LamaInpainting: 20 | 21 | def __init__(self): 22 | self.model = None 23 | self.device = 'cpu' #devices.get_optimal_device() 24 | # self.device = devices.get_optimal_device() 25 | 26 | @staticmethod 27 | def load_image(pilimg, mode='RGB'): 28 | img = np.array(pilimg.convert(mode)) 29 | if img.ndim == 3: 30 | print('transpose') 31 | img = np.transpose(img, (2, 0, 1)) 32 | out_img = img.astype('float32') / 255 33 | return out_img 34 | 35 | def load_model(self): 36 | config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.yaml') 37 | cfg = yaml.safe_load(open(config_path, 'rt')) 38 | cfg = OmegaConf.create(cfg) 39 | cfg.training_model.predict_only = True 40 | cfg.visualizer.kind = 'noop' 41 | lamapth = util.lazy_loader('ControlNetLama.pth') 42 | self.model = load_checkpoint(cfg, lamapth, strict=False, map_location='cpu') 43 | self.model = self.model.to(self.device) 44 | self.model.eval() 45 | 46 | def unload_model(self): 47 | if self.model is not None: 48 | self.model.cpu() 49 | 50 | def __call__(self, image, mask): 51 | if self.model is None: 52 | self.load_model() 53 | self.model.to(self.device) 54 | 55 | opencv_image = cv2.cvtColor(np.array(image.convert('RGB')), cv2.COLOR_RGB2BGR)[:, :, 0:3] 56 | opencv_mask = cv2.cvtColor(np.array(mask.convert('RGB')), cv2.COLOR_RGB2BGR)[:, :, 0:1] 57 | color = np.ascontiguousarray(opencv_image).astype(np.float32) / 255.0 58 | mask = np.ascontiguousarray(opencv_mask).astype(np.float32) / 255.0 59 | 60 | with torch.no_grad(): 61 | color = torch.from_numpy(color).float().to(self.device) 62 | mask = torch.from_numpy(mask).float().to(self.device) 63 | mask = (mask > 0.5).float() 64 | color = color * (1 - mask) 65 | image_feed = torch.cat([color, mask], dim=2) 66 | image_feed = rearrange(image_feed, 'h w c -> 1 c h w') 67 | result = self.model(image_feed)[0] 68 | result = rearrange(result, 'c h w -> h w c') 69 | result = result * mask + color * (1 - mask) 70 | result *= 255.0 71 | 72 | img = result.detach().cpu().numpy().clip(0, 255).astype(np.uint8) 73 | color_coverted = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 74 | pil_image = Image.fromarray(color_coverted) 75 | return pil_image 76 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/config.yaml: -------------------------------------------------------------------------------- 1 | run_title: b18_ffc075_batch8x15 2 | training_model: 3 | kind: default 4 | visualize_each_iters: 1000 5 | concat_mask: true 6 | store_discr_outputs_for_vis: true 7 | losses: 8 | l1: 9 | weight_missing: 0 10 | weight_known: 10 11 | perceptual: 12 | weight: 0 13 | adversarial: 14 | kind: r1 15 | weight: 10 16 | gp_coef: 0.001 17 | mask_as_fake_target: true 18 | allow_scale_mask: true 19 | feature_matching: 20 | weight: 100 21 | resnet_pl: 22 | weight: 30 23 | weights_path: ${env:TORCH_HOME} 24 | 25 | optimizers: 26 | generator: 27 | kind: adam 28 | lr: 0.001 29 | discriminator: 30 | kind: adam 31 | lr: 0.0001 32 | visualizer: 33 | key_order: 34 | - image 35 | - predicted_image 36 | - discr_output_fake 37 | - discr_output_real 38 | - inpainted 39 | rescale_keys: 40 | - discr_output_fake 41 | - discr_output_real 42 | kind: directory 43 | outdir: /group-volume/User-Driven-Content-Generation/r.suvorov/inpainting/experiments/r.suvorov_2021-04-30_14-41-12_train_simple_pix2pix2_gap_sdpl_novgg_large_b18_ffc075_batch8x15/samples 44 | location: 45 | data_root_dir: /group-volume/User-Driven-Content-Generation/datasets/inpainting_data_root_large 46 | out_root_dir: /group-volume/User-Driven-Content-Generation/${env:USER}/inpainting/experiments 47 | tb_dir: /group-volume/User-Driven-Content-Generation/${env:USER}/inpainting/tb_logs 48 | data: 49 | batch_size: 15 50 | val_batch_size: 2 51 | num_workers: 3 52 | train: 53 | indir: ${location.data_root_dir}/train 54 | out_size: 256 55 | mask_gen_kwargs: 56 | irregular_proba: 1 57 | irregular_kwargs: 58 | max_angle: 4 59 | max_len: 200 60 | max_width: 100 61 | max_times: 5 62 | min_times: 1 63 | box_proba: 1 64 | box_kwargs: 65 | margin: 10 66 | bbox_min_size: 30 67 | bbox_max_size: 150 68 | max_times: 3 69 | min_times: 1 70 | segm_proba: 0 71 | segm_kwargs: 72 | confidence_threshold: 0.5 73 | max_object_area: 0.5 74 | min_mask_area: 0.07 75 | downsample_levels: 6 76 | num_variants_per_mask: 1 77 | rigidness_mode: 1 78 | max_foreground_coverage: 0.3 79 | max_foreground_intersection: 0.7 80 | max_mask_intersection: 0.1 81 | max_hidden_area: 0.1 82 | max_scale_change: 0.25 83 | horizontal_flip: true 84 | max_vertical_shift: 0.2 85 | position_shuffle: true 86 | transform_variant: distortions 87 | dataloader_kwargs: 88 | batch_size: ${data.batch_size} 89 | shuffle: true 90 | num_workers: ${data.num_workers} 91 | val: 92 | indir: ${location.data_root_dir}/val 93 | img_suffix: .png 94 | dataloader_kwargs: 95 | batch_size: ${data.val_batch_size} 96 | shuffle: false 97 | num_workers: ${data.num_workers} 98 | visual_test: 99 | indir: ${location.data_root_dir}/korean_test 100 | img_suffix: _input.png 101 | pad_out_to_modulo: 32 102 | dataloader_kwargs: 103 | batch_size: 1 104 | shuffle: false 105 | num_workers: ${data.num_workers} 106 | generator: 107 | kind: ffc_resnet 108 | input_nc: 4 109 | output_nc: 3 110 | ngf: 64 111 | n_downsampling: 3 112 | n_blocks: 18 113 | add_out_act: sigmoid 114 | init_conv_kwargs: 115 | ratio_gin: 0 116 | ratio_gout: 0 117 | enable_lfu: false 118 | downsample_conv_kwargs: 119 | ratio_gin: ${generator.init_conv_kwargs.ratio_gout} 120 | ratio_gout: ${generator.downsample_conv_kwargs.ratio_gin} 121 | enable_lfu: false 122 | resnet_conv_kwargs: 123 | ratio_gin: 0.75 124 | ratio_gout: ${generator.resnet_conv_kwargs.ratio_gin} 125 | enable_lfu: false 126 | discriminator: 127 | kind: pix2pixhd_nlayer 128 | input_nc: 3 129 | ndf: 64 130 | n_layers: 4 131 | evaluator: 132 | kind: default 133 | inpainted_key: inpainted 134 | integral_kind: ssim_fid100_f1 135 | trainer: 136 | kwargs: 137 | gpus: -1 138 | accelerator: ddp 139 | max_epochs: 200 140 | gradient_clip_val: 1 141 | log_gpu_memory: None 142 | limit_train_batches: 25000 143 | val_check_interval: ${trainer.kwargs.limit_train_batches} 144 | log_every_n_steps: 1000 145 | precision: 32 146 | terminate_on_nan: false 147 | check_val_every_n_epoch: 1 148 | num_sanity_val_steps: 8 149 | limit_val_batches: 1000 150 | replace_sampler_ddp: false 151 | checkpoint_kwargs: 152 | verbose: true 153 | save_top_k: 5 154 | save_last: true 155 | period: 1 156 | monitor: val_ssim_fid100_f1_total_mean 157 | mode: max 158 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portu-sim/sd-webui-bmab/7a81441a72cef95cd9fdb732ee933bb04799dab3/sd_bmab/external/lama/saicinpainting/__init__.py -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portu-sim/sd-webui-bmab/7a81441a72cef95cd9fdb732ee933bb04799dab3/sd_bmab/external/lama/saicinpainting/training/__init__.py -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portu-sim/sd-webui-bmab/7a81441a72cef95cd9fdb732ee933bb04799dab3/sd_bmab/external/lama/saicinpainting/training/data/__init__.py -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/losses/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portu-sim/sd-webui-bmab/7a81441a72cef95cd9fdb732ee933bb04799dab3/sd_bmab/external/lama/saicinpainting/training/losses/__init__.py -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/losses/constants.py: -------------------------------------------------------------------------------- 1 | weights = {"ade20k": 2 | [6.34517766497462, 3 | 9.328358208955224, 4 | 11.389521640091116, 5 | 16.10305958132045, 6 | 20.833333333333332, 7 | 22.22222222222222, 8 | 25.125628140703515, 9 | 43.29004329004329, 10 | 50.5050505050505, 11 | 54.6448087431694, 12 | 55.24861878453038, 13 | 60.24096385542168, 14 | 62.5, 15 | 66.2251655629139, 16 | 84.74576271186442, 17 | 90.90909090909092, 18 | 91.74311926605505, 19 | 96.15384615384616, 20 | 96.15384615384616, 21 | 97.08737864077669, 22 | 102.04081632653062, 23 | 135.13513513513513, 24 | 149.2537313432836, 25 | 153.84615384615384, 26 | 163.93442622950818, 27 | 166.66666666666666, 28 | 188.67924528301887, 29 | 192.30769230769232, 30 | 217.3913043478261, 31 | 227.27272727272725, 32 | 227.27272727272725, 33 | 227.27272727272725, 34 | 303.03030303030306, 35 | 322.5806451612903, 36 | 333.3333333333333, 37 | 370.3703703703703, 38 | 384.61538461538464, 39 | 416.6666666666667, 40 | 416.6666666666667, 41 | 434.7826086956522, 42 | 434.7826086956522, 43 | 454.5454545454545, 44 | 454.5454545454545, 45 | 500.0, 46 | 526.3157894736842, 47 | 526.3157894736842, 48 | 555.5555555555555, 49 | 555.5555555555555, 50 | 555.5555555555555, 51 | 555.5555555555555, 52 | 555.5555555555555, 53 | 555.5555555555555, 54 | 555.5555555555555, 55 | 588.2352941176471, 56 | 588.2352941176471, 57 | 588.2352941176471, 58 | 588.2352941176471, 59 | 588.2352941176471, 60 | 666.6666666666666, 61 | 666.6666666666666, 62 | 666.6666666666666, 63 | 666.6666666666666, 64 | 714.2857142857143, 65 | 714.2857142857143, 66 | 714.2857142857143, 67 | 714.2857142857143, 68 | 714.2857142857143, 69 | 769.2307692307693, 70 | 769.2307692307693, 71 | 769.2307692307693, 72 | 833.3333333333334, 73 | 833.3333333333334, 74 | 833.3333333333334, 75 | 833.3333333333334, 76 | 909.090909090909, 77 | 1000.0, 78 | 1111.111111111111, 79 | 1111.111111111111, 80 | 1111.111111111111, 81 | 1111.111111111111, 82 | 1111.111111111111, 83 | 1250.0, 84 | 1250.0, 85 | 1250.0, 86 | 1250.0, 87 | 1250.0, 88 | 1428.5714285714287, 89 | 1428.5714285714287, 90 | 1428.5714285714287, 91 | 1428.5714285714287, 92 | 1428.5714285714287, 93 | 1428.5714285714287, 94 | 1428.5714285714287, 95 | 1666.6666666666667, 96 | 1666.6666666666667, 97 | 1666.6666666666667, 98 | 1666.6666666666667, 99 | 1666.6666666666667, 100 | 1666.6666666666667, 101 | 1666.6666666666667, 102 | 1666.6666666666667, 103 | 1666.6666666666667, 104 | 1666.6666666666667, 105 | 1666.6666666666667, 106 | 2000.0, 107 | 2000.0, 108 | 2000.0, 109 | 2000.0, 110 | 2000.0, 111 | 2000.0, 112 | 2000.0, 113 | 2000.0, 114 | 2000.0, 115 | 2000.0, 116 | 2000.0, 117 | 2000.0, 118 | 2000.0, 119 | 2000.0, 120 | 2000.0, 121 | 2000.0, 122 | 2000.0, 123 | 2500.0, 124 | 2500.0, 125 | 2500.0, 126 | 2500.0, 127 | 2500.0, 128 | 2500.0, 129 | 2500.0, 130 | 2500.0, 131 | 2500.0, 132 | 2500.0, 133 | 2500.0, 134 | 2500.0, 135 | 2500.0, 136 | 3333.3333333333335, 137 | 3333.3333333333335, 138 | 3333.3333333333335, 139 | 3333.3333333333335, 140 | 3333.3333333333335, 141 | 3333.3333333333335, 142 | 3333.3333333333335, 143 | 3333.3333333333335, 144 | 3333.3333333333335, 145 | 3333.3333333333335, 146 | 3333.3333333333335, 147 | 3333.3333333333335, 148 | 3333.3333333333335, 149 | 5000.0, 150 | 5000.0, 151 | 5000.0] 152 | } -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/losses/distance_weighting.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | import torchvision 5 | 6 | from sd_bmab.external.lama.saicinpainting.training.losses.perceptual import IMAGENET_STD, IMAGENET_MEAN 7 | 8 | 9 | def dummy_distance_weighter(real_img, pred_img, mask): 10 | return mask 11 | 12 | 13 | def get_gauss_kernel(kernel_size, width_factor=1): 14 | coords = torch.stack(torch.meshgrid(torch.arange(kernel_size), 15 | torch.arange(kernel_size)), 16 | dim=0).float() 17 | diff = torch.exp(-((coords - kernel_size // 2) ** 2).sum(0) / kernel_size / width_factor) 18 | diff /= diff.sum() 19 | return diff 20 | 21 | 22 | class BlurMask(nn.Module): 23 | def __init__(self, kernel_size=5, width_factor=1): 24 | super().__init__() 25 | self.filter = nn.Conv2d(1, 1, kernel_size, padding=kernel_size // 2, padding_mode='replicate', bias=False) 26 | self.filter.weight.data.copy_(get_gauss_kernel(kernel_size, width_factor=width_factor)) 27 | 28 | def forward(self, real_img, pred_img, mask): 29 | with torch.no_grad(): 30 | result = self.filter(mask) * mask 31 | return result 32 | 33 | 34 | class EmulatedEDTMask(nn.Module): 35 | def __init__(self, dilate_kernel_size=5, blur_kernel_size=5, width_factor=1): 36 | super().__init__() 37 | self.dilate_filter = nn.Conv2d(1, 1, dilate_kernel_size, padding=dilate_kernel_size// 2, padding_mode='replicate', 38 | bias=False) 39 | self.dilate_filter.weight.data.copy_(torch.ones(1, 1, dilate_kernel_size, dilate_kernel_size, dtype=torch.float)) 40 | self.blur_filter = nn.Conv2d(1, 1, blur_kernel_size, padding=blur_kernel_size // 2, padding_mode='replicate', bias=False) 41 | self.blur_filter.weight.data.copy_(get_gauss_kernel(blur_kernel_size, width_factor=width_factor)) 42 | 43 | def forward(self, real_img, pred_img, mask): 44 | with torch.no_grad(): 45 | known_mask = 1 - mask 46 | dilated_known_mask = (self.dilate_filter(known_mask) > 1).float() 47 | result = self.blur_filter(1 - dilated_known_mask) * mask 48 | return result 49 | 50 | 51 | class PropagatePerceptualSim(nn.Module): 52 | def __init__(self, level=2, max_iters=10, temperature=500, erode_mask_size=3): 53 | super().__init__() 54 | vgg = torchvision.models.vgg19(pretrained=True).features 55 | vgg_avg_pooling = [] 56 | 57 | for weights in vgg.parameters(): 58 | weights.requires_grad = False 59 | 60 | cur_level_i = 0 61 | for module in vgg.modules(): 62 | if module.__class__.__name__ == 'Sequential': 63 | continue 64 | elif module.__class__.__name__ == 'MaxPool2d': 65 | vgg_avg_pooling.append(nn.AvgPool2d(kernel_size=2, stride=2, padding=0)) 66 | else: 67 | vgg_avg_pooling.append(module) 68 | if module.__class__.__name__ == 'ReLU': 69 | cur_level_i += 1 70 | if cur_level_i == level: 71 | break 72 | 73 | self.features = nn.Sequential(*vgg_avg_pooling) 74 | 75 | self.max_iters = max_iters 76 | self.temperature = temperature 77 | self.do_erode = erode_mask_size > 0 78 | if self.do_erode: 79 | self.erode_mask = nn.Conv2d(1, 1, erode_mask_size, padding=erode_mask_size // 2, bias=False) 80 | self.erode_mask.weight.data.fill_(1) 81 | 82 | def forward(self, real_img, pred_img, mask): 83 | with torch.no_grad(): 84 | real_img = (real_img - IMAGENET_MEAN.to(real_img)) / IMAGENET_STD.to(real_img) 85 | real_feats = self.features(real_img) 86 | 87 | vertical_sim = torch.exp(-(real_feats[:, :, 1:] - real_feats[:, :, :-1]).pow(2).sum(1, keepdim=True) 88 | / self.temperature) 89 | horizontal_sim = torch.exp(-(real_feats[:, :, :, 1:] - real_feats[:, :, :, :-1]).pow(2).sum(1, keepdim=True) 90 | / self.temperature) 91 | 92 | mask_scaled = F.interpolate(mask, size=real_feats.shape[-2:], mode='bilinear', align_corners=False) 93 | if self.do_erode: 94 | mask_scaled = (self.erode_mask(mask_scaled) > 1).float() 95 | 96 | cur_knowness = 1 - mask_scaled 97 | 98 | for iter_i in range(self.max_iters): 99 | new_top_knowness = F.pad(cur_knowness[:, :, :-1] * vertical_sim, (0, 0, 1, 0), mode='replicate') 100 | new_bottom_knowness = F.pad(cur_knowness[:, :, 1:] * vertical_sim, (0, 0, 0, 1), mode='replicate') 101 | 102 | new_left_knowness = F.pad(cur_knowness[:, :, :, :-1] * horizontal_sim, (1, 0, 0, 0), mode='replicate') 103 | new_right_knowness = F.pad(cur_knowness[:, :, :, 1:] * horizontal_sim, (0, 1, 0, 0), mode='replicate') 104 | 105 | new_knowness = torch.stack([new_top_knowness, new_bottom_knowness, 106 | new_left_knowness, new_right_knowness], 107 | dim=0).max(0).values 108 | 109 | cur_knowness = torch.max(cur_knowness, new_knowness) 110 | 111 | cur_knowness = F.interpolate(cur_knowness, size=mask.shape[-2:], mode='bilinear') 112 | result = torch.min(mask, 1 - cur_knowness) 113 | 114 | return result 115 | 116 | 117 | def make_mask_distance_weighter(kind='none', **kwargs): 118 | if kind == 'none': 119 | return dummy_distance_weighter 120 | if kind == 'blur': 121 | return BlurMask(**kwargs) 122 | if kind == 'edt': 123 | return EmulatedEDTMask(**kwargs) 124 | if kind == 'pps': 125 | return PropagatePerceptualSim(**kwargs) 126 | raise ValueError(f'Unknown mask distance weighter kind {kind}') 127 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/losses/feature_matching.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import torch 4 | import torch.nn.functional as F 5 | 6 | 7 | def masked_l2_loss(pred, target, mask, weight_known, weight_missing): 8 | per_pixel_l2 = F.mse_loss(pred, target, reduction='none') 9 | pixel_weights = mask * weight_missing + (1 - mask) * weight_known 10 | return (pixel_weights * per_pixel_l2).mean() 11 | 12 | 13 | def masked_l1_loss(pred, target, mask, weight_known, weight_missing): 14 | per_pixel_l1 = F.l1_loss(pred, target, reduction='none') 15 | pixel_weights = mask * weight_missing + (1 - mask) * weight_known 16 | return (pixel_weights * per_pixel_l1).mean() 17 | 18 | 19 | def feature_matching_loss(fake_features: List[torch.Tensor], target_features: List[torch.Tensor], mask=None): 20 | if mask is None: 21 | res = torch.stack([F.mse_loss(fake_feat, target_feat) 22 | for fake_feat, target_feat in zip(fake_features, target_features)]).mean() 23 | else: 24 | res = 0 25 | norm = 0 26 | for fake_feat, target_feat in zip(fake_features, target_features): 27 | cur_mask = F.interpolate(mask, size=fake_feat.shape[-2:], mode='bilinear', align_corners=False) 28 | error_weights = 1 - cur_mask 29 | cur_val = ((fake_feat - target_feat).pow(2) * error_weights).mean() 30 | res = res + cur_val 31 | norm += 1 32 | res = res / norm 33 | return res 34 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/losses/perceptual.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | import torchvision 5 | 6 | # from models.ade20k import ModelBuilder 7 | from sd_bmab.external.lama.saicinpainting.utils import check_and_warn_input_range 8 | 9 | 10 | IMAGENET_MEAN = torch.FloatTensor([0.485, 0.456, 0.406])[None, :, None, None] 11 | IMAGENET_STD = torch.FloatTensor([0.229, 0.224, 0.225])[None, :, None, None] 12 | 13 | 14 | class PerceptualLoss(nn.Module): 15 | def __init__(self, normalize_inputs=True): 16 | super(PerceptualLoss, self).__init__() 17 | 18 | self.normalize_inputs = normalize_inputs 19 | self.mean_ = IMAGENET_MEAN 20 | self.std_ = IMAGENET_STD 21 | 22 | vgg = torchvision.models.vgg19(pretrained=True).features 23 | vgg_avg_pooling = [] 24 | 25 | for weights in vgg.parameters(): 26 | weights.requires_grad = False 27 | 28 | for module in vgg.modules(): 29 | if module.__class__.__name__ == 'Sequential': 30 | continue 31 | elif module.__class__.__name__ == 'MaxPool2d': 32 | vgg_avg_pooling.append(nn.AvgPool2d(kernel_size=2, stride=2, padding=0)) 33 | else: 34 | vgg_avg_pooling.append(module) 35 | 36 | self.vgg = nn.Sequential(*vgg_avg_pooling) 37 | 38 | def do_normalize_inputs(self, x): 39 | return (x - self.mean_.to(x.device)) / self.std_.to(x.device) 40 | 41 | def partial_losses(self, input, target, mask=None): 42 | check_and_warn_input_range(target, 0, 1, 'PerceptualLoss target in partial_losses') 43 | 44 | # we expect input and target to be in [0, 1] range 45 | losses = [] 46 | 47 | if self.normalize_inputs: 48 | features_input = self.do_normalize_inputs(input) 49 | features_target = self.do_normalize_inputs(target) 50 | else: 51 | features_input = input 52 | features_target = target 53 | 54 | for layer in self.vgg[:30]: 55 | 56 | features_input = layer(features_input) 57 | features_target = layer(features_target) 58 | 59 | if layer.__class__.__name__ == 'ReLU': 60 | loss = F.mse_loss(features_input, features_target, reduction='none') 61 | 62 | if mask is not None: 63 | cur_mask = F.interpolate(mask, size=features_input.shape[-2:], 64 | mode='bilinear', align_corners=False) 65 | loss = loss * (1 - cur_mask) 66 | 67 | loss = loss.mean(dim=tuple(range(1, len(loss.shape)))) 68 | losses.append(loss) 69 | 70 | return losses 71 | 72 | def forward(self, input, target, mask=None): 73 | losses = self.partial_losses(input, target, mask=mask) 74 | return torch.stack(losses).sum(dim=0) 75 | 76 | def get_global_features(self, input): 77 | check_and_warn_input_range(input, 0, 1, 'PerceptualLoss input in get_global_features') 78 | 79 | if self.normalize_inputs: 80 | features_input = self.do_normalize_inputs(input) 81 | else: 82 | features_input = input 83 | 84 | features_input = self.vgg(features_input) 85 | return features_input 86 | 87 | 88 | class ResNetPL(nn.Module): 89 | def __init__(self, weight=1, 90 | weights_path=None, arch_encoder='resnet50dilated', segmentation=True): 91 | super().__init__() 92 | self.impl = ModelBuilder.get_encoder(weights_path=weights_path, 93 | arch_encoder=arch_encoder, 94 | arch_decoder='ppm_deepsup', 95 | fc_dim=2048, 96 | segmentation=segmentation) 97 | self.impl.eval() 98 | for w in self.impl.parameters(): 99 | w.requires_grad_(False) 100 | 101 | self.weight = weight 102 | 103 | def forward(self, pred, target): 104 | pred = (pred - IMAGENET_MEAN.to(pred)) / IMAGENET_STD.to(pred) 105 | target = (target - IMAGENET_MEAN.to(target)) / IMAGENET_STD.to(target) 106 | 107 | pred_feats = self.impl(pred, return_feature_maps=True) 108 | target_feats = self.impl(target, return_feature_maps=True) 109 | 110 | result = torch.stack([F.mse_loss(cur_pred, cur_target) 111 | for cur_pred, cur_target 112 | in zip(pred_feats, target_feats)]).sum() * self.weight 113 | return result 114 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/losses/segmentation.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | from .constants import weights as constant_weights 6 | 7 | 8 | class CrossEntropy2d(nn.Module): 9 | def __init__(self, reduction="mean", ignore_label=255, weights=None, *args, **kwargs): 10 | """ 11 | weight (Tensor, optional): a manual rescaling weight given to each class. 12 | If given, has to be a Tensor of size "nclasses" 13 | """ 14 | super(CrossEntropy2d, self).__init__() 15 | self.reduction = reduction 16 | self.ignore_label = ignore_label 17 | self.weights = weights 18 | if self.weights is not None: 19 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 20 | self.weights = torch.FloatTensor(constant_weights[weights]).to(device) 21 | 22 | def forward(self, predict, target): 23 | """ 24 | Args: 25 | predict:(n, c, h, w) 26 | target:(n, 1, h, w) 27 | """ 28 | target = target.long() 29 | assert not target.requires_grad 30 | assert predict.dim() == 4, "{0}".format(predict.size()) 31 | assert target.dim() == 4, "{0}".format(target.size()) 32 | assert predict.size(0) == target.size(0), "{0} vs {1} ".format(predict.size(0), target.size(0)) 33 | assert target.size(1) == 1, "{0}".format(target.size(1)) 34 | assert predict.size(2) == target.size(2), "{0} vs {1} ".format(predict.size(2), target.size(2)) 35 | assert predict.size(3) == target.size(3), "{0} vs {1} ".format(predict.size(3), target.size(3)) 36 | target = target.squeeze(1) 37 | n, c, h, w = predict.size() 38 | target_mask = (target >= 0) * (target != self.ignore_label) 39 | target = target[target_mask] 40 | predict = predict.transpose(1, 2).transpose(2, 3).contiguous() 41 | predict = predict[target_mask.view(n, h, w, 1).repeat(1, 1, 1, c)].view(-1, c) 42 | loss = F.cross_entropy(predict, target, weight=self.weights, reduction=self.reduction) 43 | return loss 44 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/losses/style_loss.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torchvision.models as models 4 | 5 | 6 | class PerceptualLoss(nn.Module): 7 | r""" 8 | Perceptual loss, VGG-based 9 | https://arxiv.org/abs/1603.08155 10 | https://github.com/dxyang/StyleTransfer/blob/master/utils.py 11 | """ 12 | 13 | def __init__(self, weights=[1.0, 1.0, 1.0, 1.0, 1.0]): 14 | super(PerceptualLoss, self).__init__() 15 | self.add_module('vgg', VGG19()) 16 | self.criterion = torch.nn.L1Loss() 17 | self.weights = weights 18 | 19 | def __call__(self, x, y): 20 | # Compute features 21 | x_vgg, y_vgg = self.vgg(x), self.vgg(y) 22 | 23 | content_loss = 0.0 24 | content_loss += self.weights[0] * self.criterion(x_vgg['relu1_1'], y_vgg['relu1_1']) 25 | content_loss += self.weights[1] * self.criterion(x_vgg['relu2_1'], y_vgg['relu2_1']) 26 | content_loss += self.weights[2] * self.criterion(x_vgg['relu3_1'], y_vgg['relu3_1']) 27 | content_loss += self.weights[3] * self.criterion(x_vgg['relu4_1'], y_vgg['relu4_1']) 28 | content_loss += self.weights[4] * self.criterion(x_vgg['relu5_1'], y_vgg['relu5_1']) 29 | 30 | 31 | return content_loss 32 | 33 | 34 | class VGG19(torch.nn.Module): 35 | def __init__(self): 36 | super(VGG19, self).__init__() 37 | features = models.vgg19(pretrained=True).features 38 | self.relu1_1 = torch.nn.Sequential() 39 | self.relu1_2 = torch.nn.Sequential() 40 | 41 | self.relu2_1 = torch.nn.Sequential() 42 | self.relu2_2 = torch.nn.Sequential() 43 | 44 | self.relu3_1 = torch.nn.Sequential() 45 | self.relu3_2 = torch.nn.Sequential() 46 | self.relu3_3 = torch.nn.Sequential() 47 | self.relu3_4 = torch.nn.Sequential() 48 | 49 | self.relu4_1 = torch.nn.Sequential() 50 | self.relu4_2 = torch.nn.Sequential() 51 | self.relu4_3 = torch.nn.Sequential() 52 | self.relu4_4 = torch.nn.Sequential() 53 | 54 | self.relu5_1 = torch.nn.Sequential() 55 | self.relu5_2 = torch.nn.Sequential() 56 | self.relu5_3 = torch.nn.Sequential() 57 | self.relu5_4 = torch.nn.Sequential() 58 | 59 | for x in range(2): 60 | self.relu1_1.add_module(str(x), features[x]) 61 | 62 | for x in range(2, 4): 63 | self.relu1_2.add_module(str(x), features[x]) 64 | 65 | for x in range(4, 7): 66 | self.relu2_1.add_module(str(x), features[x]) 67 | 68 | for x in range(7, 9): 69 | self.relu2_2.add_module(str(x), features[x]) 70 | 71 | for x in range(9, 12): 72 | self.relu3_1.add_module(str(x), features[x]) 73 | 74 | for x in range(12, 14): 75 | self.relu3_2.add_module(str(x), features[x]) 76 | 77 | for x in range(14, 16): 78 | self.relu3_2.add_module(str(x), features[x]) 79 | 80 | for x in range(16, 18): 81 | self.relu3_4.add_module(str(x), features[x]) 82 | 83 | for x in range(18, 21): 84 | self.relu4_1.add_module(str(x), features[x]) 85 | 86 | for x in range(21, 23): 87 | self.relu4_2.add_module(str(x), features[x]) 88 | 89 | for x in range(23, 25): 90 | self.relu4_3.add_module(str(x), features[x]) 91 | 92 | for x in range(25, 27): 93 | self.relu4_4.add_module(str(x), features[x]) 94 | 95 | for x in range(27, 30): 96 | self.relu5_1.add_module(str(x), features[x]) 97 | 98 | for x in range(30, 32): 99 | self.relu5_2.add_module(str(x), features[x]) 100 | 101 | for x in range(32, 34): 102 | self.relu5_3.add_module(str(x), features[x]) 103 | 104 | for x in range(34, 36): 105 | self.relu5_4.add_module(str(x), features[x]) 106 | 107 | # don't need the gradients, just want the features 108 | for param in self.parameters(): 109 | param.requires_grad = False 110 | 111 | def forward(self, x): 112 | relu1_1 = self.relu1_1(x) 113 | relu1_2 = self.relu1_2(relu1_1) 114 | 115 | relu2_1 = self.relu2_1(relu1_2) 116 | relu2_2 = self.relu2_2(relu2_1) 117 | 118 | relu3_1 = self.relu3_1(relu2_2) 119 | relu3_2 = self.relu3_2(relu3_1) 120 | relu3_3 = self.relu3_3(relu3_2) 121 | relu3_4 = self.relu3_4(relu3_3) 122 | 123 | relu4_1 = self.relu4_1(relu3_4) 124 | relu4_2 = self.relu4_2(relu4_1) 125 | relu4_3 = self.relu4_3(relu4_2) 126 | relu4_4 = self.relu4_4(relu4_3) 127 | 128 | relu5_1 = self.relu5_1(relu4_4) 129 | relu5_2 = self.relu5_2(relu5_1) 130 | relu5_3 = self.relu5_3(relu5_2) 131 | relu5_4 = self.relu5_4(relu5_3) 132 | 133 | out = { 134 | 'relu1_1': relu1_1, 135 | 'relu1_2': relu1_2, 136 | 137 | 'relu2_1': relu2_1, 138 | 'relu2_2': relu2_2, 139 | 140 | 'relu3_1': relu3_1, 141 | 'relu3_2': relu3_2, 142 | 'relu3_3': relu3_3, 143 | 'relu3_4': relu3_4, 144 | 145 | 'relu4_1': relu4_1, 146 | 'relu4_2': relu4_2, 147 | 'relu4_3': relu4_3, 148 | 'relu4_4': relu4_4, 149 | 150 | 'relu5_1': relu5_1, 151 | 'relu5_2': relu5_2, 152 | 'relu5_3': relu5_3, 153 | 'relu5_4': relu5_4, 154 | } 155 | return out 156 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/modules/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sd_bmab.external.lama.saicinpainting.training.modules.ffc import FFCResNetGenerator 4 | from sd_bmab.external.lama.saicinpainting.training.modules.pix2pixhd import GlobalGenerator, MultiDilatedGlobalGenerator, \ 5 | NLayerDiscriminator, MultidilatedNLayerDiscriminator 6 | 7 | 8 | def make_generator(config, kind, **kwargs): 9 | logging.info(f'Make generator {kind}') 10 | 11 | if kind == 'pix2pixhd_multidilated': 12 | return MultiDilatedGlobalGenerator(**kwargs) 13 | 14 | if kind == 'pix2pixhd_global': 15 | return GlobalGenerator(**kwargs) 16 | 17 | if kind == 'ffc_resnet': 18 | return FFCResNetGenerator(**kwargs) 19 | 20 | raise ValueError(f'Unknown generator kind {kind}') 21 | 22 | 23 | def make_discriminator(kind, **kwargs): 24 | logging.info(f'Make discriminator {kind}') 25 | 26 | if kind == 'pix2pixhd_nlayer_multidilated': 27 | return MultidilatedNLayerDiscriminator(**kwargs) 28 | 29 | if kind == 'pix2pixhd_nlayer': 30 | return NLayerDiscriminator(**kwargs) 31 | 32 | raise ValueError(f'Unknown discriminator kind {kind}') 33 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/modules/base.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Tuple, List 3 | 4 | import torch 5 | import torch.nn as nn 6 | 7 | from sd_bmab.external.lama.saicinpainting.training.modules.depthwise_sep_conv import DepthWiseSeperableConv 8 | from sd_bmab.external.lama.saicinpainting.training.modules.multidilated_conv import MultidilatedConv 9 | 10 | 11 | class BaseDiscriminator(nn.Module): 12 | @abc.abstractmethod 13 | def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, List[torch.Tensor]]: 14 | """ 15 | Predict scores and get intermediate activations. Useful for feature matching loss 16 | :return tuple (scores, list of intermediate activations) 17 | """ 18 | raise NotImplemented() 19 | 20 | 21 | def get_conv_block_ctor(kind='default'): 22 | if not isinstance(kind, str): 23 | return kind 24 | if kind == 'default': 25 | return nn.Conv2d 26 | if kind == 'depthwise': 27 | return DepthWiseSeperableConv 28 | if kind == 'multidilated': 29 | return MultidilatedConv 30 | raise ValueError(f'Unknown convolutional block kind {kind}') 31 | 32 | 33 | def get_norm_layer(kind='bn'): 34 | if not isinstance(kind, str): 35 | return kind 36 | if kind == 'bn': 37 | return nn.BatchNorm2d 38 | if kind == 'in': 39 | return nn.InstanceNorm2d 40 | raise ValueError(f'Unknown norm block kind {kind}') 41 | 42 | 43 | def get_activation(kind='tanh'): 44 | if kind == 'tanh': 45 | return nn.Tanh() 46 | if kind == 'sigmoid': 47 | return nn.Sigmoid() 48 | if kind is False: 49 | return nn.Identity() 50 | raise ValueError(f'Unknown activation kind {kind}') 51 | 52 | 53 | class SimpleMultiStepGenerator(nn.Module): 54 | def __init__(self, steps: List[nn.Module]): 55 | super().__init__() 56 | self.steps = nn.ModuleList(steps) 57 | 58 | def forward(self, x): 59 | cur_in = x 60 | outs = [] 61 | for step in self.steps: 62 | cur_out = step(cur_in) 63 | outs.append(cur_out) 64 | cur_in = torch.cat((cur_in, cur_out), dim=1) 65 | return torch.cat(outs[::-1], dim=1) 66 | 67 | def deconv_factory(kind, ngf, mult, norm_layer, activation, max_features): 68 | if kind == 'convtranspose': 69 | return [nn.ConvTranspose2d(min(max_features, ngf * mult), 70 | min(max_features, int(ngf * mult / 2)), 71 | kernel_size=3, stride=2, padding=1, output_padding=1), 72 | norm_layer(min(max_features, int(ngf * mult / 2))), activation] 73 | elif kind == 'bilinear': 74 | return [nn.Upsample(scale_factor=2, mode='bilinear'), 75 | DepthWiseSeperableConv(min(max_features, ngf * mult), 76 | min(max_features, int(ngf * mult / 2)), 77 | kernel_size=3, stride=1, padding=1), 78 | norm_layer(min(max_features, int(ngf * mult / 2))), activation] 79 | else: 80 | raise Exception(f"Invalid deconv kind: {kind}") -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/modules/depthwise_sep_conv.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | class DepthWiseSeperableConv(nn.Module): 5 | def __init__(self, in_dim, out_dim, *args, **kwargs): 6 | super().__init__() 7 | if 'groups' in kwargs: 8 | # ignoring groups for Depthwise Sep Conv 9 | del kwargs['groups'] 10 | 11 | self.depthwise = nn.Conv2d(in_dim, in_dim, *args, groups=in_dim, **kwargs) 12 | self.pointwise = nn.Conv2d(in_dim, out_dim, kernel_size=1) 13 | 14 | def forward(self, x): 15 | out = self.depthwise(x) 16 | out = self.pointwise(out) 17 | return out -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/modules/fake_fakes.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from kornia import SamplePadding 3 | from kornia.augmentation import RandomAffine, CenterCrop 4 | 5 | 6 | class FakeFakesGenerator: 7 | def __init__(self, aug_proba=0.5, img_aug_degree=30, img_aug_translate=0.2): 8 | self.grad_aug = RandomAffine(degrees=360, 9 | translate=0.2, 10 | padding_mode=SamplePadding.REFLECTION, 11 | keepdim=False, 12 | p=1) 13 | self.img_aug = RandomAffine(degrees=img_aug_degree, 14 | translate=img_aug_translate, 15 | padding_mode=SamplePadding.REFLECTION, 16 | keepdim=True, 17 | p=1) 18 | self.aug_proba = aug_proba 19 | 20 | def __call__(self, input_images, masks): 21 | blend_masks = self._fill_masks_with_gradient(masks) 22 | blend_target = self._make_blend_target(input_images) 23 | result = input_images * (1 - blend_masks) + blend_target * blend_masks 24 | return result, blend_masks 25 | 26 | def _make_blend_target(self, input_images): 27 | batch_size = input_images.shape[0] 28 | permuted = input_images[torch.randperm(batch_size)] 29 | augmented = self.img_aug(input_images) 30 | is_aug = (torch.rand(batch_size, device=input_images.device)[:, None, None, None] < self.aug_proba).float() 31 | result = augmented * is_aug + permuted * (1 - is_aug) 32 | return result 33 | 34 | def _fill_masks_with_gradient(self, masks): 35 | batch_size, _, height, width = masks.shape 36 | grad = torch.linspace(0, 1, steps=width * 2, device=masks.device, dtype=masks.dtype) \ 37 | .view(1, 1, 1, -1).expand(batch_size, 1, height * 2, width * 2) 38 | grad = self.grad_aug(grad) 39 | grad = CenterCrop((height, width))(grad) 40 | grad *= masks 41 | 42 | grad_for_min = grad + (1 - masks) * 10 43 | grad -= grad_for_min.view(batch_size, -1).min(-1).values[:, None, None, None] 44 | grad /= grad.view(batch_size, -1).max(-1).values[:, None, None, None] + 1e-6 45 | grad.clamp_(min=0, max=1) 46 | 47 | return grad 48 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/modules/multidilated_conv.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import random 4 | from sd_bmab.external.lama.saicinpainting.training.modules.depthwise_sep_conv import DepthWiseSeperableConv 5 | 6 | class MultidilatedConv(nn.Module): 7 | def __init__(self, in_dim, out_dim, kernel_size, dilation_num=3, comb_mode='sum', equal_dim=True, 8 | shared_weights=False, padding=1, min_dilation=1, shuffle_in_channels=False, use_depthwise=False, **kwargs): 9 | super().__init__() 10 | convs = [] 11 | self.equal_dim = equal_dim 12 | assert comb_mode in ('cat_out', 'sum', 'cat_in', 'cat_both'), comb_mode 13 | if comb_mode in ('cat_out', 'cat_both'): 14 | self.cat_out = True 15 | if equal_dim: 16 | assert out_dim % dilation_num == 0 17 | out_dims = [out_dim // dilation_num] * dilation_num 18 | self.index = sum([[i + j * (out_dims[0]) for j in range(dilation_num)] for i in range(out_dims[0])], []) 19 | else: 20 | out_dims = [out_dim // 2 ** (i + 1) for i in range(dilation_num - 1)] 21 | out_dims.append(out_dim - sum(out_dims)) 22 | index = [] 23 | starts = [0] + out_dims[:-1] 24 | lengths = [out_dims[i] // out_dims[-1] for i in range(dilation_num)] 25 | for i in range(out_dims[-1]): 26 | for j in range(dilation_num): 27 | index += list(range(starts[j], starts[j] + lengths[j])) 28 | starts[j] += lengths[j] 29 | self.index = index 30 | assert(len(index) == out_dim) 31 | self.out_dims = out_dims 32 | else: 33 | self.cat_out = False 34 | self.out_dims = [out_dim] * dilation_num 35 | 36 | if comb_mode in ('cat_in', 'cat_both'): 37 | if equal_dim: 38 | assert in_dim % dilation_num == 0 39 | in_dims = [in_dim // dilation_num] * dilation_num 40 | else: 41 | in_dims = [in_dim // 2 ** (i + 1) for i in range(dilation_num - 1)] 42 | in_dims.append(in_dim - sum(in_dims)) 43 | self.in_dims = in_dims 44 | self.cat_in = True 45 | else: 46 | self.cat_in = False 47 | self.in_dims = [in_dim] * dilation_num 48 | 49 | conv_type = DepthWiseSeperableConv if use_depthwise else nn.Conv2d 50 | dilation = min_dilation 51 | for i in range(dilation_num): 52 | if isinstance(padding, int): 53 | cur_padding = padding * dilation 54 | else: 55 | cur_padding = padding[i] 56 | convs.append(conv_type( 57 | self.in_dims[i], self.out_dims[i], kernel_size, padding=cur_padding, dilation=dilation, **kwargs 58 | )) 59 | if i > 0 and shared_weights: 60 | convs[-1].weight = convs[0].weight 61 | convs[-1].bias = convs[0].bias 62 | dilation *= 2 63 | self.convs = nn.ModuleList(convs) 64 | 65 | self.shuffle_in_channels = shuffle_in_channels 66 | if self.shuffle_in_channels: 67 | # shuffle list as shuffling of tensors is nondeterministic 68 | in_channels_permute = list(range(in_dim)) 69 | random.shuffle(in_channels_permute) 70 | # save as buffer so it is saved and loaded with checkpoint 71 | self.register_buffer('in_channels_permute', torch.tensor(in_channels_permute)) 72 | 73 | def forward(self, x): 74 | if self.shuffle_in_channels: 75 | x = x[:, self.in_channels_permute] 76 | 77 | outs = [] 78 | if self.cat_in: 79 | if self.equal_dim: 80 | x = x.chunk(len(self.convs), dim=1) 81 | else: 82 | new_x = [] 83 | start = 0 84 | for dim in self.in_dims: 85 | new_x.append(x[:, start:start+dim]) 86 | start += dim 87 | x = new_x 88 | for i, conv in enumerate(self.convs): 89 | if self.cat_in: 90 | input = x[i] 91 | else: 92 | input = x 93 | outs.append(conv(input)) 94 | if self.cat_out: 95 | out = torch.cat(outs, dim=1)[:, self.index] 96 | else: 97 | out = sum(outs) 98 | return out 99 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/modules/spatial_transform.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | from kornia.geometry.transform import rotate 5 | 6 | 7 | class LearnableSpatialTransformWrapper(nn.Module): 8 | def __init__(self, impl, pad_coef=0.5, angle_init_range=80, train_angle=True): 9 | super().__init__() 10 | self.impl = impl 11 | self.angle = torch.rand(1) * angle_init_range 12 | if train_angle: 13 | self.angle = nn.Parameter(self.angle, requires_grad=True) 14 | self.pad_coef = pad_coef 15 | 16 | def forward(self, x): 17 | if torch.is_tensor(x): 18 | return self.inverse_transform(self.impl(self.transform(x)), x) 19 | elif isinstance(x, tuple): 20 | x_trans = tuple(self.transform(elem) for elem in x) 21 | y_trans = self.impl(x_trans) 22 | return tuple(self.inverse_transform(elem, orig_x) for elem, orig_x in zip(y_trans, x)) 23 | else: 24 | raise ValueError(f'Unexpected input type {type(x)}') 25 | 26 | def transform(self, x): 27 | height, width = x.shape[2:] 28 | pad_h, pad_w = int(height * self.pad_coef), int(width * self.pad_coef) 29 | x_padded = F.pad(x, [pad_w, pad_w, pad_h, pad_h], mode='reflect') 30 | x_padded_rotated = rotate(x_padded, angle=self.angle.to(x_padded)) 31 | return x_padded_rotated 32 | 33 | def inverse_transform(self, y_padded_rotated, orig_x): 34 | height, width = orig_x.shape[2:] 35 | pad_h, pad_w = int(height * self.pad_coef), int(width * self.pad_coef) 36 | 37 | y_padded = rotate(y_padded_rotated, angle=-self.angle.to(y_padded_rotated)) 38 | y_height, y_width = y_padded.shape[2:] 39 | y = y_padded[:, :, pad_h : y_height - pad_h, pad_w : y_width - pad_w] 40 | return y 41 | 42 | 43 | if __name__ == '__main__': 44 | layer = LearnableSpatialTransformWrapper(nn.Identity()) 45 | x = torch.arange(2* 3 * 15 * 15).view(2, 3, 15, 15).float() 46 | y = layer(x) 47 | assert x.shape == y.shape 48 | assert torch.allclose(x[:, :, 1:, 1:][:, :, :-1, :-1], y[:, :, 1:, 1:][:, :, :-1, :-1]) 49 | print('all ok') 50 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/modules/squeeze_excitation.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | 3 | 4 | class SELayer(nn.Module): 5 | def __init__(self, channel, reduction=16): 6 | super(SELayer, self).__init__() 7 | self.avg_pool = nn.AdaptiveAvgPool2d(1) 8 | self.fc = nn.Sequential( 9 | nn.Linear(channel, channel // reduction, bias=False), 10 | nn.ReLU(inplace=True), 11 | nn.Linear(channel // reduction, channel, bias=False), 12 | nn.Sigmoid() 13 | ) 14 | 15 | def forward(self, x): 16 | b, c, _, _ = x.size() 17 | y = self.avg_pool(x).view(b, c) 18 | y = self.fc(y).view(b, c, 1, 1) 19 | res = x * y.expand_as(x) 20 | return res 21 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/trainers/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import torch 3 | from sd_bmab.external.lama.saicinpainting.training.trainers.default import DefaultInpaintingTrainingModule 4 | 5 | 6 | def get_training_model_class(kind): 7 | if kind == 'default': 8 | return DefaultInpaintingTrainingModule 9 | 10 | raise ValueError(f'Unknown trainer module {kind}') 11 | 12 | 13 | def make_training_model(config): 14 | kind = config.training_model.kind 15 | kwargs = dict(config.training_model) 16 | kwargs.pop('kind') 17 | kwargs['use_ddp'] = config.trainer.kwargs.get('accelerator', None) == 'ddp' 18 | 19 | logging.info(f'Make training model {kind}') 20 | 21 | cls = get_training_model_class(kind) 22 | return cls(config, **kwargs) 23 | 24 | 25 | def load_checkpoint(train_config, path, map_location='cuda', strict=True): 26 | model = make_training_model(train_config).generator 27 | state = torch.load(path, map_location=map_location) 28 | model.load_state_dict(state, strict=strict) 29 | return model 30 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/visualizers/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sd_bmab.external.lama.saicinpainting.training.visualizers.directory import DirectoryVisualizer 4 | from sd_bmab.external.lama.saicinpainting.training.visualizers.noop import NoopVisualizer 5 | 6 | 7 | def make_visualizer(kind, **kwargs): 8 | logging.info(f'Make visualizer {kind}') 9 | 10 | if kind == 'directory': 11 | return DirectoryVisualizer(**kwargs) 12 | if kind == 'noop': 13 | return NoopVisualizer() 14 | 15 | raise ValueError(f'Unknown visualizer kind {kind}') 16 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/visualizers/base.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Dict, List 3 | 4 | import numpy as np 5 | import torch 6 | from skimage import color 7 | from skimage.segmentation import mark_boundaries 8 | 9 | from . import colors 10 | 11 | COLORS, _ = colors.generate_colors(151) # 151 - max classes for semantic segmentation 12 | 13 | 14 | class BaseVisualizer: 15 | @abc.abstractmethod 16 | def __call__(self, epoch_i, batch_i, batch, suffix='', rank=None): 17 | """ 18 | Take a batch, make an image from it and visualize 19 | """ 20 | raise NotImplementedError() 21 | 22 | 23 | def visualize_mask_and_images(images_dict: Dict[str, np.ndarray], keys: List[str], 24 | last_without_mask=True, rescale_keys=None, mask_only_first=None, 25 | black_mask=False) -> np.ndarray: 26 | mask = images_dict['mask'] > 0.5 27 | result = [] 28 | for i, k in enumerate(keys): 29 | img = images_dict[k] 30 | img = np.transpose(img, (1, 2, 0)) 31 | 32 | if rescale_keys is not None and k in rescale_keys: 33 | img = img - img.min() 34 | img /= img.max() + 1e-5 35 | if len(img.shape) == 2: 36 | img = np.expand_dims(img, 2) 37 | 38 | if img.shape[2] == 1: 39 | img = np.repeat(img, 3, axis=2) 40 | elif (img.shape[2] > 3): 41 | img_classes = img.argmax(2) 42 | img = color.label2rgb(img_classes, colors=COLORS) 43 | 44 | if mask_only_first: 45 | need_mark_boundaries = i == 0 46 | else: 47 | need_mark_boundaries = i < len(keys) - 1 or not last_without_mask 48 | 49 | if need_mark_boundaries: 50 | if black_mask: 51 | img = img * (1 - mask[0][..., None]) 52 | img = mark_boundaries(img, 53 | mask[0], 54 | color=(1., 0., 0.), 55 | outline_color=(1., 1., 1.), 56 | mode='thick') 57 | result.append(img) 58 | return np.concatenate(result, axis=1) 59 | 60 | 61 | def visualize_mask_and_images_batch(batch: Dict[str, torch.Tensor], keys: List[str], max_items=10, 62 | last_without_mask=True, rescale_keys=None) -> np.ndarray: 63 | batch = {k: tens.detach().cpu().numpy() for k, tens in batch.items() 64 | if k in keys or k == 'mask'} 65 | 66 | batch_size = next(iter(batch.values())).shape[0] 67 | items_to_vis = min(batch_size, max_items) 68 | result = [] 69 | for i in range(items_to_vis): 70 | cur_dct = {k: tens[i] for k, tens in batch.items()} 71 | result.append(visualize_mask_and_images(cur_dct, keys, last_without_mask=last_without_mask, 72 | rescale_keys=rescale_keys)) 73 | return np.concatenate(result, axis=0) 74 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/visualizers/colors.py: -------------------------------------------------------------------------------- 1 | import random 2 | import colorsys 3 | 4 | import numpy as np 5 | import matplotlib 6 | matplotlib.use('agg') 7 | import matplotlib.pyplot as plt 8 | from matplotlib.colors import LinearSegmentedColormap 9 | 10 | 11 | def generate_colors(nlabels, type='bright', first_color_black=False, last_color_black=True, verbose=False): 12 | # https://stackoverflow.com/questions/14720331/how-to-generate-random-colors-in-matplotlib 13 | """ 14 | Creates a random colormap to be used together with matplotlib. Useful for segmentation tasks 15 | :param nlabels: Number of labels (size of colormap) 16 | :param type: 'bright' for strong colors, 'soft' for pastel colors 17 | :param first_color_black: Option to use first color as black, True or False 18 | :param last_color_black: Option to use last color as black, True or False 19 | :param verbose: Prints the number of labels and shows the colormap. True or False 20 | :return: colormap for matplotlib 21 | """ 22 | if type not in ('bright', 'soft'): 23 | print ('Please choose "bright" or "soft" for type') 24 | return 25 | 26 | if verbose: 27 | print('Number of labels: ' + str(nlabels)) 28 | 29 | # Generate color map for bright colors, based on hsv 30 | if type == 'bright': 31 | randHSVcolors = [(np.random.uniform(low=0.0, high=1), 32 | np.random.uniform(low=0.2, high=1), 33 | np.random.uniform(low=0.9, high=1)) for i in range(nlabels)] 34 | 35 | # Convert HSV list to RGB 36 | randRGBcolors = [] 37 | for HSVcolor in randHSVcolors: 38 | randRGBcolors.append(colorsys.hsv_to_rgb(HSVcolor[0], HSVcolor[1], HSVcolor[2])) 39 | 40 | if first_color_black: 41 | randRGBcolors[0] = [0, 0, 0] 42 | 43 | if last_color_black: 44 | randRGBcolors[-1] = [0, 0, 0] 45 | 46 | random_colormap = LinearSegmentedColormap.from_list('new_map', randRGBcolors, N=nlabels) 47 | 48 | # Generate soft pastel colors, by limiting the RGB spectrum 49 | if type == 'soft': 50 | low = 0.6 51 | high = 0.95 52 | randRGBcolors = [(np.random.uniform(low=low, high=high), 53 | np.random.uniform(low=low, high=high), 54 | np.random.uniform(low=low, high=high)) for i in range(nlabels)] 55 | 56 | if first_color_black: 57 | randRGBcolors[0] = [0, 0, 0] 58 | 59 | if last_color_black: 60 | randRGBcolors[-1] = [0, 0, 0] 61 | random_colormap = LinearSegmentedColormap.from_list('new_map', randRGBcolors, N=nlabels) 62 | 63 | # Display colorbar 64 | if verbose: 65 | from matplotlib import colors, colorbar 66 | from matplotlib import pyplot as plt 67 | fig, ax = plt.subplots(1, 1, figsize=(15, 0.5)) 68 | 69 | bounds = np.linspace(0, nlabels, nlabels + 1) 70 | norm = colors.BoundaryNorm(bounds, nlabels) 71 | 72 | cb = colorbar.ColorbarBase(ax, cmap=random_colormap, norm=norm, spacing='proportional', ticks=None, 73 | boundaries=bounds, format='%1i', orientation=u'horizontal') 74 | 75 | return randRGBcolors, random_colormap 76 | 77 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/visualizers/directory.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import cv2 4 | import numpy as np 5 | 6 | from sd_bmab.external.lama.saicinpainting.training.visualizers.base import BaseVisualizer, visualize_mask_and_images_batch 7 | from sd_bmab.external.lama.saicinpainting.utils import check_and_warn_input_range 8 | 9 | 10 | class DirectoryVisualizer(BaseVisualizer): 11 | DEFAULT_KEY_ORDER = 'image predicted_image inpainted'.split(' ') 12 | 13 | def __init__(self, outdir, key_order=DEFAULT_KEY_ORDER, max_items_in_batch=10, 14 | last_without_mask=True, rescale_keys=None): 15 | self.outdir = outdir 16 | os.makedirs(self.outdir, exist_ok=True) 17 | self.key_order = key_order 18 | self.max_items_in_batch = max_items_in_batch 19 | self.last_without_mask = last_without_mask 20 | self.rescale_keys = rescale_keys 21 | 22 | def __call__(self, epoch_i, batch_i, batch, suffix='', rank=None): 23 | check_and_warn_input_range(batch['image'], 0, 1, 'DirectoryVisualizer target image') 24 | vis_img = visualize_mask_and_images_batch(batch, self.key_order, max_items=self.max_items_in_batch, 25 | last_without_mask=self.last_without_mask, 26 | rescale_keys=self.rescale_keys) 27 | 28 | vis_img = np.clip(vis_img * 255, 0, 255).astype('uint8') 29 | 30 | curoutdir = os.path.join(self.outdir, f'epoch{epoch_i:04d}{suffix}') 31 | os.makedirs(curoutdir, exist_ok=True) 32 | rank_suffix = f'_r{rank}' if rank is not None else '' 33 | out_fname = os.path.join(curoutdir, f'batch{batch_i:07d}{rank_suffix}.jpg') 34 | 35 | vis_img = cv2.cvtColor(vis_img, cv2.COLOR_RGB2BGR) 36 | cv2.imwrite(out_fname, vis_img) 37 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/training/visualizers/noop.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.external.lama.saicinpainting.training.visualizers.base import BaseVisualizer 2 | 3 | 4 | class NoopVisualizer(BaseVisualizer): 5 | def __init__(self, *args, **kwargs): 6 | pass 7 | 8 | def __call__(self, epoch_i, batch_i, batch, suffix='', rank=None): 9 | pass 10 | -------------------------------------------------------------------------------- /sd_bmab/external/lama/saicinpainting/utils.py: -------------------------------------------------------------------------------- 1 | import bisect 2 | import functools 3 | import logging 4 | import numbers 5 | import os 6 | import signal 7 | import sys 8 | import traceback 9 | import warnings 10 | 11 | import torch 12 | from pytorch_lightning import seed_everything 13 | 14 | LOGGER = logging.getLogger(__name__) 15 | 16 | 17 | def check_and_warn_input_range(tensor, min_value, max_value, name): 18 | actual_min = tensor.min() 19 | actual_max = tensor.max() 20 | if actual_min < min_value or actual_max > max_value: 21 | warnings.warn(f"{name} must be in {min_value}..{max_value} range, but it ranges {actual_min}..{actual_max}") 22 | 23 | 24 | def sum_dict_with_prefix(target, cur_dict, prefix, default=0): 25 | for k, v in cur_dict.items(): 26 | target_key = prefix + k 27 | target[target_key] = target.get(target_key, default) + v 28 | 29 | 30 | def average_dicts(dict_list): 31 | result = {} 32 | norm = 1e-3 33 | for dct in dict_list: 34 | sum_dict_with_prefix(result, dct, '') 35 | norm += 1 36 | for k in list(result): 37 | result[k] /= norm 38 | return result 39 | 40 | 41 | def add_prefix_to_keys(dct, prefix): 42 | return {prefix + k: v for k, v in dct.items()} 43 | 44 | 45 | def set_requires_grad(module, value): 46 | for param in module.parameters(): 47 | param.requires_grad = value 48 | 49 | 50 | def flatten_dict(dct): 51 | result = {} 52 | for k, v in dct.items(): 53 | if isinstance(k, tuple): 54 | k = '_'.join(k) 55 | if isinstance(v, dict): 56 | for sub_k, sub_v in flatten_dict(v).items(): 57 | result[f'{k}_{sub_k}'] = sub_v 58 | else: 59 | result[k] = v 60 | return result 61 | 62 | 63 | class LinearRamp: 64 | def __init__(self, start_value=0, end_value=1, start_iter=-1, end_iter=0): 65 | self.start_value = start_value 66 | self.end_value = end_value 67 | self.start_iter = start_iter 68 | self.end_iter = end_iter 69 | 70 | def __call__(self, i): 71 | if i < self.start_iter: 72 | return self.start_value 73 | if i >= self.end_iter: 74 | return self.end_value 75 | part = (i - self.start_iter) / (self.end_iter - self.start_iter) 76 | return self.start_value * (1 - part) + self.end_value * part 77 | 78 | 79 | class LadderRamp: 80 | def __init__(self, start_iters, values): 81 | self.start_iters = start_iters 82 | self.values = values 83 | assert len(values) == len(start_iters) + 1, (len(values), len(start_iters)) 84 | 85 | def __call__(self, i): 86 | segment_i = bisect.bisect_right(self.start_iters, i) 87 | return self.values[segment_i] 88 | 89 | 90 | def get_ramp(kind='ladder', **kwargs): 91 | if kind == 'linear': 92 | return LinearRamp(**kwargs) 93 | if kind == 'ladder': 94 | return LadderRamp(**kwargs) 95 | raise ValueError(f'Unexpected ramp kind: {kind}') 96 | 97 | 98 | def print_traceback_handler(sig, frame): 99 | LOGGER.warning(f'Received signal {sig}') 100 | bt = ''.join(traceback.format_stack()) 101 | LOGGER.warning(f'Requested stack trace:\n{bt}') 102 | 103 | 104 | def register_debug_signal_handlers(sig=None, handler=print_traceback_handler): 105 | LOGGER.warning(f'Setting signal {sig} handler {handler}') 106 | signal.signal(sig, handler) 107 | 108 | 109 | def handle_deterministic_config(config): 110 | seed = dict(config).get('seed', None) 111 | if seed is None: 112 | return False 113 | 114 | seed_everything(seed) 115 | return True 116 | 117 | 118 | def get_shape(t): 119 | if torch.is_tensor(t): 120 | return tuple(t.shape) 121 | elif isinstance(t, dict): 122 | return {n: get_shape(q) for n, q in t.items()} 123 | elif isinstance(t, (list, tuple)): 124 | return [get_shape(q) for q in t] 125 | elif isinstance(t, numbers.Number): 126 | return type(t) 127 | else: 128 | raise ValueError('unexpected type {}'.format(type(t))) 129 | 130 | 131 | def get_has_ddp_rank(): 132 | master_port = os.environ.get('MASTER_PORT', None) 133 | node_rank = os.environ.get('NODE_RANK', None) 134 | local_rank = os.environ.get('LOCAL_RANK', None) 135 | world_size = os.environ.get('WORLD_SIZE', None) 136 | has_rank = master_port is not None or node_rank is not None or local_rank is not None or world_size is not None 137 | return has_rank 138 | 139 | 140 | def handle_ddp_subprocess(): 141 | def main_decorator(main_func): 142 | @functools.wraps(main_func) 143 | def new_main(*args, **kwargs): 144 | # Trainer sets MASTER_PORT, NODE_RANK, LOCAL_RANK, WORLD_SIZE 145 | parent_cwd = os.environ.get('TRAINING_PARENT_WORK_DIR', None) 146 | has_parent = parent_cwd is not None 147 | has_rank = get_has_ddp_rank() 148 | assert has_parent == has_rank, f'Inconsistent state: has_parent={has_parent}, has_rank={has_rank}' 149 | 150 | if has_parent: 151 | # we are in the worker 152 | sys.argv.extend([ 153 | f'hydra.run.dir={parent_cwd}', 154 | # 'hydra/hydra_logging=disabled', 155 | # 'hydra/job_logging=disabled' 156 | ]) 157 | # do nothing if this is a top-level process 158 | # TRAINING_PARENT_WORK_DIR is set in handle_ddp_parent_process after hydra initialization 159 | 160 | main_func(*args, **kwargs) 161 | return new_main 162 | return main_decorator 163 | 164 | 165 | def handle_ddp_parent_process(): 166 | parent_cwd = os.environ.get('TRAINING_PARENT_WORK_DIR', None) 167 | has_parent = parent_cwd is not None 168 | has_rank = get_has_ddp_rank() 169 | assert has_parent == has_rank, f'Inconsistent state: has_parent={has_parent}, has_rank={has_rank}' 170 | 171 | if parent_cwd is None: 172 | os.environ['TRAINING_PARENT_WORK_DIR'] = os.getcwd() 173 | 174 | return has_parent 175 | -------------------------------------------------------------------------------- /sd_bmab/masking/__init__.py: -------------------------------------------------------------------------------- 1 | from modules import shared 2 | 3 | from sd_bmab.base import MaskBase 4 | from sd_bmab.masking.sam import SamPredictVitB 5 | from sd_bmab.masking.sam_hq import SamHqPredictVitB 6 | from sd_bmab.util import debug_print 7 | 8 | 9 | masks = [SamPredictVitB(), SamHqPredictVitB()] 10 | dmasks = {s.name: s for s in masks} 11 | 12 | 13 | def get_mask_generator(name='None') -> MaskBase: 14 | model = dmasks.get(name, dmasks[shared.opts.bmab_mask_model]) 15 | debug_print(f'Use mask model {model.name}') 16 | return model 17 | 18 | 19 | def list_mask_names(): 20 | return [s.name for s in masks] 21 | 22 | 23 | def release(): 24 | SamPredictVitB.release() 25 | SamHqPredictVitB.release() 26 | -------------------------------------------------------------------------------- /sd_bmab/masking/sam.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.base import MaskBase 2 | import cv2 3 | import numpy as np 4 | 5 | import torch 6 | 7 | from PIL import Image 8 | from modules.safe import unsafe_torch_load, load 9 | from modules.devices import device, torch_gc 10 | 11 | from segment_anything import SamPredictor 12 | from segment_anything import sam_model_registry 13 | 14 | from sd_bmab import util 15 | 16 | 17 | sam_model = None 18 | 19 | 20 | class SamPredict(MaskBase): 21 | 22 | def __init__(self) -> None: 23 | super().__init__() 24 | 25 | @property 26 | def name(self): 27 | return f'sam_{self.type}' 28 | 29 | @property 30 | def type(self): 31 | pass 32 | 33 | @property 34 | def file(self): 35 | pass 36 | 37 | @classmethod 38 | def init(cls, model_type, filename, *args, **kwargs): 39 | checkpoint_file = util.lazy_loader(filename) 40 | global sam_model 41 | if sam_model is None: 42 | torch.load = unsafe_torch_load 43 | sam_model = sam_model_registry[model_type](checkpoint=checkpoint_file) 44 | sam_model.to(device=device) 45 | torch.load = load 46 | return sam_model 47 | 48 | def load(self): 49 | return SamPredict.init(self.type, self.file) 50 | 51 | def predict(self, image, boxes): 52 | sam = self.load() 53 | mask_predictor = SamPredictor(sam) 54 | 55 | numpy_image = np.array(image) 56 | opencv_image = cv2.cvtColor(numpy_image, cv2.COLOR_RGB2BGR) 57 | mask_predictor.set_image(opencv_image) 58 | 59 | result = Image.new('L', image.size, 0) 60 | for box in boxes: 61 | x1, y1, x2, y2 = box 62 | 63 | box = np.array([int(x1), int(y1), int(x2), int(y2)]) 64 | masks, scores, logits = mask_predictor.predict( 65 | box=box, 66 | multimask_output=False 67 | ) 68 | 69 | mask = Image.fromarray(masks[0]) 70 | result.paste(mask, mask=mask) 71 | return result 72 | 73 | def predict_multiple(self, image, points, labels, boxes=None): 74 | sam = self.load() 75 | mask_predictor = SamPredictor(sam) 76 | 77 | numpy_image = np.array(image) 78 | opencv_image = cv2.cvtColor(numpy_image, cv2.COLOR_RGB2BGR) 79 | mask_predictor.set_image(opencv_image) 80 | 81 | result = Image.new('L', image.size, 0) 82 | kwargs = dict(multimask_output=True) 83 | if len(points) > 0: 84 | kwargs['point_coords'] = points 85 | kwargs['point_labels'] = labels 86 | if boxes is not None and boxes: 87 | for box in boxes: 88 | x1, y1, x2, y2 = box 89 | box = np.array([int(x1), int(y1), int(x2), int(y2)]) 90 | masks, scores, logits = mask_predictor.predict( 91 | box=box, 92 | **kwargs 93 | ) 94 | for mask in masks: 95 | mask = Image.fromarray(mask) 96 | result.paste(mask, mask=mask) 97 | else: 98 | masks, scores, logits = mask_predictor.predict( 99 | **kwargs 100 | ) 101 | for mask in masks: 102 | mask = Image.fromarray(mask) 103 | result.paste(mask, mask=mask) 104 | return result 105 | 106 | @classmethod 107 | def release(cls): 108 | global sam_model 109 | if sam_model is not None: 110 | sam_model = None 111 | torch_gc() 112 | 113 | 114 | class SamPredictVitB(SamPredict): 115 | 116 | @property 117 | def type(self): 118 | return 'vit_b' 119 | 120 | @property 121 | def file(self): 122 | return 'sam_vit_b_01ec64.pth' 123 | -------------------------------------------------------------------------------- /sd_bmab/masking/sam_hq.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.base import MaskBase 2 | import cv2 3 | import numpy as np 4 | 5 | import torch 6 | 7 | from PIL import Image 8 | from modules.safe import unsafe_torch_load, load 9 | from modules.devices import device, torch_gc 10 | 11 | from segment_anything_hq import SamPredictor 12 | from segment_anything_hq import sam_model_registry 13 | 14 | from sd_bmab import util 15 | 16 | 17 | sam_hq_model = None 18 | 19 | 20 | class SamHqPredict(MaskBase): 21 | 22 | def __init__(self) -> None: 23 | super().__init__() 24 | 25 | @property 26 | def name(self): 27 | return f'sam_hq_{self.type}' 28 | 29 | @property 30 | def type(self): 31 | pass 32 | 33 | @property 34 | def file(self): 35 | pass 36 | 37 | @classmethod 38 | def init(cls, model_type, filename, *args, **kwargs): 39 | checkpoint_file = util.lazy_loader(filename) 40 | global sam_hq_model 41 | if sam_hq_model is None: 42 | torch.load = unsafe_torch_load 43 | sam_hq_model = sam_model_registry[model_type](checkpoint=checkpoint_file) 44 | sam_hq_model.to(device=device) 45 | torch.load = load 46 | return sam_hq_model 47 | 48 | def load(self): 49 | return SamHqPredict.init(self.type, self.file) 50 | 51 | def predict(self, image, boxes): 52 | sam = self.load() 53 | mask_predictor = SamPredictor(sam) 54 | 55 | numpy_image = np.array(image) 56 | opencv_image = cv2.cvtColor(numpy_image, cv2.COLOR_RGB2BGR) 57 | mask_predictor.set_image(opencv_image) 58 | 59 | result = Image.new('L', image.size, 0) 60 | for box in boxes: 61 | x1, y1, x2, y2 = box 62 | 63 | box = np.array([int(x1), int(y1), int(x2), int(y2)]) 64 | masks, scores, logits = mask_predictor.predict( 65 | box=box, 66 | multimask_output=False 67 | ) 68 | 69 | mask = Image.fromarray(masks[0]) 70 | result.paste(mask, mask=mask) 71 | return result 72 | 73 | def predict_multiple(self, image, points, labels, boxes=None): 74 | sam = self.load() 75 | mask_predictor = SamPredictor(sam) 76 | 77 | numpy_image = np.array(image) 78 | opencv_image = cv2.cvtColor(numpy_image, cv2.COLOR_RGB2BGR) 79 | mask_predictor.set_image(opencv_image) 80 | 81 | result = Image.new('L', image.size, 0) 82 | kwargs = dict(multimask_output=True) 83 | if len(points) > 0: 84 | kwargs['point_coords'] = points 85 | kwargs['point_labels'] = labels 86 | if boxes is not None: 87 | for box in boxes: 88 | x1, y1, x2, y2 = box 89 | box = np.array([int(x1), int(y1), int(x2), int(y2)]) 90 | masks, scores, logits = mask_predictor.predict( 91 | box=box, 92 | **kwargs 93 | ) 94 | for mask in masks: 95 | mask = Image.fromarray(mask) 96 | result.paste(mask, mask=mask) 97 | else: 98 | masks, scores, logits = mask_predictor.predict( 99 | **kwargs 100 | ) 101 | for mask in masks: 102 | mask = Image.fromarray(mask) 103 | result.paste(mask, mask=mask) 104 | return result 105 | 106 | @classmethod 107 | def release(cls): 108 | global sam_hq_model 109 | if sam_hq_model is not None: 110 | sam_hq_model = None 111 | torch_gc() 112 | 113 | 114 | class SamHqPredictVitB(SamHqPredict): 115 | 116 | @property 117 | def type(self): 118 | return 'vit_b' 119 | 120 | @property 121 | def file(self): 122 | return 'sam_hq_vit_b.pth' 123 | -------------------------------------------------------------------------------- /sd_bmab/pipeline/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portu-sim/sd-webui-bmab/7a81441a72cef95cd9fdb732ee933bb04799dab3/sd_bmab/pipeline/__init__.py -------------------------------------------------------------------------------- /sd_bmab/pipeline/internal/__init__.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.pipeline.internal.internalpipe import process_img2img, process_intermediate_before_upscale, process_intermediate_after_upscale, is_controlnet_required 2 | from sd_bmab.pipeline.internal.intermediate import Preprocess 3 | -------------------------------------------------------------------------------- /sd_bmab/pipeline/internal/intermediate.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | from sd_bmab.base.context import Context 4 | from sd_bmab.base.processorbase import ProcessorBase 5 | from sd_bmab.pipeline.internal import process_intermediate_before_upscale, process_intermediate_after_upscale 6 | 7 | 8 | class Preprocess(ProcessorBase): 9 | def __init__(self) -> None: 10 | super().__init__() 11 | 12 | def preprocess(self, context: Context, image: Image): 13 | return context.is_txtimg() and not context.is_hires_fix() 14 | 15 | def process(self, context: Context, image: Image): 16 | image = process_intermediate_before_upscale(context, image) 17 | return process_intermediate_after_upscale(context, image) 18 | 19 | def postprocess(self, context: Context, image: Image): 20 | pass 21 | -------------------------------------------------------------------------------- /sd_bmab/pipeline/internal/internalpipe.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | from functools import partial 3 | from modules import images 4 | from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img 5 | from modules import shared 6 | 7 | from sd_bmab.util import debug_print 8 | from sd_bmab.base import Context 9 | from sd_bmab.processors.detailer import FaceDetailer, FaceDetailerBeforeUpsacle 10 | 11 | from sd_bmab.processors.basic import EdgeEnhancement, NoiseAlpha, Img2imgMasking, ICLightBeforeUpsacle 12 | from sd_bmab.processors.preprocess import ResizeIntermidiateBeforeUpscale 13 | from sd_bmab.processors.preprocess import ResamplePreprocessorBeforeUpscale, ResizeIntermidiateAfterUpsacle 14 | from sd_bmab.processors.preprocess import PretrainingDetailerBeforeUpscale 15 | 16 | 17 | def is_controlnet_required(context): 18 | pipeline_modules = [ 19 | ResamplePreprocessorBeforeUpscale(), 20 | ResizeIntermidiateBeforeUpscale() 21 | ] 22 | for mod in pipeline_modules: 23 | if mod.use_controlnet(context): 24 | return True 25 | return False 26 | 27 | 28 | def process_intermediate_before_upscale(context, image): 29 | pipeline_before_upscale = [ 30 | ResamplePreprocessorBeforeUpscale(), 31 | PretrainingDetailerBeforeUpscale(), 32 | FaceDetailerBeforeUpsacle(), 33 | ICLightBeforeUpsacle(), 34 | ResizeIntermidiateBeforeUpscale(), 35 | ] 36 | 37 | processed = image.copy() 38 | for proc in pipeline_before_upscale: 39 | try: 40 | result = proc.preprocess(context, processed) 41 | if result is None or not result: 42 | continue 43 | if shared.state.interrupted or shared.state.skipped: 44 | break 45 | ret = proc.process(context, processed) 46 | if shared.state.interrupted or shared.state.skipped: 47 | break 48 | proc.postprocess(context, processed) 49 | processed = ret 50 | except Exception: 51 | traceback.print_exc() 52 | return processed 53 | 54 | 55 | def process_intermediate_after_upscale(context, image): 56 | pipeline_before_upscale = [ 57 | EdgeEnhancement(), 58 | ResizeIntermidiateAfterUpsacle(), 59 | Img2imgMasking(), 60 | NoiseAlpha(), 61 | ] 62 | 63 | processed = image.copy() 64 | for proc in pipeline_before_upscale: 65 | result = proc.preprocess(context, processed) 66 | if result is None or not result: 67 | continue 68 | if shared.state.interrupted or shared.state.skipped: 69 | break 70 | ret = proc.process(context, processed) 71 | proc.postprocess(context, processed) 72 | processed = ret 73 | return processed 74 | 75 | 76 | def process_img2img(ctx): 77 | if not ctx.is_img2img(): 78 | return 79 | 80 | image = ctx.sdprocessing.init_images[0] 81 | debug_print('process img2img ', image.size) 82 | image = process_intermediate_before_upscale(ctx, image) 83 | image = process_intermediate_after_upscale(ctx, image) 84 | debug_print('process img2img ', image.size) 85 | ctx.sdprocessing.init_images[0] = image 86 | 87 | 88 | ''' 89 | def process_hiresfix(ctx): 90 | if not isinstance(ctx.sdprocessing, StableDiffusionProcessingTxt2Img): 91 | return 92 | 93 | if hasattr(ctx.sdprocessing, '__sample'): 94 | return 95 | 96 | all_processors = [ 97 | FaceDetailer(), 98 | EdgeEnhancement(), 99 | ResizeIntermidiateBeforeUpscale(), 100 | ResizeIntermidiateAfterUpsacle(), 101 | NoiseAlpha() 102 | ] 103 | 104 | if True not in [proc.preprocess(ctx, None) for proc in all_processors]: 105 | return 106 | 107 | ctx.sdprocessing.__sample = ctx.sdprocessing.sample 108 | 109 | def resize(ctx: Context, resize_mode, img, width, height, upscaler_name=None): 110 | images.resize_image = ctx.sdprocessing.resize_hook 111 | pidx = ctx.sdprocessing.iteration * ctx.sdprocessing.batch_size 112 | ctx.index += 1 113 | ctx.args['current_prompt'] = ctx.sdprocessing.all_prompts[pidx] 114 | img = process_intermediate_step1(ctx, img) 115 | im = ctx.sdprocessing.resize_hook(resize_mode, img, width, height, upscaler_name) 116 | im = process_intermediate_step2(ctx, im) 117 | images.resize_image = partial(resize, ctx) 118 | return im 119 | 120 | def _sample(ctx: Context, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts): 121 | ctx.sdprocessing.resize_hook = images.resize_image 122 | images.resize_image = partial(resize, ctx) 123 | try: 124 | ret = ctx.sdprocessing.__sample(conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts) 125 | except Exception as e: 126 | raise e 127 | finally: 128 | images.resize_image = ctx.sdprocessing.resize_hook 129 | return ret 130 | 131 | ctx.sdprocessing.sample = partial(_sample, ctx) 132 | ''' 133 | -------------------------------------------------------------------------------- /sd_bmab/pipeline/post/__init__.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.pipeline.post.mainpipe import process, process_controlnet, release, is_controlnet_required 2 | -------------------------------------------------------------------------------- /sd_bmab/pipeline/post/mainpipe.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | from modules import shared 4 | 5 | from sd_bmab.processors.postprocess import AfterProcessUpscaler, BeforeProcessUpscaler 6 | from sd_bmab.processors.postprocess import InpaintResize, InpaintLamaResize, FinalFilter 7 | from sd_bmab.processors.detailer import FaceDetailer, PersonDetailer, HandDetailer, PreprocessFaceDetailer 8 | from sd_bmab.processors.utils import BeforeProcessFileSaver, AfterProcessFileSaver 9 | from sd_bmab.processors.utils import ApplyModel, RollbackModel, CheckPointChanger, CheckPointRestore 10 | from sd_bmab.processors.basic import FinalProcessorBasic, ICLightAfterUpsacle 11 | from sd_bmab.processors.controlnet import LineartNoise, Openpose, IpAdapter 12 | from sd_bmab.processors.preprocess import RefinerPreprocessor, PretrainingDetailer 13 | from sd_bmab.processors.preprocess import ResamplePreprocessor, PreprocessFilter 14 | from sd_bmab.processors.postprocess import Watermark 15 | from sd_bmab.pipeline.internal import Preprocess 16 | from sd_bmab.util import debug_print 17 | 18 | 19 | def is_controlnet_required(context): 20 | pipeline_modules = [ 21 | ResamplePreprocessor(), 22 | InpaintLamaResize(), 23 | ] 24 | for mod in pipeline_modules: 25 | if mod.use_controlnet(context): 26 | return True 27 | return False 28 | 29 | 30 | def process(context, image): 31 | pipeline_modules = [ 32 | BeforeProcessFileSaver(), 33 | PreprocessFaceDetailer(), 34 | CheckPointChanger(), 35 | PreprocessFilter(), 36 | ResamplePreprocessor(), 37 | PretrainingDetailer(), 38 | Preprocess(), 39 | RefinerPreprocessor(), 40 | ICLightAfterUpsacle(), 41 | InpaintResize(), 42 | InpaintLamaResize(), 43 | BeforeProcessUpscaler(), 44 | ApplyModel(), 45 | PersonDetailer(), 46 | FaceDetailer(), 47 | HandDetailer(), 48 | RollbackModel(), 49 | CheckPointRestore(), 50 | AfterProcessUpscaler(), 51 | FinalProcessorBasic(), 52 | FinalFilter(), 53 | Watermark(), 54 | AfterProcessFileSaver() 55 | ] 56 | 57 | processed = image.copy() 58 | 59 | try: 60 | for proc in pipeline_modules: 61 | try: 62 | if shared.state.interrupted or shared.state.skipped: 63 | return 64 | 65 | result = proc.preprocess(context, processed) 66 | if result is None or not result: 67 | continue 68 | if shared.state.interrupted or shared.state.skipped: 69 | break 70 | ret = proc.process(context, processed) 71 | if shared.state.interrupted or shared.state.skipped: 72 | break 73 | proc.postprocess(context, processed) 74 | processed = ret 75 | except: 76 | traceback.print_exc() 77 | finally: 78 | debug_print('Restore Checkpoint at final') 79 | RollbackModel().process(context, processed) 80 | CheckPointRestore().process(context, processed) 81 | for proc in pipeline_modules: 82 | try: 83 | proc.finalprocess(context, processed) 84 | except: 85 | traceback.print_exc() 86 | return processed 87 | 88 | ''' 89 | def process_intermediate(context, image): 90 | all_processors = [ 91 | FaceDetailer(), 92 | EdgeEnhancement(), 93 | ResizeIntermidiate(), 94 | NoiseAlpha() 95 | ] 96 | 97 | processed = image.copy() 98 | 99 | for proc in all_processors: 100 | result = proc.preprocess(context, processed) 101 | if result is None or not result: 102 | continue 103 | ret = proc.process(context, processed) 104 | proc.postprocess(context, processed) 105 | processed = ret 106 | 107 | return processed 108 | ''' 109 | 110 | def process_controlnet(context): 111 | all_processors = [ 112 | LineartNoise(), 113 | Openpose(), 114 | IpAdapter() 115 | ] 116 | 117 | for proc in all_processors: 118 | result = proc.preprocess(context, None) 119 | if result is None or not result: 120 | continue 121 | proc.process(context, None) 122 | proc.postprocess(context, None) 123 | 124 | 125 | def release(): 126 | pass 127 | -------------------------------------------------------------------------------- /sd_bmab/processors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portu-sim/sd-webui-bmab/7a81441a72cef95cd9fdb732ee933bb04799dab3/sd_bmab/processors/__init__.py -------------------------------------------------------------------------------- /sd_bmab/processors/basic/__init__.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.processors.basic.edge import EdgeEnhancement 2 | from sd_bmab.processors.basic.final import FinalProcessorBasic 3 | from sd_bmab.processors.basic.intermediate import NoiseAlpha 4 | from sd_bmab.processors.basic.blend import BlendImage 5 | from sd_bmab.processors.basic.img2imgmasking import Img2imgMasking 6 | from sd_bmab.processors.basic.iclight import ICLight, ICLightBeforeUpsacle, ICLightAfterUpsacle 7 | 8 | -------------------------------------------------------------------------------- /sd_bmab/processors/basic/blend.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | from sd_bmab.base.context import Context 4 | from sd_bmab.base.processorbase import ProcessorBase 5 | from sd_bmab.util import debug_print 6 | 7 | 8 | class BlendImage(ProcessorBase): 9 | def __init__(self) -> None: 10 | super().__init__() 11 | self.enabled = False 12 | self.input_image = None 13 | self.alpha = 0 14 | 15 | def preprocess(self, context: Context, image: Image): 16 | self.enabled = context.args['blend_enabled'] 17 | self.input_image = context.args['input_image'] 18 | self.alpha = context.args['blend_alpha'] 19 | #return self.enabled and self.input_image is not None and 0 <= self.alpha <= 1 20 | return False 21 | 22 | def process(self, context: Context, image: Image): 23 | context.add_generation_param('BMAB blend alpha', self.alpha) 24 | #blend = Image.fromarray(self.input_image, mode='RGB') 25 | blend = self.input_image 26 | img = Image.new(mode='RGB', size=image.size) 27 | img.paste(image, (0, 0)) 28 | img.paste(blend) 29 | image = Image.blend(image, img, alpha=self.alpha) 30 | return image 31 | 32 | def postprocess(self, context: Context, image: Image): 33 | pass 34 | -------------------------------------------------------------------------------- /sd_bmab/processors/basic/edge.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | 4 | from PIL import Image 5 | from PIL import ImageOps 6 | from sd_bmab.base.context import Context 7 | from sd_bmab.base.processorbase import ProcessorBase 8 | 9 | 10 | class EdgeEnhancement(ProcessorBase): 11 | def __init__(self) -> None: 12 | super().__init__() 13 | 14 | self.edge_low_threadhold = 50 15 | self.edge_high_threadhold = 200 16 | self.edge_strength = 0.5 17 | 18 | def preprocess(self, context: Context, image: Image): 19 | if context.args['edge_flavor_enabled']: 20 | self.edge_low_threadhold = context.args['edge_low_threadhold'] 21 | self.edge_high_threadhold = context.args['edge_high_threadhold'] 22 | self.edge_strength = context.args['edge_strength'] 23 | return context.args['edge_flavor_enabled'] 24 | 25 | def process(self, context: Context, image: Image): 26 | context.add_generation_param('BMAB edge flavor low threadhold', self.edge_low_threadhold) 27 | context.add_generation_param('BMAB edge flavor high threadhold', self.edge_high_threadhold) 28 | context.add_generation_param('BMAB edge flavor strength', self.edge_strength) 29 | 30 | numpy_image = np.array(image) 31 | base = cv2.cvtColor(numpy_image, cv2.COLOR_RGB2BGR) 32 | arcanny = cv2.Canny(base, self.edge_low_threadhold, self.edge_high_threadhold) 33 | canny = Image.fromarray(arcanny) 34 | canny = ImageOps.invert(canny) 35 | 36 | img = image.convert('RGB') 37 | newdata = [(0, 0, 0) if mdata == 0 else ndata for mdata, ndata in zip(canny.getdata(), img.getdata())] 38 | newbase = Image.new('RGB', img.size) 39 | newbase.putdata(newdata) 40 | return Image.blend(img, newbase, alpha=self.edge_strength).convert("RGB") 41 | -------------------------------------------------------------------------------- /sd_bmab/processors/basic/final.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from PIL import Image 4 | from PIL import ImageEnhance 5 | 6 | from sd_bmab import util 7 | from sd_bmab.base.context import Context 8 | from sd_bmab.base.processorbase import ProcessorBase 9 | 10 | 11 | def calc_color_temperature(temp): 12 | white = (255.0, 254.11008387561782, 250.0419083427406) 13 | 14 | temperature = temp / 100 15 | 16 | if temperature <= 66: 17 | red = 255.0 18 | else: 19 | red = float(temperature - 60) 20 | red = 329.698727446 * math.pow(red, -0.1332047592) 21 | if red < 0: 22 | red = 0 23 | if red > 255: 24 | red = 255 25 | 26 | if temperature <= 66: 27 | green = temperature 28 | green = 99.4708025861 * math.log(green) - 161.1195681661 29 | else: 30 | green = float(temperature - 60) 31 | green = 288.1221695283 * math.pow(green, -0.0755148492) 32 | if green < 0: 33 | green = 0 34 | if green > 255: 35 | green = 255 36 | 37 | if temperature >= 66: 38 | blue = 255.0 39 | else: 40 | if temperature <= 19: 41 | blue = 0.0 42 | else: 43 | blue = float(temperature - 10) 44 | blue = 138.5177312231 * math.log(blue) - 305.0447927307 45 | if blue < 0: 46 | blue = 0 47 | if blue > 255: 48 | blue = 255 49 | 50 | return red / white[0], green / white[1], blue / white[2] 51 | 52 | 53 | class FinalProcessorBasic(ProcessorBase): 54 | def __init__(self) -> None: 55 | super().__init__() 56 | self.noise_alpha_final = 0 57 | self.contrast = 1 58 | self.brightness = 1 59 | self.sharpeness = 1 60 | self.color_saturation = 1 61 | self.color_temperature = 0 62 | 63 | def preprocess(self, context: Context, image: Image): 64 | self.contrast = context.args['contrast'] 65 | self.brightness = context.args['brightness'] 66 | self.sharpeness = context.args['sharpeness'] 67 | self.color_saturation = context.args['color_saturation'] 68 | self.color_temperature = context.args['color_temperature'] 69 | self.noise_alpha_final = context.args['noise_alpha_final'] 70 | return True 71 | 72 | def process(self, context: Context, image: Image): 73 | 74 | if self.noise_alpha_final != 0: 75 | context.add_generation_param('BMAB noise alpha final', self.noise_alpha_final) 76 | img_noise = util.generate_noise(context.sdprocessing.seed, image.size[0], image.size[1]) 77 | image = Image.blend(image, img_noise, alpha=self.noise_alpha_final) 78 | 79 | if self.contrast != 1: 80 | context.add_generation_param('BMAB contrast', self.contrast) 81 | enhancer = ImageEnhance.Contrast(image) 82 | image = enhancer.enhance(self.contrast) 83 | 84 | if self.brightness != 1: 85 | context.add_generation_param('BMAB brightness', self.brightness) 86 | enhancer = ImageEnhance.Brightness(image) 87 | image = enhancer.enhance(self.brightness) 88 | 89 | if self.sharpeness != 1: 90 | context.add_generation_param('BMAB sharpeness', self.sharpeness) 91 | enhancer = ImageEnhance.Sharpness(image) 92 | image = enhancer.enhance(self.sharpeness) 93 | 94 | if self.color_saturation != 1: 95 | context.add_generation_param('BMAB color', self.color_saturation) 96 | enhancer = ImageEnhance.Color(image) 97 | image = enhancer.enhance(self.color_saturation) 98 | 99 | if self.color_temperature != 0: 100 | context.add_generation_param('BMAB color temperature', self.color_temperature) 101 | temp = calc_color_temperature(6500 + self.color_temperature) 102 | az = [] 103 | for d in image.getdata(): 104 | az.append((int(d[0] * temp[0]), int(d[1] * temp[1]), int(d[2] * temp[2]))) 105 | image = Image.new('RGB', image.size) 106 | image.putdata(az) 107 | 108 | return image 109 | 110 | def postprocess(self, context: Context, image: Image): 111 | pass 112 | -------------------------------------------------------------------------------- /sd_bmab/processors/basic/iclight.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | from sd_bmab.base import cache 4 | from sd_bmab.base import ProcessorBase 5 | from sd_bmab.base.context import Context 6 | from sd_bmab.external import load_external_module 7 | 8 | 9 | class ICLight(ProcessorBase): 10 | 11 | def __init__(self) -> None: 12 | super().__init__() 13 | self.iclight_opt = {} 14 | self.enabled = False 15 | self.enable_before_upscale = False 16 | self.style = 'normal' 17 | self.prompt = '' 18 | self.preference = 'None' 19 | self.use_background_image = False 20 | self.blending = 0.5 21 | 22 | def preprocess(self, context: Context, image: Image): 23 | self.iclight_opt = context.args.get('module_config', {}).get('iclight', {}) 24 | self.enabled = self.iclight_opt.get('enabled', self.enabled) 25 | self.enable_before_upscale = self.iclight_opt.get('enable_before_upscale', self.enable_before_upscale) 26 | self.style = self.iclight_opt.get('style', self.style) 27 | self.prompt = self.iclight_opt.get('prompt', self.prompt) 28 | self.preference = self.iclight_opt.get('preference', self.preference) 29 | self.use_background_image = self.iclight_opt.get('use_background_image', self.use_background_image) 30 | self.blending = self.iclight_opt.get('blending', self.blending) 31 | return self.enabled 32 | 33 | def process(self, context: Context, image: Image): 34 | mod = load_external_module('iclight', 'bmabiclight') 35 | bg_image = self.get_background_image() if self.use_background_image else None 36 | return mod.bmab_relight(context, self.style, image, bg_image, self.prompt, self.blending, self.preference) 37 | 38 | def postprocess(self, context: Context, image: Image): 39 | pass 40 | 41 | @staticmethod 42 | def get_background_image(): 43 | img = cache.get_image_from_cache('iclight_background.png') 44 | if img is not None: 45 | return img 46 | return Image.new('RGB', (512, 768), (0, 0, 0)) 47 | 48 | @staticmethod 49 | def put_backgound_image(img): 50 | cache.put_image_to_cache('iclight_background.png', img) 51 | 52 | @staticmethod 53 | def get_styles(): 54 | return ['intensive', 'less intensive', 'normal', 'soft'] 55 | 56 | 57 | class ICLightBeforeUpsacle(ICLight): 58 | 59 | def preprocess(self, context: Context, image: Image): 60 | super().preprocess(context, image) 61 | return self.enabled and self.enable_before_upscale 62 | 63 | 64 | class ICLightAfterUpsacle(ICLight): 65 | 66 | def preprocess(self, context: Context, image: Image): 67 | super().preprocess(context, image) 68 | return self.enabled and not self.enable_before_upscale 69 | -------------------------------------------------------------------------------- /sd_bmab/processors/basic/img2imgmasking.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | from modules import devices 4 | from modules.processing import StableDiffusionProcessingImg2Img 5 | 6 | from sd_bmab import masking 7 | from sd_bmab import external 8 | from sd_bmab.base.context import Context 9 | from sd_bmab.base.processorbase import ProcessorBase 10 | 11 | 12 | class Img2imgMasking(ProcessorBase): 13 | def __init__(self) -> None: 14 | super().__init__() 15 | self.enabled = False 16 | self.prompt = '' 17 | self.input_image = None 18 | 19 | def preprocess(self, context: Context, image: Image): 20 | self.enabled = context.args['detect_enabled'] 21 | self.prompt = context.args['masking_prompt'] 22 | self.input_image = context.args['input_image'] 23 | return not context.is_txtimg() and self.enabled 24 | 25 | def sam(self, context, prompt, input_image): 26 | with external.ModuleAutoLoader('groundingdino', 'grdino') as dino: 27 | boxes, logits, phrases = dino.dino_predict(input_image, prompt, 0.35, 0.25) 28 | sam = masking.get_mask_generator() 29 | mask = sam.predict(input_image, boxes) 30 | return mask 31 | 32 | def process(self, context: Context, image: Image): 33 | if context.sdprocessing.image_mask is not None: 34 | context.sdprocessing.image_mask = self.sam(self.prompt, context.sdprocessing.init_images[0]) 35 | context.script.extra_image.append(context.sdprocessing.image_mask) 36 | if context.sdprocessing.image_mask is None and self.input_image is not None: 37 | mask = self.sam(context, self.prompt, context.sdprocessing.init_images[0]) 38 | newpil = Image.new('RGB', context.sdprocessing.init_images[0].size) 39 | newdata = [bdata if mdata == 0 else ndata for mdata, ndata, bdata in 40 | zip(mask.getdata(), context.sdprocessing.init_images[0].getdata(), self.input_image.getdata())] 41 | newpil.putdata(newdata) 42 | context.script.extra_image.append(newpil) 43 | return newpil 44 | return image 45 | 46 | def postprocess(self, context: Context, image: Image): 47 | devices.torch_gc() 48 | -------------------------------------------------------------------------------- /sd_bmab/processors/basic/intermediate.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from sd_bmab import util 3 | from sd_bmab.base.context import Context 4 | from sd_bmab.base.processorbase import ProcessorBase 5 | 6 | 7 | class NoiseAlpha(ProcessorBase): 8 | def __init__(self) -> None: 9 | super().__init__() 10 | self.noise_alpha = 0 11 | 12 | def preprocess(self, context: Context, image: Image): 13 | self.noise_alpha = context.args['noise_alpha'] 14 | return self.noise_alpha != 0 15 | 16 | def process(self, context: Context, image: Image): 17 | context.add_generation_param('BMAB noise alpha', self.noise_alpha) 18 | img_noise = util.generate_noise(context.sdprocessing.seed, image.size[0], image.size[1]) 19 | return Image.blend(image, img_noise, alpha=self.noise_alpha) 20 | 21 | def postprocess(self, context: Context, image: Image): 22 | pass 23 | -------------------------------------------------------------------------------- /sd_bmab/processors/basic/preprocessfilter.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.base import filter 2 | 3 | 4 | def run_preprocess_filter(context): 5 | module_config = context.args.get('module_config', {}) 6 | filter_name = module_config.get('preprocess_filter', None) 7 | if filter_name is None or filter_name == 'None': 8 | return 9 | 10 | bmab_filter = filter.get_filter(filter_name) 11 | filter.preprocess_filter(bmab_filter, context, None) 12 | filter.process_filter(bmab_filter, context, None, None) 13 | filter.postprocess_filter(bmab_filter, context) 14 | -------------------------------------------------------------------------------- /sd_bmab/processors/controlnet/__init__.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.processors.controlnet.noise import LineartNoise 2 | from sd_bmab.processors.controlnet.pose import Openpose 3 | from sd_bmab.processors.controlnet.ipadapter import IpAdapter 4 | -------------------------------------------------------------------------------- /sd_bmab/processors/controlnet/noise.py: -------------------------------------------------------------------------------- 1 | import os 2 | from PIL import Image 3 | 4 | from modules import shared 5 | 6 | import sd_bmab 7 | from sd_bmab import util, controlnet 8 | from sd_bmab.util import debug_print 9 | from sd_bmab.base.context import Context 10 | from sd_bmab.base.processorbase import ProcessorBase 11 | from sd_bmab.base import cache 12 | 13 | 14 | class LineartNoise(ProcessorBase): 15 | def __init__(self) -> None: 16 | super().__init__() 17 | self.controlnet_opt = {} 18 | self.enabled = False 19 | self.with_refiner = False 20 | self.noise_enabled = False 21 | self.noise_strength = 0.4 22 | self.noise_begin = 0.1 23 | self.noise_end = 0.9 24 | self.noise_hiresfix = 'Both' 25 | 26 | @staticmethod 27 | def with_refiner(context: Context): 28 | controlnet_opt = context.args.get('module_config', {}).get('controlnet', {}) 29 | enabled = controlnet_opt.get('enabled', False) 30 | with_refiner = controlnet_opt.get('with_refiner', False) 31 | debug_print('with refiner', enabled, with_refiner) 32 | return enabled and with_refiner 33 | 34 | def preprocess(self, context: Context, image: Image): 35 | self.controlnet_opt = context.args.get('module_config', {}).get('controlnet', {}) 36 | self.enabled = self.controlnet_opt.get('enabled', False) 37 | self.with_refiner = self.controlnet_opt.get('with_refiner', False) 38 | self.noise_enabled = self.controlnet_opt.get('noise', False) 39 | self.noise_strength = self.controlnet_opt.get('noise_strength', 0.4) 40 | self.noise_begin = self.controlnet_opt.get('noise_begin', 0.1) 41 | self.noise_end = self.controlnet_opt.get('noise_end', 0.9) 42 | self.noise_hiresfix = self.controlnet_opt.get('noise_hiresfix', self.noise_hiresfix) 43 | 44 | debug_print('Noise', context.is_refiner_context(), context.with_refiner(), self.with_refiner) 45 | if context.is_refiner_context(): 46 | return self.enabled and self.noise_enabled and self.with_refiner 47 | elif context.with_refiner() and self.with_refiner: 48 | return False 49 | return self.enabled and self.noise_enabled 50 | 51 | @staticmethod 52 | def get_noise_args(image, weight, begin, end, hr_option): 53 | cn_args = { 54 | 'enabled': True, 55 | 'image': image if isinstance(image, str) and os.path.exists(image) else util.b64_encoding(image.convert('RGB')), 56 | 'model': shared.opts.bmab_cn_lineart, 57 | 'weight': weight, 58 | "guidance_start": begin, 59 | "guidance_end": end, 60 | 'resize_mode': 'Just Resize', 61 | 'pixel_perfect': False, 62 | 'control_mode': 'ControlNet is more important', 63 | 'processor_res': 512, 64 | 'threshold_a': 0.5, 65 | 'threshold_b': 0.5, 66 | 'hr_option': hr_option 67 | } 68 | return cn_args 69 | 70 | def get_controlnet_args(self, context): 71 | img = util.generate_noise(context.sdprocessing.seed, context.sdprocessing.width, context.sdprocessing.height) 72 | noise = img.convert('L').convert('RGB') 73 | return self.get_noise_args(noise, self.noise_strength, self.noise_begin, self.noise_end, self.noise_hiresfix) 74 | 75 | def process(self, context: Context, image: Image): 76 | context.add_generation_param('BMAB controlnet mode', 'lineart') 77 | context.add_generation_param('BMAB noise strength', self.noise_strength) 78 | context.add_generation_param('BMAB noise begin', self.noise_begin) 79 | context.add_generation_param('BMAB noise end', self.noise_end) 80 | 81 | index = controlnet.get_controlnet_index(context.sdprocessing) 82 | img = cache.get_noise_from_cache(context.sdprocessing.seed, context.sdprocessing.width, context.sdprocessing.height) 83 | cn_op_arg = self.get_noise_args(img, self.noise_strength, self.noise_begin, self.noise_end, self.noise_hiresfix) 84 | debug_print(f'Noise Enabled {index}') 85 | sc_args = list(context.sdprocessing.script_args) 86 | sc_args[index] = cn_op_arg 87 | context.sdprocessing.script_args = tuple(sc_args) 88 | 89 | def postprocess(self, context: Context, image: Image): 90 | pass 91 | 92 | 93 | -------------------------------------------------------------------------------- /sd_bmab/processors/controlnet/pose.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import random 4 | 5 | from PIL import Image 6 | 7 | from modules import shared 8 | 9 | import sd_bmab 10 | from sd_bmab import util, controlnet 11 | from sd_bmab.util import debug_print 12 | from sd_bmab.base.context import Context 13 | from sd_bmab.base.processorbase import ProcessorBase 14 | 15 | 16 | class Openpose(ProcessorBase): 17 | def __init__(self) -> None: 18 | super().__init__() 19 | self.controlnet_opt = {} 20 | self.enabled = False 21 | self.pose_enabled = False 22 | self.pose_strength = 0.3 23 | self.pose_begin = 0.0 24 | self.pose_end = 1.0 25 | self.pose_face_only = False 26 | self.pose_selected = 'Random' 27 | 28 | def preprocess(self, context: Context, image: Image): 29 | self.controlnet_opt = context.args.get('module_config', {}).get('controlnet', {}) 30 | self.enabled = self.controlnet_opt.get('enabled', False) 31 | self.pose_enabled = self.controlnet_opt.get('pose', False) 32 | self.pose_strength = self.controlnet_opt.get('pose_strength', self.pose_strength) 33 | self.pose_begin = self.controlnet_opt.get('pose_begin', self.pose_begin) 34 | self.pose_end = self.controlnet_opt.get('pose_end', self.pose_end) 35 | self.pose_face_only = self.controlnet_opt.get('pose_face_only', self.pose_face_only) 36 | self.pose_selected = self.controlnet_opt.get('pose_selected', self.pose_selected) 37 | return self.enabled and self.pose_enabled 38 | 39 | def get_openpose_args(self, image): 40 | cn_args = { 41 | 'enabled': True, 42 | 'image': image if isinstance(image, str) and os.path.exists(image) else util.b64_encoding(image.convert('RGB')), 43 | 'module': 'openpose_faceonly' if self.pose_face_only else 'openpose', 44 | 'model': shared.opts.bmab_cn_openpose, 45 | 'weight': self.pose_strength, 46 | "guidance_start": self.pose_begin, 47 | "guidance_end": self.pose_end, 48 | 'resize_mode': 'Just Resize', 49 | 'pixel_perfect': False, 50 | 'control_mode': 'My prompt is more important', 51 | 'processor_res': 512, 52 | 'threshold_a': 0.5, 53 | 'threshold_b': 0.5, 54 | 'hr_option': 'Low res only' 55 | } 56 | return cn_args 57 | 58 | def process(self, context: Context, image: Image): 59 | context.add_generation_param('BMAB controlnet pose mode', 'openpose') 60 | context.add_generation_param('BMAB pose strength', self.pose_strength) 61 | context.add_generation_param('BMAB pose begin', self.pose_begin) 62 | context.add_generation_param('BMAB pose end', self.pose_end) 63 | 64 | img = self.load_random_image(context) 65 | if img is None: 66 | return 67 | 68 | index = controlnet.get_controlnet_index(context.sdprocessing) 69 | cn_op_arg = self.get_openpose_args(img) 70 | debug_print(f'Pose Enabled {index}') 71 | sc_args = list(context.sdprocessing.script_args) 72 | sc_args[index] = cn_op_arg 73 | context.sdprocessing.script_args = tuple(sc_args) 74 | 75 | def postprocess(self, context: Context, image: Image): 76 | pass 77 | 78 | def load_random_image(self, context): 79 | path = os.path.dirname(sd_bmab.__file__) 80 | path = os.path.normpath(os.path.join(path, '../resources/pose')) 81 | if os.path.exists(path) and os.path.isdir(path): 82 | file_mask = f'{path}/*.*' 83 | files = glob.glob(file_mask) 84 | if not files: 85 | debug_print(f'Not found pose files in {path}') 86 | return None 87 | img = context.load('preprocess_image') 88 | if img is not None: 89 | return img 90 | if self.pose_selected == 'Random': 91 | file = random.choice(files) 92 | debug_print(f'Random pose {file}') 93 | return self.get_cache(context, file) 94 | else: 95 | img_name = f'{path}/{self.pose_selected}' 96 | return self.get_cache(context, img_name) 97 | debug_print(f'Not found directory {path}') 98 | return None 99 | 100 | def get_cache(self, context, file): 101 | if self.pose_face_only: 102 | path = os.path.dirname(sd_bmab.__file__) 103 | path = os.path.normpath(os.path.join(path, '../resources/cache')) 104 | if os.path.exists(path) and os.path.isdir(path): 105 | b = os.path.basename(file) 106 | file_mask = f'{path}/pose_face_{b}' 107 | if os.path.exists(file_mask): 108 | return Image.open(file_mask) 109 | return Image.open(file) 110 | 111 | 112 | @staticmethod 113 | def list_pose(): 114 | path = os.path.dirname(sd_bmab.__file__) 115 | path = os.path.normpath(os.path.join(path, '../resources/pose')) 116 | if os.path.exists(path) and os.path.isdir(path): 117 | file_mask = f'{path}/*.*' 118 | files = glob.glob(file_mask) 119 | return [os.path.basename(f) for f in files] 120 | debug_print(f'Not found directory {path}') 121 | return [] 122 | 123 | @staticmethod 124 | def get_pose(f): 125 | if f == 'Random' or f == 'Preprocess': 126 | return Image.new('RGB', (512, 512), 0) 127 | path = os.path.dirname(sd_bmab.__file__) 128 | path = os.path.normpath(os.path.join(path, '../resources/pose')) 129 | if os.path.exists(path) and os.path.isdir(path): 130 | img_name = f'{path}/{f}' 131 | return Image.open(img_name) 132 | return Image.new('RGB', (512, 512), 0) 133 | 134 | @staticmethod 135 | def pose_selected(*args): 136 | return Openpose.get_pose(args[0]) 137 | -------------------------------------------------------------------------------- /sd_bmab/processors/detailer/__init__.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.processors.detailer.face import FaceDetailer, PreprocessFaceDetailer, FaceDetailerBeforeUpsacle 2 | from sd_bmab.processors.detailer.hand import HandDetailer 3 | from sd_bmab.processors.detailer.person import PersonDetailer 4 | -------------------------------------------------------------------------------- /sd_bmab/processors/postprocess/__init__.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.processors.postprocess.upscaleafterprocess import AfterProcessUpscaler 2 | from sd_bmab.processors.postprocess.upscalebeforeprocess import BeforeProcessUpscaler 3 | from sd_bmab.processors.postprocess.inpaint import InpaintResize 4 | from sd_bmab.processors.postprocess.inpaintlama import InpaintLamaResize 5 | from sd_bmab.processors.postprocess.finalfilter import FinalFilter 6 | from sd_bmab.processors.postprocess.watermark import Watermark 7 | -------------------------------------------------------------------------------- /sd_bmab/processors/postprocess/finalfilter.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from sd_bmab.base import Context, ProcessorBase 3 | from sd_bmab.base import filter 4 | 5 | 6 | class FinalFilter(ProcessorBase): 7 | def __init__(self) -> None: 8 | super().__init__() 9 | self.filter_name = 'None' 10 | self.filter = None 11 | 12 | def preprocess(self, context: Context, image: Image): 13 | self.filter_name = context.args.get('postprocess_final_filter', self.filter_name) 14 | return self.filter_name != 'None' 15 | 16 | def process(self, context: Context, image: Image): 17 | self.filter = filter.get_filter(self.filter_name) 18 | if self.filter is not None: 19 | filter.preprocess_filter(self.filter, context, image) 20 | image = filter.process_filter(self.filter, context, None, image) 21 | filter.postprocess_filter(self.filter, context) 22 | return image 23 | 24 | def finalprocess(self, context: Context, image: Image): 25 | if self.filter is not None: 26 | filter.finalprocess_filter(self.filter, context) 27 | self.filter = None 28 | -------------------------------------------------------------------------------- /sd_bmab/processors/postprocess/inpaint.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from PIL import ImageDraw 3 | 4 | from modules import devices 5 | from modules.processing import process_images, StableDiffusionProcessingImg2Img 6 | 7 | from sd_bmab import util 8 | from sd_bmab.base import apply_extensions, build_img2img, Context, ProcessorBase 9 | 10 | from sd_bmab.util import debug_print 11 | from sd_bmab.detectors import UltralyticsPersonDetector8n 12 | 13 | 14 | class InpaintResize(ProcessorBase): 15 | def __init__(self) -> None: 16 | super().__init__() 17 | self.resize_by_person_opt = None 18 | self.value = 0, 19 | self.denoising_strength = 0.4 20 | self.dilation = 4 21 | self.mode = 'Inpaint' 22 | 23 | def preprocess(self, context: Context, image: Image): 24 | enabled = context.args.get('resize_by_person_enabled', False) 25 | self.resize_by_person_opt = context.args.get('module_config', {}).get('resize_by_person_opt', {}) 26 | self.value = self.resize_by_person_opt.get('scale', 0) 27 | self.denoising_strength = self.resize_by_person_opt.get('denoising_strength', 0.4) 28 | self.dilation = self.resize_by_person_opt.get('dilation', 0.4) 29 | self.mode = self.resize_by_person_opt.get('mode', self.mode) 30 | 31 | return enabled and self.mode == 'Inpaint' 32 | 33 | def process(self, context: Context, image: Image): 34 | debug_print('prepare detector') 35 | detector = UltralyticsPersonDetector8n() 36 | boxes, logits = detector.predict(context, image) 37 | 38 | org_size = image.size 39 | debug_print('size', org_size) 40 | 41 | largest = (0, None) 42 | for box in boxes: 43 | x1, y1, x2, y2 = box 44 | size = (x2 - x1) * (y2 - y1) 45 | if size > largest[0]: 46 | largest = (size, box) 47 | 48 | if largest[0] == 0: 49 | return image 50 | 51 | x1, y1, x2, y2 = largest[1] 52 | ratio = (y2 - y1) / image.height 53 | debug_print('ratio', ratio) 54 | debug_print('org_size', org_size) 55 | if ratio <= self.value: 56 | return image 57 | image_ratio = ratio / self.value 58 | if image_ratio < 1.0: 59 | return image 60 | debug_print('scale', image_ratio) 61 | ratio = image_ratio 62 | 63 | org_size = image.size 64 | dw, dh = org_size 65 | 66 | context.add_generation_param('BMAB controlnet mode', 'inpaint') 67 | context.add_generation_param('BMAB resize by person ratio', '%.3s' % ratio) 68 | 69 | resized_width = int(dw / ratio) 70 | resized_height = int(dh / ratio) 71 | resized = image.resize((resized_width, resized_height), resample=util.LANCZOS) 72 | context.sdprocessing.resize_mode = 2 73 | input_image = util.resize_image(2, resized, dw, dh) 74 | 75 | offset_x = int((dw - resized_width) / 2) 76 | offset_y = dh - resized_height 77 | 78 | mask = Image.new('L', (dw, dh), 255) 79 | dr = ImageDraw.Draw(mask, 'L') 80 | dr.rectangle((offset_x, offset_y, offset_x + resized_width, offset_y + resized_height), fill=0) 81 | mask = mask.resize(org_size, resample=util.LANCZOS) 82 | mask = util.dilate_mask(mask, self.dilation) 83 | 84 | opt = dict(mask=mask, denoising_strength=self.denoising_strength) 85 | i2i_param = build_img2img(context.sdprocessing, input_image, opt) 86 | 87 | img2img = StableDiffusionProcessingImg2Img(**i2i_param) 88 | img2img.cached_c = [None, None] 89 | img2img.cached_uc = [None, None] 90 | img2img.scripts, img2img.script_args = apply_extensions(context.sdprocessing, cn_enabled=False) 91 | 92 | processed = process_images(img2img) 93 | img = processed.images[0] 94 | 95 | img2img.close() 96 | 97 | return img 98 | 99 | def postprocess(self, context: Context, image: Image): 100 | devices.torch_gc() 101 | -------------------------------------------------------------------------------- /sd_bmab/processors/postprocess/inpaintlama.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from PIL import ImageDraw 3 | 4 | from modules import shared 5 | from modules import devices 6 | from modules.processing import process_images, StableDiffusionProcessingImg2Img 7 | 8 | from sd_bmab import util 9 | from sd_bmab.base import apply_extensions, build_img2img, Context, ProcessorBase 10 | from sd_bmab.util import debug_print 11 | from sd_bmab.detectors import UltralyticsPersonDetector8n 12 | 13 | 14 | class InpaintLamaResize(ProcessorBase): 15 | def __init__(self) -> None: 16 | super().__init__() 17 | self.resize_by_person_opt = None 18 | self.value = 0, 19 | self.denoising_strength = 0.4 20 | self.dilation = 4 21 | self.mode = 'ControlNet inpaint+lama' 22 | self.enabled = False 23 | 24 | def use_controlnet(self, context: Context): 25 | self.preprocess(context, None) 26 | return self.enabled 27 | 28 | def preprocess(self, context: Context, image: Image): 29 | self.enabled = context.args.get('resize_by_person_enabled', self.enabled) 30 | self.resize_by_person_opt = context.args.get('module_config', {}).get('resize_by_person_opt', {}) 31 | self.value = self.resize_by_person_opt.get('scale', 0) 32 | self.denoising_strength = self.resize_by_person_opt.get('denoising_strength', 0.4) 33 | self.dilation = self.resize_by_person_opt.get('dilation', 0.4) 34 | self.mode = self.resize_by_person_opt.get('mode', self.mode) 35 | 36 | return self.enabled and self.mode == 'ControlNet inpaint+lama' 37 | 38 | @staticmethod 39 | def get_inpaint_lama_args(image, mask): 40 | cn_args = { 41 | 'input_image': util.b64_encoding(image), 42 | 'mask': util.b64_encoding(mask), 43 | 'module': 'inpaint_only+lama', 44 | 'model': shared.opts.bmab_cn_inpaint, 45 | 'weight': 1, 46 | "guidance_start": 0, 47 | "guidance_end": 1, 48 | 'resize_mode': 'Resize and Fill', 49 | 'pixel_perfect': False, 50 | 'control_mode': 'ControlNet is more important', 51 | 'processor_res': 512, 52 | 'threshold_a': 64, 53 | 'threshold_b': 64, 54 | } 55 | return cn_args 56 | 57 | def get_ratio(self, context, img, p): 58 | p.extra_generation_params['BMAB process_resize_by_person'] = self.value 59 | 60 | final_ratio = 1 61 | debug_print('prepare detector') 62 | detector = UltralyticsPersonDetector8n() 63 | boxes, logits = detector.predict(context, img) 64 | 65 | largest = (0, None) 66 | for box in boxes: 67 | x1, y1, x2, y2 = box 68 | size = (x2 - x1) * (y2 - y1) 69 | if size > largest[0]: 70 | largest = (size, box) 71 | 72 | if largest[0] == 0: 73 | return final_ratio 74 | 75 | x1, y1, x2, y2 = largest[1] 76 | ratio = (y2 - y1) / img.height 77 | debug_print('ratio', ratio) 78 | 79 | if ratio > self.value: 80 | image_ratio = ratio / self.value 81 | if image_ratio < 1.0: 82 | return final_ratio 83 | final_ratio = image_ratio 84 | return final_ratio 85 | 86 | def resize_by_person_using_controlnet(self, context, p): 87 | if not isinstance(p, StableDiffusionProcessingImg2Img): 88 | return False 89 | 90 | cn_args = util.get_cn_args(p) 91 | 92 | debug_print('resize_by_person_enabled_inpaint', self.value) 93 | img = p.init_images[0] 94 | context.script.extra_image.append(img) 95 | 96 | ratio = self.get_ratio(context, img, p) 97 | debug_print('image resize ratio', ratio) 98 | org_size = img.size 99 | dw, dh = org_size 100 | 101 | if ratio == 1: 102 | return False 103 | 104 | p.extra_generation_params['BMAB controlnet mode'] = 'inpaint+lama' 105 | p.extra_generation_params['BMAB resize by person ratio'] = '%.3s' % ratio 106 | 107 | resized_width = int(dw / ratio) 108 | resized_height = int(dh / ratio) 109 | resized = img.resize((resized_width, resized_height), resample=util.LANCZOS) 110 | p.resize_mode = 2 111 | input_image = util.resize_image(2, resized, dw, dh) 112 | p.init_images[0] = input_image 113 | 114 | offset_x = int((dw - resized_width) / 2) 115 | offset_y = dh - resized_height 116 | 117 | mask = Image.new('L', (dw, dh), 255) 118 | dr = ImageDraw.Draw(mask, 'L') 119 | dr.rectangle((offset_x, offset_y, offset_x + resized_width, offset_y + resized_height), fill=0) 120 | mask = mask.resize(org_size, resample=util.LANCZOS) 121 | mask = util.dilate_mask(mask, self.dilation) 122 | 123 | cn_op_arg = self.get_inpaint_lama_args(input_image, mask) 124 | idx = cn_args[0] 125 | sc_args = list(p.script_args) 126 | sc_args[idx] = cn_op_arg 127 | p.script_args = tuple(sc_args) 128 | return True 129 | 130 | def process(self, context: Context, image: Image): 131 | opt = dict(denoising_strength=self.denoising_strength) 132 | i2i_param = build_img2img(context.sdprocessing, image, opt) 133 | 134 | img2img = StableDiffusionProcessingImg2Img(**i2i_param) 135 | img2img.cached_c = [None, None] 136 | img2img.cached_uc = [None, None] 137 | img2img.scripts, img2img.script_args = apply_extensions(context.sdprocessing, cn_enabled=True) 138 | 139 | if self.resize_by_person_using_controlnet(context, img2img): 140 | processed = process_images(img2img) 141 | image = processed.images[0] 142 | img2img.close() 143 | devices.torch_gc() 144 | return image 145 | 146 | def postprocess(self, context: Context, image: Image): 147 | pass 148 | -------------------------------------------------------------------------------- /sd_bmab/processors/postprocess/upscaleafterprocess.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | from modules import images 4 | 5 | from sd_bmab.base.context import Context 6 | from sd_bmab.base.processorbase import ProcessorBase 7 | from sd_bmab.util import debug_print 8 | 9 | 10 | class AfterProcessUpscaler(ProcessorBase): 11 | def __init__(self) -> None: 12 | super().__init__() 13 | self.ratio = 1.5 14 | self.upscaler = 'None' 15 | 16 | def preprocess(self, context: Context, image: Image): 17 | self.ratio = context.args['upscale_ratio'] 18 | self.upscaler = context.args['upscaler_name'] 19 | return context.args['upscale_enabled'] and context.args['detailing_after_upscale'] 20 | 21 | def process(self, context: Context, image: Image): 22 | debug_print(f'Upscale ratio {self.ratio} Upscaler {self.upscaler}') 23 | context.add_generation_param('BMAB_upscale_option', f'Upscale ratio {self.ratio} Upscaler {self.upscaler}') 24 | 25 | if self.ratio < 1.0 or self.ratio > 4.0: 26 | debug_print('upscale out of range') 27 | return image 28 | image = image.convert('RGB') 29 | context.add_generation_param('BMAB process upscale', self.ratio) 30 | context.args['max_area'] = image.width * image.height 31 | context.args['upscale_limit'] = True 32 | 33 | w = image.width 34 | h = image.height 35 | img = images.resize_image(0, image, int(w * self.ratio), int(h * self.ratio), self.upscaler) 36 | return img.convert('RGB') 37 | 38 | def postprocess(self, context: Context, image: Image): 39 | pass 40 | -------------------------------------------------------------------------------- /sd_bmab/processors/postprocess/upscalebeforeprocess.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | from modules import images 4 | 5 | from sd_bmab.base.context import Context 6 | from sd_bmab.base.processorbase import ProcessorBase 7 | from sd_bmab.util import debug_print 8 | 9 | 10 | class BeforeProcessUpscaler(ProcessorBase): 11 | def __init__(self) -> None: 12 | super().__init__() 13 | self.ratio = 1.5 14 | self.upscaler = 'None' 15 | 16 | def preprocess(self, context: Context, image: Image): 17 | self.ratio = context.args['upscale_ratio'] 18 | self.upscaler = context.args['upscaler_name'] 19 | return context.args['upscale_enabled'] and not context.args['detailing_after_upscale'] 20 | 21 | def process(self, context: Context, image: Image): 22 | debug_print(f'Upscale ratio {self.ratio} Upscaler {self.upscaler}') 23 | context.add_generation_param('BMAB_upscale_option', f'Upscale ratio {self.ratio} Upscaler {self.upscaler}') 24 | 25 | if self.ratio < 1.0 or self.ratio > 4.0: 26 | debug_print('upscale out of range') 27 | return image 28 | image = image.convert('RGB') 29 | context.add_generation_param('BMAB process upscale', self.ratio) 30 | context.args['upscale_limit'] = True 31 | 32 | w = image.width 33 | h = image.height 34 | img = images.resize_image(0, image, int(w * self.ratio), int(h * self.ratio), self.upscaler) 35 | return img.convert('RGB') 36 | 37 | def postprocess(self, context: Context, image: Image): 38 | pass 39 | -------------------------------------------------------------------------------- /sd_bmab/processors/postprocess/watermark.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import glob 4 | 5 | from PIL import Image 6 | from PIL import ImageDraw 7 | from PIL import ImageFont 8 | from sd_bmab.base import Context, ProcessorBase 9 | 10 | 11 | class Watermark(ProcessorBase): 12 | alignment = { 13 | 'top': lambda w, h, cx, cy: (w / 2 - cx / 2, 0), 14 | 'top-right': lambda w, h, cx, cy: (w - cx, 0), 15 | 'right': lambda w, h, cx, cy: (w - cx, h / 2 - cy / 2), 16 | 'bottom-right': lambda w, h, cx, cy: (w - cx, h - cy), 17 | 'bottom': lambda w, h, cx, cy: (w / 2 - cx / 2, h - cy), 18 | 'bottom-left': lambda w, h, cx, cy: (0, h - cy), 19 | 'left': lambda w, h, cx, cy: (0, h / 2 - cy / 2), 20 | 'top-left': lambda w, h, cx, cy: (0, 0), 21 | 'center': lambda w, h, cx, cy: (w / 2 - cx / 2, h / 2 - cy / 2), 22 | } 23 | 24 | def __init__(self) -> None: 25 | super().__init__() 26 | self.enabled = False 27 | self.font = None 28 | self.alignment = 'bottom-left' 29 | self.text_alignment = 'left' 30 | self.rotate = 0 31 | self.color = '#000000' 32 | self.background_color = '#00000000' 33 | self.font_size = 12 34 | self.transparency = 100 35 | self.background_transparency = 0 36 | self.margin = 5 37 | self.text = '' 38 | 39 | def preprocess(self, context: Context, image: Image): 40 | watermark_opt = context.args.get('module_config', {}).get('watermark', {}) 41 | self.enabled = watermark_opt.get('enabled', self.enabled) 42 | self.font = watermark_opt.get('font', self.font) 43 | self.alignment = watermark_opt.get('alignment', self.alignment) 44 | self.text_alignment = watermark_opt.get('text_alignment', self.text_alignment) 45 | _rotate = watermark_opt.get('rotate', self.rotate) 46 | self.rotate = int(_rotate) 47 | self.color = watermark_opt.get('color', self.color) 48 | self.background_color = watermark_opt.get('background_color', self.background_color) 49 | self.font_size = watermark_opt.get('font_size', self.font_size) 50 | self.transparency = watermark_opt.get('transparency', self.transparency) 51 | self.background_transparency = watermark_opt.get('background_transparency', self.background_transparency) 52 | self.margin = watermark_opt.get('margin', self.margin) 53 | self.text = watermark_opt.get('text', self.text) 54 | 55 | return self.enabled 56 | 57 | def process(self, context: Context, image: Image): 58 | 59 | background_color = self.color_hex_to_rgb(self.background_color, int(255 * (self.background_transparency / 100))) 60 | 61 | if os.path.isfile(self.text): 62 | cropped = Image.open(self.text) 63 | else: 64 | font = self.get_font(self.font, self.font_size) 65 | color = self.color_hex_to_rgb(self.color, int(255 * (self.transparency / 100))) 66 | 67 | # 1st 68 | base = Image.new('RGBA', image.size, background_color) 69 | draw = ImageDraw.Draw(base) 70 | bbox = draw.textbbox((0, 0), self.text, font=font) 71 | draw.text((0, 0), self.text, font=font, fill=color, align=self.text_alignment) 72 | cropped = base.crop(bbox) 73 | 74 | # 2st margin 75 | base = Image.new('RGBA', (cropped.width + self.margin * 2, cropped.height + self.margin * 2), background_color) 76 | base.paste(cropped, (self.margin, self.margin)) 77 | 78 | # 3rd rotate 79 | base = base.rotate(self.rotate, expand=True) 80 | 81 | # 4th 82 | image = image.convert('RGBA') 83 | image2 = image.copy() 84 | x, y = Watermark.alignment[self.alignment](image.width, image.height, base.width, base.height) 85 | image2.paste(base, (int(x), int(y))) 86 | image = Image.alpha_composite(image, image2) 87 | 88 | return image.convert('RGB') 89 | 90 | @staticmethod 91 | def color_hex_to_rgb(value, transparency): 92 | value = value.lstrip('#') 93 | lv = len(value) 94 | r, g, b = tuple(int(value[i:i + 2], 16) for i in range(0, lv, 2)) 95 | return r, g, b, transparency 96 | 97 | @staticmethod 98 | def list_fonts(): 99 | if sys.platform == 'win32': 100 | path = 'C:\\Windows\\Fonts\\*.ttf' 101 | files = glob.glob(path) 102 | return [os.path.basename(f) for f in files] 103 | if sys.platform == 'darwin': 104 | path = '/System/Library/Fonts/*' 105 | files = glob.glob(path) 106 | return [os.path.basename(f) for f in files] 107 | if sys.platform == 'linux': 108 | path = '/usr/share/fonts/*' 109 | files = glob.glob(path) 110 | fonts = [os.path.basename(f) for f in files] 111 | if 'SAGEMAKER_INTERNAL_IMAGE_URI' in os.environ: 112 | path = '/opt/conda/envs/sagemaker-distribution/fonts/*' 113 | files = glob.glob(path) 114 | fonts.extend([os.path.basename(f) for f in files]) 115 | return fonts 116 | return [''] 117 | 118 | @staticmethod 119 | def get_font(font, size): 120 | if sys.platform == 'win32': 121 | path = f'C:\\Windows\\Fonts\\{font}' 122 | return ImageFont.truetype(path, size, encoding="unic") 123 | if sys.platform == 'darwin': 124 | path = f'/System/Library/Fonts/{font}' 125 | return ImageFont.truetype(path, size, encoding="unic") 126 | if sys.platform == 'linux': 127 | if 'SAGEMAKER_INTERNAL_IMAGE_URI' in os.environ: 128 | path = f'/opt/conda/envs/sagemaker-distribution/fonts/{font}' 129 | else: 130 | path = f'/usr/share/fonts/{font}' 131 | return ImageFont.truetype(path, size, encoding="unic") 132 | -------------------------------------------------------------------------------- /sd_bmab/processors/preprocess/__init__.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.processors.preprocess.resize import ResizeIntermidiateBeforeUpscale, ResizeIntermidiateAfterUpsacle 2 | from sd_bmab.processors.preprocess.refiner import RefinerPreprocessor 3 | from sd_bmab.processors.preprocess.pretraining import PretrainingDetailer, PretrainingDetailerBeforeUpscale 4 | from sd_bmab.processors.preprocess.resample import ResamplePreprocessor, ResamplePreprocessorBeforeUpscale 5 | from sd_bmab.processors.preprocess.preprocessfilter import PreprocessFilter 6 | 7 | -------------------------------------------------------------------------------- /sd_bmab/processors/preprocess/preprocessfilter.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from sd_bmab.base import Context, ProcessorBase 3 | from sd_bmab.base import filter 4 | 5 | 6 | class PreprocessFilter(ProcessorBase): 7 | def __init__(self) -> None: 8 | super().__init__() 9 | self.filter_name = 'None' 10 | self.filter = None 11 | 12 | def preprocess(self, context: Context, image: Image): 13 | self.filter_name = context.args.get('txt2img_filter_hresfix_before_upscale', self.filter_name) 14 | return not context.is_hires_fix() and self.filter_name != 'None' 15 | 16 | def process(self, context: Context, image: Image): 17 | self.filter = filter.get_filter(self.filter_name) 18 | if self.filter is not None: 19 | filter.preprocess_filter(self.filter, context, image) 20 | image = filter.process_filter(self.filter, context, None, image) 21 | filter.postprocess_filter(self.filter, context) 22 | return image 23 | 24 | def finalprocess(self, context: Context, image: Image): 25 | if self.filter is not None: 26 | filter.finalprocess_filter(self.filter, context) 27 | self.filter = None 28 | -------------------------------------------------------------------------------- /sd_bmab/processors/preprocess/refiner.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | from modules import devices 4 | from modules import images 5 | 6 | from sd_bmab import util 7 | from sd_bmab import constants 8 | from sd_bmab.util import debug_print 9 | from sd_bmab.base import process_img2img, Context, ProcessorBase, process_img2img_with_controlnet 10 | from sd_bmab.processors.controlnet import LineartNoise 11 | 12 | 13 | class RefinerPreprocessor(ProcessorBase): 14 | def __init__(self) -> None: 15 | super().__init__() 16 | 17 | self.refiner_opt = {} 18 | self.enabled = False 19 | self.checkpoint = constants.checkpoint_default 20 | self.vae = constants.vae_default 21 | self.keep_checkpoint = True 22 | self.prompt = None 23 | self.negative_prompt = None 24 | self.sampler = None 25 | self.scheduler = None 26 | self.upscaler = None 27 | self.steps = 20 28 | self.cfg_scale = 0.7 29 | self.denoising_strength = 0.75 30 | self.scale = 1 31 | self.width = 0 32 | self.height = 0 33 | 34 | def preprocess(self, context: Context, image: Image): 35 | self.enabled = context.args['refiner_enabled'] 36 | self.refiner_opt = context.args.get('module_config', {}).get('refiner_opt', {}) 37 | 38 | self.checkpoint = self.refiner_opt.get('checkpoint', self.checkpoint) 39 | self.vae = self.refiner_opt.get('vae', self.vae) 40 | self.keep_checkpoint = self.refiner_opt.get('keep_checkpoint', True) 41 | self.prompt = self.refiner_opt.get('prompt', '') 42 | self.negative_prompt = self.refiner_opt.get('negative_prompt', '') 43 | self.sampler = self.refiner_opt.get('sampler', None) 44 | self.scheduler = self.refiner_opt.get('scheduler', None) 45 | self.upscaler = self.refiner_opt.get('upscaler', None) 46 | self.steps = self.refiner_opt.get('steps', None) 47 | self.cfg_scale = self.refiner_opt.get('cfg_scale', None) 48 | self.denoising_strength = self.refiner_opt.get('denoising_strength', None) 49 | self.scale = self.refiner_opt.get('scale', None) 50 | self.width = self.refiner_opt.get('width', None) 51 | self.height = self.refiner_opt.get('height', None) 52 | 53 | if self.enabled: 54 | context.refiner = self 55 | 56 | return self.enabled 57 | 58 | def process(self, context: Context, image: Image): 59 | output_width = image.width 60 | output_height = image.height 61 | 62 | if not (self.width == 0 and self.height == 0 and self.scale == 1): 63 | if (self.width == 0 or self.height == 0) and self.scale != 1: 64 | output_width = int(image.width * self.scale) 65 | output_height = int(image.height * self.scale) 66 | elif self.width != 0 and self.height != 0: 67 | output_width = self.width 68 | output_height = self.height 69 | 70 | if image.width != output_width or image.height != output_height: 71 | LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS) 72 | if self.upscaler == constants.fast_upscaler: 73 | image = image.resize((output_width, output_height), resample=LANCZOS) 74 | else: 75 | image = images.resize_image(0, image, output_width, output_height, self.upscaler) 76 | 77 | if self.prompt == '': 78 | self.prompt = context.get_prompt_by_index() 79 | debug_print('prompt', self.prompt) 80 | elif self.prompt.find('#!org!#') >= 0: 81 | current_prompt = context.get_prompt_by_index() 82 | self.prompt = self.prompt.replace('#!org!#', current_prompt) 83 | debug_print('Prompt', self.prompt) 84 | if self.negative_prompt == '': 85 | self.negative_prompt = context.sdprocessing.negative_prompt 86 | if self.sampler == constants.sampler_default: 87 | self.sampler = context.sdprocessing.sampler_name 88 | if self.scheduler == constants.scheduler_default: 89 | self.scheduler = util.get_scheduler(context.sdprocessing) 90 | 91 | seed, subseed = context.get_seeds() 92 | options = dict( 93 | seed=seed, subseed=subseed, 94 | denoising_strength=self.denoising_strength, 95 | resize_mode=0, 96 | mask=None, 97 | mask_blur=4, 98 | inpainting_fill=1, 99 | inpaint_full_res=True, 100 | inpaint_full_res_padding=32, 101 | inpainting_mask_invert=0, 102 | initial_noise_multiplier=1.0, 103 | prompt=self.prompt, 104 | negative_prompt=self.negative_prompt, 105 | sampler_name=self.sampler, 106 | scheduler=self.scheduler, 107 | batch_size=1, 108 | n_iter=1, 109 | steps=self.steps, 110 | cfg_scale=self.cfg_scale, 111 | width=output_width, 112 | height=output_height, 113 | restore_faces=False, 114 | do_not_save_samples=True, 115 | do_not_save_grid=True, 116 | ) 117 | context.add_job() 118 | 119 | if self.checkpoint is not None and self.checkpoint != constants.checkpoint_default: 120 | override_settings = options.get('override_settings', {}) 121 | override_settings['sd_model_checkpoint'] = self.checkpoint 122 | options['override_settings'] = override_settings 123 | if self.vae is not None and self.vae != constants.vae_default: 124 | override_settings = options.get('override_settings', {}) 125 | override_settings['sd_vae'] = self.vae 126 | options['override_settings'] = override_settings 127 | 128 | if LineartNoise.with_refiner(context): 129 | ln = LineartNoise() 130 | if ln.preprocess(context, None): 131 | controlnet = ln.get_controlnet_args(context) 132 | image = process_img2img_with_controlnet(context, image, options, controlnet=[controlnet]) 133 | else: 134 | image = process_img2img(context, image, options=options) 135 | else: 136 | image = process_img2img(context, image, options=options) 137 | 138 | if not self.keep_checkpoint: 139 | debug_print('Rollback model') 140 | context.restore_checkpoint() 141 | 142 | return image 143 | 144 | @staticmethod 145 | def process_callback(self, context, img2img): 146 | ctx = Context.newContext(self, img2img, context.args, 0) 147 | ctx.refiner = self 148 | ln = LineartNoise() 149 | if ln.preprocess(ctx, None): 150 | ln.process(ctx, None) 151 | ln.postprocess(ctx, None) 152 | 153 | def postprocess(self, context: Context, image: Image): 154 | devices.torch_gc() 155 | -------------------------------------------------------------------------------- /sd_bmab/processors/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.processors.utils.filesave import AfterProcessFileSaver, BeforeProcessFileSaver 2 | from sd_bmab.processors.utils.modelswitch import ApplyModel, RollbackModel 3 | from sd_bmab.processors.utils.checkpoint import CheckPointChanger, CheckPointRestore 4 | -------------------------------------------------------------------------------- /sd_bmab/processors/utils/checkpoint.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | from sd_bmab import constants 4 | from sd_bmab.base.context import Context 5 | from sd_bmab.base.processorbase import ProcessorBase 6 | from sd_bmab.util import debug_print 7 | 8 | 9 | class CheckPointChanger(ProcessorBase): 10 | def __init__(self) -> None: 11 | super().__init__() 12 | self.checkpoint = constants.checkpoint_default 13 | self.vae = constants.vae_default 14 | 15 | def preprocess(self, context: Context, image: Image): 16 | self.checkpoint = context.args['preprocess_checkpoint'] 17 | self.vae = context.args['preprocess_vae'] 18 | return not (self.checkpoint == constants.checkpoint_default and self.vae == constants.vae_default) 19 | 20 | def process(self, context: Context, image: Image): 21 | debug_print('Change checkpoint', self.checkpoint, self.vae) 22 | context.save_and_apply_checkpoint(self.checkpoint, self.vae) 23 | return image 24 | 25 | 26 | class CheckPointRestore(ProcessorBase): 27 | def __init__(self) -> None: 28 | super().__init__() 29 | 30 | def preprocess(self, context: Context, image: Image): 31 | return True 32 | 33 | def process(self, context: Context, image: Image): 34 | context.restore_checkpoint() 35 | return image 36 | -------------------------------------------------------------------------------- /sd_bmab/processors/utils/filesave.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | from modules import shared 4 | from modules import images 5 | 6 | from sd_bmab.base.context import Context 7 | from sd_bmab.base.processorbase import ProcessorBase 8 | 9 | 10 | class BeforeProcessFileSaver(ProcessorBase): 11 | def __init__(self) -> None: 12 | super().__init__() 13 | 14 | def preprocess(self, context: Context, image: Image): 15 | return shared.opts.bmab_save_image_before_process 16 | 17 | def process(self, context: Context, image: Image): 18 | images.save_image( 19 | image, context.sdprocessing.outpath_samples, '', 20 | context.sdprocessing.all_seeds[context.index], context.sdprocessing.all_prompts[context.index], 21 | shared.opts.samples_format, p=context.sdprocessing, suffix='-before-bmab') 22 | return image 23 | 24 | def postprocess(self, context: Context, image: Image): 25 | pass 26 | 27 | 28 | class AfterProcessFileSaver(ProcessorBase): 29 | def __init__(self) -> None: 30 | super().__init__() 31 | 32 | def preprocess(self, context: Context, image: Image): 33 | return shared.opts.bmab_save_image_after_process 34 | 35 | def process(self, context: Context, image: Image): 36 | images.save_image( 37 | image, context.sdprocessing.outpath_samples, '', 38 | context.sdprocessing.all_seeds[context.index], context.sdprocessing.all_prompts[context.index], 39 | shared.opts.samples_format, p=context.sdprocessing, suffix="-after-bmab") 40 | return image 41 | 42 | def postprocess(self, context: Context, image: Image): 43 | pass 44 | -------------------------------------------------------------------------------- /sd_bmab/processors/utils/modelswitch.py: -------------------------------------------------------------------------------- 1 | 2 | from PIL import Image 3 | 4 | from modules import shared, sd_models 5 | 6 | from sd_bmab.base.context import Context 7 | from sd_bmab.base.processorbase import ProcessorBase 8 | 9 | 10 | base_sd_model = None 11 | 12 | 13 | def change_model(name): 14 | if name is None: 15 | return 16 | info = sd_models.get_closet_checkpoint_match(name) 17 | if info is None: 18 | print(f'Unknown model: {name}') 19 | return 20 | sd_models.reload_model_weights(shared.sd_model, info) 21 | 22 | 23 | class ApplyModel(ProcessorBase): 24 | def __init__(self) -> None: 25 | super().__init__() 26 | 27 | def preprocess(self, context: Context, image: Image): 28 | return shared.opts.bmab_use_specific_model 29 | 30 | def process(self, context: Context, image: Image): 31 | global base_sd_model 32 | base_sd_model = shared.opts.data['sd_model_checkpoint'] 33 | change_model(shared.opts.bmab_model) 34 | return image 35 | 36 | def postprocess(self, context: Context, image: Image): 37 | pass 38 | 39 | 40 | class RollbackModel(ProcessorBase): 41 | def __init__(self) -> None: 42 | super().__init__() 43 | 44 | def preprocess(self, context: Context, image: Image): 45 | return shared.opts.bmab_use_specific_model 46 | 47 | def process(self, context: Context, image: Image): 48 | global base_sd_model 49 | if base_sd_model is not None: 50 | change_model(base_sd_model) 51 | base_sd_model = None 52 | return image 53 | 54 | def postprocess(self, context: Context, image: Image): 55 | pass 56 | -------------------------------------------------------------------------------- /sd_bmab/sd_override/__init__.py: -------------------------------------------------------------------------------- 1 | from sd_bmab.sd_override.samper import override_samplers 2 | from sd_bmab.sd_override.img2img import StableDiffusionProcessingImg2ImgOv 3 | from sd_bmab.sd_override.txt2img import StableDiffusionProcessingTxt2ImgOv 4 | 5 | from modules import processing 6 | 7 | processing.StableDiffusionProcessingImg2Img = StableDiffusionProcessingImg2ImgOv 8 | processing.StableDiffusionProcessingTxt2Img = StableDiffusionProcessingTxt2ImgOv 9 | 10 | 11 | def override_sd_webui(): 12 | # override_samplers() 13 | pass 14 | -------------------------------------------------------------------------------- /sd_bmab/sd_override/img2img.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from modules.processing import StableDiffusionProcessingImg2Img 4 | 5 | 6 | @dataclass(repr=False) 7 | class StableDiffusionProcessingImg2ImgOv(StableDiffusionProcessingImg2Img): 8 | extra_noise: int = 0 9 | 10 | def __post_init__(self): 11 | super().__post_init__() 12 | 13 | def init(self, all_prompts, all_seeds, all_subseeds): 14 | ret = super().init(all_prompts, all_seeds, all_subseeds) 15 | self.extra_generation_params['Hires prompt'] = '' 16 | self.extra_generation_params['Hires negative prompt'] = '' 17 | return ret 18 | 19 | def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts): 20 | return super().sample(conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts) 21 | 22 | -------------------------------------------------------------------------------- /sd_bmab/sd_override/samper.py: -------------------------------------------------------------------------------- 1 | import modules 2 | import k_diffusion.sampling 3 | from modules.sd_samplers_kdiffusion import KDiffusionSampler 4 | from modules.sd_samplers import set_samplers 5 | from modules.shared import opts, state 6 | import inspect 7 | from modules import sd_samplers_common 8 | from modules.script_callbacks import ExtraNoiseParams, extra_noise_callback 9 | 10 | 11 | class KDiffusionSamplerBMAB(KDiffusionSampler): 12 | 13 | def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, steps=None, image_conditioning=None): 14 | steps, t_enc = sd_samplers_common.setup_img2img_steps(p, steps) 15 | 16 | sigmas = self.get_sigmas(p, steps) 17 | sigma_sched = sigmas[steps - t_enc - 1:] 18 | 19 | xi = x + noise * sigma_sched[0] 20 | 21 | if opts.img2img_extra_noise > 0: 22 | p.extra_generation_params["Extra noise"] = opts.img2img_extra_noise 23 | extra_noise_params = ExtraNoiseParams(noise, x, xi) 24 | extra_noise_callback(extra_noise_params) 25 | noise = extra_noise_params.noise 26 | xi += noise * opts.img2img_extra_noise 27 | 28 | if hasattr(p, 'extra_noise') and p.extra_noise > 0: 29 | p.extra_generation_params["Extra noise"] = p.extra_noise 30 | extra_noise_params = ExtraNoiseParams(noise, x, xi) 31 | extra_noise_callback(extra_noise_params) 32 | noise = extra_noise_params.noise 33 | xi += noise * p.extra_noise 34 | 35 | extra_params_kwargs = self.initialize(p) 36 | parameters = inspect.signature(self.func).parameters 37 | 38 | if 'sigma_min' in parameters: 39 | ## last sigma is zero which isn't allowed by DPM Fast & Adaptive so taking value before last 40 | extra_params_kwargs['sigma_min'] = sigma_sched[-2] 41 | if 'sigma_max' in parameters: 42 | extra_params_kwargs['sigma_max'] = sigma_sched[0] 43 | if 'n' in parameters: 44 | extra_params_kwargs['n'] = len(sigma_sched) - 1 45 | if 'sigma_sched' in parameters: 46 | extra_params_kwargs['sigma_sched'] = sigma_sched 47 | if 'sigmas' in parameters: 48 | extra_params_kwargs['sigmas'] = sigma_sched 49 | 50 | if self.config.options.get('brownian_noise', False): 51 | noise_sampler = self.create_noise_sampler(x, sigmas, p) 52 | extra_params_kwargs['noise_sampler'] = noise_sampler 53 | 54 | if self.config.options.get('solver_type', None) == 'heun': 55 | extra_params_kwargs['solver_type'] = 'heun' 56 | 57 | self.model_wrap_cfg.init_latent = x 58 | self.last_latent = x 59 | self.sampler_extra_args = { 60 | 'cond': conditioning, 61 | 'image_cond': image_conditioning, 62 | 'uncond': unconditional_conditioning, 63 | 'cond_scale': p.cfg_scale, 64 | 's_min_uncond': self.s_min_uncond 65 | } 66 | 67 | samples = self.launch_sampling(t_enc + 1, lambda: self.func(self.model_wrap_cfg, xi, extra_args=self.sampler_extra_args, disable=False, callback=self.callback_state, **extra_params_kwargs)) 68 | 69 | if self.model_wrap_cfg.padded_cond_uncond: 70 | p.extra_generation_params["Pad conds"] = True 71 | 72 | return samples 73 | 74 | 75 | def override_samplers(): 76 | modules.sd_samplers_kdiffusion.samplers_data_k_diffusion = [ 77 | modules.sd_samplers_common.SamplerData(label, 78 | lambda model, funcname=funcname: KDiffusionSamplerBMAB(funcname, model), 79 | aliases, options) 80 | for label, funcname, aliases, options in modules.sd_samplers_kdiffusion.samplers_k_diffusion 81 | if callable(funcname) or hasattr(k_diffusion.sampling, funcname) 82 | ] 83 | if hasattr(modules, 'sd_samplers_timesteps'): 84 | modules.sd_samplers.all_samplers = [ 85 | *modules.sd_samplers_kdiffusion.samplers_data_k_diffusion, 86 | *modules.sd_samplers_timesteps.samplers_data_timesteps, 87 | ] 88 | else: 89 | modules.sd_samplers.all_samplers = [ 90 | *modules.sd_samplers_kdiffusion.samplers_data_k_diffusion, 91 | *modules.sd_samplers_compvis.samplers_data_compvis, 92 | ] 93 | modules.sd_samplers.all_samplers_map = {x.name: x for x in modules.sd_samplers.all_samplers} 94 | -------------------------------------------------------------------------------- /sd_bmab/sd_override/sd_models.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from modules import sd_models 4 | from modules import shared 5 | 6 | 7 | def bmab_list_models(): 8 | sd_models.checkpoints_list.clear() 9 | sd_models.checkpoint_aliases.clear() 10 | 11 | cmd_ckpt = shared.cmd_opts.ckpt 12 | if shared.cmd_opts.no_download_sd_model or cmd_ckpt != shared.sd_model_file or os.path.exists(cmd_ckpt): 13 | model_url = None 14 | else: 15 | model_url = f"{shared.hf_endpoint}/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors" 16 | 17 | model_list = sd_models.modelloader.load_models(model_path=sd_models.model_path, model_url=model_url, command_path=shared.cmd_opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name="v1-5-pruned-emaonly.safetensors", ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) 18 | 19 | second_path = shared.opts.data.get('bmab_additional_checkpoint_path', '') 20 | print(f'second path {second_path}') 21 | if os.path.exists(second_path): 22 | print(f'load checkpoint from {second_path}') 23 | model_list_seconds = sd_models.modelloader.load_models(model_path=second_path, model_url=model_url, command_path=shared.cmd_opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name="v1-5-pruned-emaonly.safetensors", ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) 24 | length = len(sd_models.model_path) 25 | temp = [(x[length:], x) for x in model_list] 26 | length = len(second_path) 27 | temp.extend([(x[length:], x) for x in model_list_seconds]) 28 | model_list = [x[1] for x in sorted(temp, key=lambda x: x[0])] 29 | 30 | if os.path.exists(cmd_ckpt): 31 | checkpoint_info = sd_models.CheckpointInfo(cmd_ckpt) 32 | checkpoint_info.register() 33 | shared.opts.data['sd_model_checkpoint'] = checkpoint_info.title 34 | elif cmd_ckpt is not None and cmd_ckpt != shared.default_sd_model_file: 35 | print(f"Checkpoint in --ckpt argument not found (Possible it was moved to {sd_models.model_path}: {cmd_ckpt}", file=sys.stderr) 36 | 37 | for filename in model_list: 38 | checkpoint_info = sd_models.CheckpointInfo(filename) 39 | checkpoint_info.register() 40 | 41 | 42 | def override(): 43 | bmab_list_models() 44 | sd_models.list_models = bmab_list_models 45 | 46 | -------------------------------------------------------------------------------- /sd_bmab/util/installhelper.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | 3 | import launch 4 | from modules import launch_utils 5 | import torch 6 | import platform 7 | 8 | 9 | groundingdino_install_info = { 10 | '2.1.2+cu121:3.10:linux:amd64': 'https://github.com/Bing-su/GroundingDINO/releases/download/v23.9.27/groundingdino-23.9.27+torch2.1.0.cu121-cp310-cp310-manylinux_2_34_x86_64.whl', 11 | '2.1.2+cu121:3.10:windows:amd64': 'https://github.com/Bing-su/GroundingDINO/releases/download/v23.9.27/groundingdino-23.9.27+torch2.1.0.cu121-cp310-cp310-win_amd64.whl', 12 | '2.1.2+cu121:3.11:linux:amd64': 'https://github.com/Bing-su/GroundingDINO/releases/download/v23.9.27/groundingdino-23.9.27+torch2.1.0.cu121-cp311-cp311-manylinux_2_34_x86_64.whl', 13 | '2.1.2+cu121:3.11:windows:amd64': 'https://github.com/Bing-su/GroundingDINO/releases/download/v23.9.27/groundingdino-23.9.27+torch2.1.0.cu121-cp311-cp311-win_amd64.whl', 14 | '2.1.2+cu121:3.9:linux:amd64': 'https://github.com/Bing-su/GroundingDINO/releases/download/v23.9.27/groundingdino-23.9.27+torch2.1.0.cu121-cp39-cp39-manylinux_2_34_x86_64.whl', 15 | '2.1.2+cu121:3.9:windows:amd64': 'https://github.com/Bing-su/GroundingDINO/releases/download/v23.9.27/groundingdino-23.9.27+torch2.1.0.cu121-cp39-cp39-win_amd64.whl', 16 | } 17 | 18 | groundingdino_install_replacement = { 19 | '2.1.0+cu121:3.10:linux:amd64': '2.1.2+cu121:3.10:linux:amd64', 20 | '2.1.1+cu121:3.10:linux:amd64': '2.1.2+cu121:3.10:linux:amd64', 21 | } 22 | 23 | 24 | available_packages = ['GroundingDINO'] 25 | 26 | 27 | def install_groudingdino(url): 28 | launch_utils.run(f'{launch_utils.python} -m pip uninstall --yes groundingdino', live=True) 29 | launch_utils.run(f'{launch_utils.python} -m pip uninstall --yes pycocotools', live=True) 30 | 31 | print('Install pycocotools') 32 | launch.run_pip('install pycocotools', 'sd-webui-bmab requirement: pycocotools') 33 | 34 | print(f'Install groundingdino from {url}. It takes minutes.') 35 | launch.run_pip(f'install {url}', 'sd-webui-bmab requirement: groundingdino') 36 | print('Done.') 37 | 38 | 39 | def get_condition(): 40 | torch_version = torch.__version__ 41 | pv = platform.python_version_tuple() 42 | system = 'windows' if platform.system() == 'Windows' else 'linux' 43 | machine = 'amd64' if platform.machine() == 'AMD64' else 'x86_64' 44 | return f'{torch_version}:{pv[0]}.{pv[1]}:{system}:{machine}' 45 | 46 | 47 | def get_temporary(): 48 | if platform.system() == 'Windows': 49 | return 'c:\\temp' 50 | return '/temp' 51 | 52 | 53 | def install(pkg_name, dd_pkg, markdown_install): 54 | groundingdino_cuda_name = 'GroundingDINO for CUDA' 55 | groundingdino_selected = pkg_name 56 | 57 | def add_new_available(cond): 58 | msg = f'GroundingDINO for CUDA Found {cond}. Please select GroudingDINO or {groundingdino_cuda_name}' 59 | newname = f'{groundingdino_cuda_name}-{cond}' 60 | if newname not in available_packages: 61 | available_packages.append(newname) 62 | return msg, newname 63 | 64 | def install_normal_groundingdino(c): 65 | url = 'https://github.com/IDEA-Research/GroundingDINO' 66 | launch_utils.run(f'{launch_utils.python} -m pip uninstall --yes groundingdino', live=True) 67 | launch_utils.run(f'{launch_utils.python} -m pip uninstall --yes pycocotools', live=True) 68 | launch_utils.run(f'{launch_utils.python} -m pip install pycocotools', live=True) 69 | if platform.system() == 'Windows': 70 | launch_utils.run(f'{launch_utils.git} clone {url} c:\\Temp\\groundingdino', live=True) 71 | launch_utils.run(f'cd c:\\Temp\\groundingdino && {launch_utils.python} -m pip install -e .', live=True) 72 | shutil.rmtree('c:\\Temp\\groundingdino', ignore_errors=True) 73 | else: 74 | launch_utils.run(f'{launch_utils.git} clone {url} /temp/groundingdino', live=True) 75 | launch_utils.run(f'cd /temp/groundingdino && {launch_utils.python} -m pip install -e .', live=True) 76 | shutil.rmtree('rm -rf /temp/groundingdino', ignore_errors=True) 77 | 78 | return f'Nothing found for this cuda system {c}. Software version of GroundingDINO installed (VERY SLOW!!!)' 79 | 80 | def cuda_in_available_packages(): 81 | for x in available_packages: 82 | if x.startswith(groundingdino_cuda_name): 83 | return True 84 | return False 85 | 86 | if pkg_name == 'GroundingDINO': 87 | cond = get_condition() 88 | if cuda_in_available_packages(): 89 | msg = install_normal_groundingdino(cond) 90 | else: 91 | replacement = groundingdino_install_replacement.get(cond) 92 | if replacement is not None: 93 | msg, groundingdino_selected = add_new_available(cond) 94 | else: 95 | groudingdino_for_cuda = groundingdino_install_info.get(cond) 96 | if groudingdino_for_cuda is None: 97 | msg = install_normal_groundingdino(cond) 98 | else: 99 | msg, groundingdino_selected = add_new_available(cond) 100 | elif pkg_name.startswith(groundingdino_cuda_name): 101 | groudingdino_for_cuda_cond = pkg_name[len(groundingdino_cuda_name)+1:] 102 | groudingdino_for_cuda = groundingdino_install_info.get(groudingdino_for_cuda_cond) 103 | if groudingdino_for_cuda is not None: 104 | install_groudingdino(groudingdino_for_cuda) 105 | msg = f'{groundingdino_cuda_name} installed. {groudingdino_for_cuda}' 106 | groundingdino_selected = f'{groundingdino_cuda_name}-{groudingdino_for_cuda_cond}' 107 | else: 108 | msg = 'Nothing installed.' 109 | else: 110 | msg = 'Nothing installed.' 111 | 112 | return { 113 | dd_pkg: { 114 | 'choices': available_packages, 115 | 'value': groundingdino_selected, 116 | '__type__': 'update' 117 | }, 118 | markdown_install: { 119 | 'value': msg, 120 | '__type__': 'update' 121 | } 122 | } 123 | 124 | --------------------------------------------------------------------------------