|
1 |
| -import os.path |
2 |
| -from concurrent.futures import ProcessPoolExecutor |
3 |
| -import multiprocessing |
4 |
| -import time |
| 1 | +import os |
5 | 2 | import re
|
6 | 3 |
|
| 4 | +import torch |
| 5 | +from PIL import Image |
| 6 | +import numpy as np |
| 7 | + |
| 8 | +from modules import modelloader, paths, deepbooru_model, devices, images, shared |
| 9 | + |
7 | 10 | re_special = re.compile(r'([\\()])')
|
8 | 11 |
|
9 |
| -def get_deepbooru_tags(pil_image): |
10 |
| - """ |
11 |
| - This method is for running only one image at a time for simple use. Used to the img2img interrogate. |
12 |
| - """ |
13 |
| - from modules import shared # prevents circular reference |
14 |
| - |
15 |
| - try: |
16 |
| - create_deepbooru_process(shared.opts.interrogate_deepbooru_score_threshold, create_deepbooru_opts()) |
17 |
| - return get_tags_from_process(pil_image) |
18 |
| - finally: |
19 |
| - release_process() |
20 |
| - |
21 |
| - |
22 |
| -OPT_INCLUDE_RANKS = "include_ranks" |
23 |
| -def create_deepbooru_opts(): |
24 |
| - from modules import shared |
25 |
| - |
26 |
| - return { |
27 |
| - "use_spaces": shared.opts.deepbooru_use_spaces, |
28 |
| - "use_escape": shared.opts.deepbooru_escape, |
29 |
| - "alpha_sort": shared.opts.deepbooru_sort_alpha, |
30 |
| - OPT_INCLUDE_RANKS: shared.opts.interrogate_return_ranks, |
31 |
| - } |
32 |
| - |
33 |
| - |
34 |
| -def deepbooru_process(queue, deepbooru_process_return, threshold, deepbooru_opts): |
35 |
| - model, tags = get_deepbooru_tags_model() |
36 |
| - while True: # while process is running, keep monitoring queue for new image |
37 |
| - pil_image = queue.get() |
38 |
| - if pil_image == "QUIT": |
39 |
| - break |
40 |
| - else: |
41 |
| - deepbooru_process_return["value"] = get_deepbooru_tags_from_model(model, tags, pil_image, threshold, deepbooru_opts) |
42 |
| - |
43 |
| - |
44 |
| -def create_deepbooru_process(threshold, deepbooru_opts): |
45 |
| - """ |
46 |
| - Creates deepbooru process. A queue is created to send images into the process. This enables multiple images |
47 |
| - to be processed in a row without reloading the model or creating a new process. To return the data, a shared |
48 |
| - dictionary is created to hold the tags created. To wait for tags to be returned, a value of -1 is assigned |
49 |
| - to the dictionary and the method adding the image to the queue should wait for this value to be updated with |
50 |
| - the tags. |
51 |
| - """ |
52 |
| - from modules import shared # prevents circular reference |
53 |
| - context = multiprocessing.get_context("spawn") |
54 |
| - shared.deepbooru_process_manager = context.Manager() |
55 |
| - shared.deepbooru_process_queue = shared.deepbooru_process_manager.Queue() |
56 |
| - shared.deepbooru_process_return = shared.deepbooru_process_manager.dict() |
57 |
| - shared.deepbooru_process_return["value"] = -1 |
58 |
| - shared.deepbooru_process = context.Process(target=deepbooru_process, args=(shared.deepbooru_process_queue, shared.deepbooru_process_return, threshold, deepbooru_opts)) |
59 |
| - shared.deepbooru_process.start() |
60 |
| - |
61 |
| - |
62 |
| -def get_tags_from_process(image): |
63 |
| - from modules import shared |
64 |
| - |
65 |
| - shared.deepbooru_process_return["value"] = -1 |
66 |
| - shared.deepbooru_process_queue.put(image) |
67 |
| - while shared.deepbooru_process_return["value"] == -1: |
68 |
| - time.sleep(0.2) |
69 |
| - caption = shared.deepbooru_process_return["value"] |
70 |
| - shared.deepbooru_process_return["value"] = -1 |
71 |
| - |
72 |
| - return caption |
73 |
| - |
74 |
| - |
75 |
| -def release_process(): |
76 |
| - """ |
77 |
| - Stops the deepbooru process to return used memory |
78 |
| - """ |
79 |
| - from modules import shared # prevents circular reference |
80 |
| - shared.deepbooru_process_queue.put("QUIT") |
81 |
| - shared.deepbooru_process.join() |
82 |
| - shared.deepbooru_process_queue = None |
83 |
| - shared.deepbooru_process = None |
84 |
| - shared.deepbooru_process_return = None |
85 |
| - shared.deepbooru_process_manager = None |
86 |
| - |
87 |
| -def get_deepbooru_tags_model(): |
88 |
| - import deepdanbooru as dd |
89 |
| - import tensorflow as tf |
90 |
| - import numpy as np |
91 |
| - this_folder = os.path.dirname(__file__) |
92 |
| - model_path = os.path.abspath(os.path.join(this_folder, '..', 'models', 'deepbooru')) |
93 |
| - if not os.path.exists(os.path.join(model_path, 'project.json')): |
94 |
| - # there is no point importing these every time |
95 |
| - import zipfile |
96 |
| - from basicsr.utils.download_util import load_file_from_url |
97 |
| - load_file_from_url( |
98 |
| - r"https://github.com/KichangKim/DeepDanbooru/releases/download/v3-20211112-sgd-e28/deepdanbooru-v3-20211112-sgd-e28.zip", |
99 |
| - model_path) |
100 |
| - with zipfile.ZipFile(os.path.join(model_path, "deepdanbooru-v3-20211112-sgd-e28.zip"), "r") as zip_ref: |
101 |
| - zip_ref.extractall(model_path) |
102 |
| - os.remove(os.path.join(model_path, "deepdanbooru-v3-20211112-sgd-e28.zip")) |
103 |
| - |
104 |
| - tags = dd.project.load_tags_from_project(model_path) |
105 |
| - model = dd.project.load_model_from_project( |
106 |
| - model_path, compile_model=False |
107 |
| - ) |
108 |
| - return model, tags |
109 |
| - |
110 |
| - |
111 |
| -def get_deepbooru_tags_from_model(model, tags, pil_image, threshold, deepbooru_opts): |
112 |
| - import deepdanbooru as dd |
113 |
| - import tensorflow as tf |
114 |
| - import numpy as np |
115 |
| - |
116 |
| - alpha_sort = deepbooru_opts['alpha_sort'] |
117 |
| - use_spaces = deepbooru_opts['use_spaces'] |
118 |
| - use_escape = deepbooru_opts['use_escape'] |
119 |
| - include_ranks = deepbooru_opts['include_ranks'] |
120 |
| - |
121 |
| - width = model.input_shape[2] |
122 |
| - height = model.input_shape[1] |
123 |
| - image = np.array(pil_image) |
124 |
| - image = tf.image.resize( |
125 |
| - image, |
126 |
| - size=(height, width), |
127 |
| - method=tf.image.ResizeMethod.AREA, |
128 |
| - preserve_aspect_ratio=True, |
129 |
| - ) |
130 |
| - image = image.numpy() # EagerTensor to np.array |
131 |
| - image = dd.image.transform_and_pad_image(image, width, height) |
132 |
| - image = image / 255.0 |
133 |
| - image_shape = image.shape |
134 |
| - image = image.reshape((1, image_shape[0], image_shape[1], image_shape[2])) |
135 |
| - |
136 |
| - y = model.predict(image)[0] |
137 |
| - |
138 |
| - result_dict = {} |
139 |
| - |
140 |
| - for i, tag in enumerate(tags): |
141 |
| - result_dict[tag] = y[i] |
142 |
| - |
143 |
| - unsorted_tags_in_theshold = [] |
144 |
| - result_tags_print = [] |
145 |
| - for tag in tags: |
146 |
| - if result_dict[tag] >= threshold: |
| 12 | + |
| 13 | +class DeepDanbooru: |
| 14 | + def __init__(self): |
| 15 | + self.model = None |
| 16 | + |
| 17 | + def load(self): |
| 18 | + if self.model is not None: |
| 19 | + return |
| 20 | + |
| 21 | + files = modelloader.load_models( |
| 22 | + model_path=os.path.join(paths.models_path, "torch_deepdanbooru"), |
| 23 | + model_url='https://github.com/AUTOMATIC1111/TorchDeepDanbooru/releases/download/v1/model-resnet_custom_v3.pt', |
| 24 | + ext_filter=".pt", |
| 25 | + download_name='model-resnet_custom_v3.pt', |
| 26 | + ) |
| 27 | + |
| 28 | + self.model = deepbooru_model.DeepDanbooruModel() |
| 29 | + self.model.load_state_dict(torch.load(files[0], map_location="cpu")) |
| 30 | + |
| 31 | + self.model.eval() |
| 32 | + self.model.to(devices.cpu, devices.dtype) |
| 33 | + |
| 34 | + def start(self): |
| 35 | + self.load() |
| 36 | + self.model.to(devices.device) |
| 37 | + |
| 38 | + def stop(self): |
| 39 | + if not shared.opts.interrogate_keep_models_in_memory: |
| 40 | + self.model.to(devices.cpu) |
| 41 | + devices.torch_gc() |
| 42 | + |
| 43 | + def tag(self, pil_image): |
| 44 | + self.start() |
| 45 | + res = self.tag_multi(pil_image) |
| 46 | + self.stop() |
| 47 | + |
| 48 | + return res |
| 49 | + |
| 50 | + def tag_multi(self, pil_image, force_disable_ranks=False): |
| 51 | + threshold = shared.opts.interrogate_deepbooru_score_threshold |
| 52 | + use_spaces = shared.opts.deepbooru_use_spaces |
| 53 | + use_escape = shared.opts.deepbooru_escape |
| 54 | + alpha_sort = shared.opts.deepbooru_sort_alpha |
| 55 | + include_ranks = shared.opts.interrogate_return_ranks and not force_disable_ranks |
| 56 | + |
| 57 | + pic = images.resize_image(2, pil_image.convert("RGB"), 512, 512) |
| 58 | + a = np.expand_dims(np.array(pic, dtype=np.float32), 0) / 255 |
| 59 | + |
| 60 | + with torch.no_grad(), devices.autocast(): |
| 61 | + x = torch.from_numpy(a).cuda() |
| 62 | + y = self.model(x)[0].detach().cpu().numpy() |
| 63 | + |
| 64 | + probability_dict = {} |
| 65 | + |
| 66 | + for tag, probability in zip(self.model.tags, y): |
| 67 | + if probability < threshold: |
| 68 | + continue |
| 69 | + |
147 | 70 | if tag.startswith("rating:"):
|
148 | 71 | continue
|
149 |
| - unsorted_tags_in_theshold.append((result_dict[tag], tag)) |
150 |
| - result_tags_print.append(f'{result_dict[tag]} {tag}') |
151 |
| - |
152 |
| - # sort tags |
153 |
| - result_tags_out = [] |
154 |
| - sort_ndx = 0 |
155 |
| - if alpha_sort: |
156 |
| - sort_ndx = 1 |
157 |
| - |
158 |
| - # sort by reverse by likelihood and normal for alpha, and format tag text as requested |
159 |
| - unsorted_tags_in_theshold.sort(key=lambda y: y[sort_ndx], reverse=(not alpha_sort)) |
160 |
| - for weight, tag in unsorted_tags_in_theshold: |
161 |
| - tag_outformat = tag |
162 |
| - if use_spaces: |
163 |
| - tag_outformat = tag_outformat.replace('_', ' ') |
164 |
| - if use_escape: |
165 |
| - tag_outformat = re.sub(re_special, r'\\\1', tag_outformat) |
166 |
| - if include_ranks: |
167 |
| - tag_outformat = f"({tag_outformat}:{weight:.3f})" |
168 |
| - |
169 |
| - result_tags_out.append(tag_outformat) |
170 |
| - |
171 |
| - print('\n'.join(sorted(result_tags_print, reverse=True))) |
172 |
| - |
173 |
| - return ', '.join(result_tags_out) |
| 72 | + |
| 73 | + probability_dict[tag] = probability |
| 74 | + |
| 75 | + if alpha_sort: |
| 76 | + tags = sorted(probability_dict) |
| 77 | + else: |
| 78 | + tags = [tag for tag, _ in sorted(probability_dict.items(), key=lambda x: -x[1])] |
| 79 | + |
| 80 | + res = [] |
| 81 | + |
| 82 | + for tag in tags: |
| 83 | + probability = probability_dict[tag] |
| 84 | + tag_outformat = tag |
| 85 | + if use_spaces: |
| 86 | + tag_outformat = tag_outformat.replace('_', ' ') |
| 87 | + if use_escape: |
| 88 | + tag_outformat = re.sub(re_special, r'\\\1', tag_outformat) |
| 89 | + if include_ranks: |
| 90 | + tag_outformat = f"({tag_outformat}:{probability:.3f})" |
| 91 | + |
| 92 | + res.append(tag_outformat) |
| 93 | + |
| 94 | + return ", ".join(res) |
| 95 | + |
| 96 | + |
| 97 | +model = DeepDanbooru() |
0 commit comments