Initial commit
commit
06a290f0e7
|
|
@ -0,0 +1,3 @@
|
|||
.idea
|
||||
__pycache__/
|
||||
/outputs/
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 Imperia Online JSC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<img alt="" src="https://img.shields.io/badge/Python-FFD43B?style=for-the-badge&logo=python&logoColor=blue" />
|
||||
<img alt="" src="https://img.shields.io/badge/PyTorch-EE4C2C?style=for-the-badge&logo=pytorch&logoColor=white" />
|
||||
<img alt="" src="https://img.shields.io/badge/Numpy-777BB4?style=for-the-badge&logo=numpy&logoColor=white" />
|
||||
|
||||
# eyemask
|
||||
|
||||
This extension is for AUTOMATIC1111's [Stable Diffusion web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui)
|
||||
|
||||
### Capabilities
|
||||
|
||||
* Create mask for eyes / face / body and redraw by multiple params
|
||||
* Wildcards for mask prompt and original prompt
|
||||
* Using of different model just for masked area redraw
|
||||
* Mask padding in px or percents
|
||||
* Mask previews
|
||||
* Separate embedded version of the script (enabled from settings)
|
||||
* Batch mode support
|
||||
* Custom API routes (including serving static files)
|
||||
|
||||
Put all wildcards files in */wildcards* dir.
|
||||
|
||||
### Install
|
||||
|
||||
Use *Install from URL* option with this repo url.
|
||||
|
||||
### Requirements
|
||||
|
||||
All requirements will be installed on first use, except for:
|
||||
- [cmake](https://cmake.org/download/) - used only for dlib masks
|
||||
|
||||
### Mask types
|
||||
|
||||
1. Eyes dlib
|
||||
2. Face dlib
|
||||
3. Face depthmask
|
||||
4. Body depthmask
|
||||
5. Face mmdet
|
||||
6. Body mmdet
|
||||
|
||||
### Examples
|
||||
|
||||
<img width="1024" src="https://bitbucket.imperiaonline.org/projects/OPAI/repos/eyemask/raw/static/images/mask-types.jpg" alt="">
|
||||
|
||||
### Contributing
|
||||
|
||||
Feel free to submit PRs to develop!
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
from launch import is_installed, run, git_clone
|
||||
from modules.paths import models_path
|
||||
from modules.sd_models import model_hash
|
||||
from modules import modelloader
|
||||
from basicsr.utils.download_util import load_file_from_url
|
||||
|
||||
dd_models_path = os.path.join(models_path, "mmdet")
|
||||
|
||||
def list_models(model_path):
|
||||
model_list = modelloader.load_models(model_path=model_path, ext_filter=[".pth"])
|
||||
|
||||
def modeltitle(path, shorthash):
|
||||
abspath = os.path.abspath(path)
|
||||
|
||||
if abspath.startswith(model_path):
|
||||
name = abspath.replace(model_path, '')
|
||||
else:
|
||||
name = os.path.basename(path)
|
||||
|
||||
if name.startswith("\\") or name.startswith("/"):
|
||||
name = name[1:]
|
||||
|
||||
shortname = os.path.splitext(name.replace("/", "_").replace("\\", "_"))[0]
|
||||
|
||||
return f'{name} [{shorthash}]', shortname
|
||||
|
||||
models = []
|
||||
for filename in model_list:
|
||||
h = model_hash(filename)
|
||||
title, short_model_name = modeltitle(filename, h)
|
||||
models.append(title)
|
||||
|
||||
return models
|
||||
|
||||
print('Installing requirements for eyemask')
|
||||
|
||||
if not is_installed("dlib"):
|
||||
python = sys.executable
|
||||
run(f'"{python}" -m pip install setuptools', desc="Installing setuptools", errdesc="Couldn't install setuptools")
|
||||
run(f'"{python}" -m pip install dlib', desc="Installing dlib", errdesc="Couldn't install dlib")
|
||||
|
||||
if not is_installed("mmdet"):
|
||||
python = sys.executable
|
||||
run(f'"{python}" -m pip install -U openmim', desc="Installing openmim", errdesc="Couldn't install openmim")
|
||||
run(f'"{python}" -m mim install mmcv-full', desc=f"Installing mmcv-full", errdesc=f"Couldn't install mmcv-full")
|
||||
run(f'"{python}" -m pip install mmdet', desc=f"Installing mmdet", errdesc=f"Couldn't install mmdet")
|
||||
|
||||
if (len(list_models(dd_models_path)) == 0):
|
||||
print("No detection models found, downloading...")
|
||||
bbox_path = os.path.join(dd_models_path, "bbox")
|
||||
segm_path = os.path.join(dd_models_path, "segm")
|
||||
load_file_from_url("https://huggingface.co/dustysys/ddetailer/resolve/main/mmdet/bbox/mmdet_anime-face_yolov3.pth", bbox_path)
|
||||
load_file_from_url("https://huggingface.co/dustysys/ddetailer/raw/main/mmdet/bbox/mmdet_anime-face_yolov3.py", bbox_path)
|
||||
load_file_from_url("https://huggingface.co/dustysys/ddetailer/resolve/main/mmdet/segm/mmdet_dd-person_mask2former.pth", segm_path)
|
||||
load_file_from_url("https://huggingface.co/dustysys/ddetailer/raw/main/mmdet/segm/mmdet_dd-person_mask2former.py", segm_path)
|
||||
|
||||
git_clone("https://github.com/isl-org/MiDaS.git", "repositories/midas", "midas")
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
|
||||
var dialog = (function () {
|
||||
|
||||
var DIALOG_FADE_IN_SPEED = 250;
|
||||
var DIALOG_FADE_OUT_SPEED = 150;
|
||||
|
||||
var $mainContainer = null;
|
||||
|
||||
var onClose = null;
|
||||
|
||||
function getContainer() {
|
||||
if (! $mainContainer) {
|
||||
$mainContainer = $('#dialogs-container');
|
||||
}
|
||||
return $mainContainer;
|
||||
}
|
||||
|
||||
var mainLayout = (
|
||||
'<div id="{0}-modal" class="modal" tabindex="-1">' +
|
||||
'<div class="modal-header">' +
|
||||
'<h5 class="modal-title"></h5>' +
|
||||
'</div>' +
|
||||
'<div id="{0}-modal-content" class="modal-content"></div>' +
|
||||
'<div class="modal-footer"></div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
function getContent(opt) {
|
||||
return mainLayout.format(opt.id);
|
||||
}
|
||||
|
||||
function getButton(btnOpt) {
|
||||
var template = '<button {0} type="button" class="button {1}">{2}</button>';
|
||||
var id = btnOpt.id ? ' id="' + btnOpt.id + '-modal-btn" ' : '';
|
||||
var className = btnOpt.className ? btnOpt.className : '';
|
||||
var text = btnOpt.text;
|
||||
return template.format(id, className, text);
|
||||
}
|
||||
|
||||
function close() {
|
||||
getContainer()
|
||||
.removeClass('open')
|
||||
.find('div.modal').fadeOut(DIALOG_FADE_OUT_SPEED);
|
||||
|
||||
if (onClose && onClose.call) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
function onFooterBtnClick(data) {
|
||||
if (data.action && data.action.call) {
|
||||
data.action.call(this, data.params || []);
|
||||
}
|
||||
if (! data.dontClose) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
function show(opt) {
|
||||
|
||||
onClose = null;
|
||||
close();
|
||||
|
||||
if ((typeof opt).toLowerCase() === 'string') {
|
||||
opt = {
|
||||
content: opt
|
||||
};
|
||||
}
|
||||
|
||||
if (! opt.id) {
|
||||
opt.id = +new Date;
|
||||
}
|
||||
|
||||
onClose = (opt.onClose && opt.onClose.call) ? opt.onClose : null;
|
||||
|
||||
var dialogId = '#{0}-modal'.format(opt.id);
|
||||
|
||||
if (! getContainer().find(dialogId).length) {
|
||||
getContainer().append(getContent(opt));
|
||||
}
|
||||
|
||||
$dialog = $(dialogId);
|
||||
|
||||
if (opt.big || opt.imageSrc) {
|
||||
var windowHeight = $(window).height();
|
||||
var minHeight = parseInt(windowHeight * 0.8) - 100;
|
||||
var top = parseInt(windowHeight * 0.08);
|
||||
$dialog.css('top', top + 'px');
|
||||
if (! opt.dontStretch) {
|
||||
$dialog.find('.modal-content').css('min-height', minHeight);
|
||||
}
|
||||
$dialog.addClass('modal-big');
|
||||
|
||||
if (opt.imageSrc) {
|
||||
$dialog.find('.modal-content').css({
|
||||
'background-image': 'url(' + opt.imageSrc + ')',
|
||||
'background-size': 'contain',
|
||||
'background-repeat' : 'no-repeat',
|
||||
'background-position': 'center center'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (opt.maxWidth) {
|
||||
$dialog.css('max-width', opt.maxWidth);
|
||||
}
|
||||
|
||||
if (opt.width) {
|
||||
$dialog.css('width', opt.width);
|
||||
}
|
||||
|
||||
if (opt.top) {
|
||||
$dialog.css('top', opt.top);
|
||||
}
|
||||
|
||||
$dialog
|
||||
.find('.modal-title').html(opt.title).end()
|
||||
.find('.modal-content').html(opt.content || opt.text || '').end()
|
||||
.bindClick(function (e) {
|
||||
e.stopPropagation();
|
||||
})
|
||||
.fadeIn(DIALOG_FADE_IN_SPEED);
|
||||
|
||||
getContainer().addClass('open');
|
||||
|
||||
if (! opt.title) {
|
||||
$dialog
|
||||
.find('.modal-header').hide().end()
|
||||
.find('.modal-content').css('padding', '30px 20px')
|
||||
}
|
||||
|
||||
if (opt.removeWrapper) {
|
||||
$dialog.find('.modal-content *').first().unwrap();
|
||||
}
|
||||
$footer = $dialog.find('.modal-footer').first().empty();
|
||||
|
||||
if (! opt.disableOverlay) {
|
||||
getContainer().bindClick(close);
|
||||
} else {
|
||||
getContainer().off('click');
|
||||
}
|
||||
|
||||
if (! opt.buttons || ! opt.buttons.length) {
|
||||
opt.buttons = [{
|
||||
id: 'modal-close-button',
|
||||
text: 'Ok'
|
||||
}];
|
||||
}
|
||||
|
||||
for (var i = 0, len = opt.buttons.length; i < len; i++) {
|
||||
if (! opt.buttons[i].id) {
|
||||
opt.buttons[i].id = Math.floor(Math.random() * 1000000);
|
||||
}
|
||||
$footer
|
||||
.append(getButton(opt.buttons[i]))
|
||||
.find('#' + opt.buttons[i].id + '-modal-btn')
|
||||
.bindClick(onFooterBtnClick, [opt.buttons[i]]);
|
||||
}
|
||||
|
||||
return $dialog;
|
||||
}
|
||||
|
||||
function showConfirm(text, action) {
|
||||
this.show({
|
||||
id: 'main-confirmation',
|
||||
title: 'Confirmation',
|
||||
content: text,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Yes',
|
||||
className: 'danger',
|
||||
action: action
|
||||
}, {
|
||||
text: 'No'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function showImage(src, title, buttons, maxWidth) {
|
||||
this.show({
|
||||
id: 'image-dialog',
|
||||
imageSrc: '/sdapi/v1/eyemask/v1/static/images/' + src,
|
||||
big: true,
|
||||
maxWidth: maxWidth || null,
|
||||
title: title || null,
|
||||
buttons: buttons || null
|
||||
});
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
this.show({
|
||||
id: 'error-dialog',
|
||||
title: 'Error',
|
||||
content: message,
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
close();
|
||||
}
|
||||
|
||||
return {
|
||||
show: show,
|
||||
hide: hide,
|
||||
confirm: showConfirm,
|
||||
image: showImage,
|
||||
error: showError,
|
||||
};
|
||||
})();
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
window.BODY_SELECTOR = '.mx-auto.container'
|
||||
|
||||
toastr.options = {
|
||||
target: window.BODY_SELECTOR,
|
||||
timeOut: 3500
|
||||
};
|
||||
|
||||
window.gradioRoot = gradioApp().querySelector('.gradio-container');
|
||||
window.$gradioRoot = $(window.gradioRoot);
|
||||
|
||||
onUiLoaded(EyeMaskController.load);
|
||||
|
||||
$gradioRoot.append('<div id="dialogs-container"></div>')
|
||||
});
|
||||
|
||||
|
||||
const EyeMaskController = (function () {
|
||||
|
||||
let container = null;
|
||||
let config = {};
|
||||
|
||||
const LS_PREFIX = 'em-save-';
|
||||
const TABS = ['txt2img', 'img2img'];
|
||||
|
||||
const emTitles = {
|
||||
'\u21A9\uFE0F': 'Load current image eyemask params',
|
||||
'Redraw original': 'Change seed after each batch',
|
||||
'Include mask': 'Include mask image in result'
|
||||
};
|
||||
|
||||
function getContainer() {
|
||||
if (!container) {
|
||||
container = gradioApp().getElementById('eye-mask-container');
|
||||
}
|
||||
return container
|
||||
}
|
||||
|
||||
function getApiUrl(path) {
|
||||
return '/sdapi/v1/eyemask/v1' + path;
|
||||
}
|
||||
|
||||
function loadTitles() {
|
||||
gradioApp().querySelectorAll('span, button, select, p').forEach(function(elem) {
|
||||
if (elem) {
|
||||
let tooltip = emTitles[elem.textContent] || emTitles[elem.value];
|
||||
if (tooltip) {
|
||||
elem.title = tooltip;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
$.ajax({
|
||||
url: getApiUrl('/config.json'),
|
||||
dataType: 'json',
|
||||
async: false,
|
||||
cache: false,
|
||||
success: function(data) {
|
||||
config = data;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getAllIds(id) {
|
||||
let result = [];
|
||||
result.push('em-{0}-txt2img'.format(id));
|
||||
result.push('em-{0}-img2img'.format(id));
|
||||
result.push('em-emb-{0}-txt2img'.format(id));
|
||||
result.push('em-emb-{0}-img2img'.format(id));
|
||||
return result;
|
||||
}
|
||||
|
||||
function loadPlaceHolders() {
|
||||
if (config.em_save_prompts) {
|
||||
['txt2img_prompt', 'img2img_prompt'].forEach(handleSavedInput);
|
||||
}
|
||||
if (config.em_save_neg_prompts) {
|
||||
['txt2img_neg_prompt', 'img2img_neg_prompt'].forEach(handleSavedInput);
|
||||
}
|
||||
if (config.em_save_em_prompts) {
|
||||
getAllIds('prompt').forEach(handleSavedInput);
|
||||
}
|
||||
if (config.em_save_em_neg_prompts) {
|
||||
getAllIds('negative-prompt').forEach(handleSavedInput);
|
||||
}
|
||||
if (config.em_save_settings) {
|
||||
[
|
||||
'enabled',
|
||||
'count',
|
||||
'mask-type',
|
||||
'mask-padding',
|
||||
'mask-steps',
|
||||
'mask-blur',
|
||||
'denoising-strength',
|
||||
'full-res-padding',
|
||||
'cfg',
|
||||
'width',
|
||||
'height',
|
||||
'include-mask',
|
||||
'padding-in-px',
|
||||
'redraw-original',
|
||||
'use-other-model',
|
||||
'mask-model'
|
||||
].forEach(function (id) {
|
||||
getAllIds(id).forEach(handleSavedInput);
|
||||
});
|
||||
}
|
||||
if (config.em_save_last_script) {
|
||||
TABS.forEach(loadLastScript)
|
||||
}
|
||||
}
|
||||
|
||||
function handleSavedInput(id) {
|
||||
|
||||
let $el = $('#{0} textarea, #{0} select, #{0} input'.format(id));
|
||||
let event = 'change input';
|
||||
|
||||
if (! $el.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let value = localStorage.getItem(LS_PREFIX + id);
|
||||
|
||||
if (value) {
|
||||
switch ($el[0].type) {
|
||||
case 'checkbox':
|
||||
$el.prop('checked', value === 'true').triggerEvent(event);
|
||||
break;
|
||||
case 'radio':
|
||||
$el.filter(':checked').prop('checked', false);
|
||||
$el.filter('[value="{0}"]'.format(value)).prop('checked', true).triggerEvent(event);
|
||||
break;
|
||||
default:
|
||||
$el.val(value).triggerEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
$el.on(event,function () {
|
||||
let value = this.value;
|
||||
if (this.type && this.type === 'checkbox') {
|
||||
value = this.checked;
|
||||
}
|
||||
localStorage.setItem(LS_PREFIX + id, value);
|
||||
});
|
||||
|
||||
if (id.indexOf('emb-enabled') > -1 && value === 'true') {
|
||||
$('#' + id.replace('em-emb-enabled-', '') + '_script_container .cursor-pointer').triggerEvent('click');
|
||||
}
|
||||
|
||||
if (id.indexOf('emb-use-other-model') > -1) {
|
||||
setTimeout(function () {
|
||||
$el.triggerEvent(event);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function loadLastScript(tab) {
|
||||
|
||||
let $select = $('#{0}_script_container #script_list select'.format(tab));
|
||||
let value = localStorage.getItem(LS_PREFIX + 'last-script-' + tab);
|
||||
|
||||
$select.on('change', function () {
|
||||
localStorage.setItem(LS_PREFIX + 'last-script-' + tab, this.value);
|
||||
});
|
||||
|
||||
if (value) {
|
||||
setTimeout(function () {
|
||||
$select.val(value).triggerEvent('change');
|
||||
},0);
|
||||
}
|
||||
}
|
||||
|
||||
function onFirstLoad() {
|
||||
getConfig();
|
||||
loadTitles();
|
||||
loadPlaceHolders();
|
||||
}
|
||||
|
||||
function load() {
|
||||
container = getContainer();
|
||||
onFirstLoad();
|
||||
}
|
||||
|
||||
function showInfo() {
|
||||
dialog.image('mask-types.jpg', 'Mask Types', null, '80%');
|
||||
}
|
||||
|
||||
return {
|
||||
load,
|
||||
showInfo
|
||||
};
|
||||
}());
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
function log(m) {
|
||||
console.log(m);
|
||||
}
|
||||
|
||||
String.prototype.format = function() {
|
||||
var args = arguments;
|
||||
return this.replace(/{(\d+)}/g, function(match, number) {
|
||||
return typeof args[number] != 'undefined' ? args[number] : match;
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
$.fn.bindClick = function (func, args) {
|
||||
if (args) {
|
||||
return this.off('click').on('click', function () {
|
||||
func.apply(this, args);
|
||||
});
|
||||
} else {
|
||||
return this.off('click').on('click', func);
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.triggerEvent = function (event) {
|
||||
if (! this.length) {
|
||||
return this;
|
||||
}
|
||||
let el = this[0];
|
||||
event.split(' ').forEach(function (evt) {
|
||||
el.dispatchEvent(new Event(evt.trim()));
|
||||
});
|
||||
return this;
|
||||
};
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,476 @@
|
|||
/*
|
||||
* Toastr
|
||||
* Copyright 2012-2015
|
||||
* Authors: John Papa, Hans Fjällemark, and Tim Ferrell.
|
||||
* All Rights Reserved.
|
||||
* Use, reproduction, distribution, and modification of this code is subject to the terms and
|
||||
* conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* ARIA Support: Greta Krafsig
|
||||
*
|
||||
* Project: https://github.com/CodeSeven/toastr
|
||||
*/
|
||||
/* global define */
|
||||
(function (define) {
|
||||
define(['jquery'], function ($) {
|
||||
return (function () {
|
||||
var $container;
|
||||
var listener;
|
||||
var toastId = 0;
|
||||
var toastType = {
|
||||
error: 'error',
|
||||
info: 'info',
|
||||
success: 'success',
|
||||
warning: 'warning'
|
||||
};
|
||||
|
||||
var toastr = {
|
||||
clear: clear,
|
||||
remove: remove,
|
||||
error: error,
|
||||
getContainer: getContainer,
|
||||
info: info,
|
||||
options: {},
|
||||
subscribe: subscribe,
|
||||
success: success,
|
||||
version: '2.1.4',
|
||||
warning: warning
|
||||
};
|
||||
|
||||
var previousToast;
|
||||
|
||||
return toastr;
|
||||
|
||||
////////////////
|
||||
|
||||
function error(message, title, optionsOverride) {
|
||||
return notify({
|
||||
type: toastType.error,
|
||||
iconClass: getOptions().iconClasses.error,
|
||||
message: message,
|
||||
optionsOverride: optionsOverride,
|
||||
title: title
|
||||
});
|
||||
}
|
||||
|
||||
function getContainer(options, create) {
|
||||
if (!options) { options = getOptions(); }
|
||||
$container = $('#' + options.containerId);
|
||||
if ($container.length) {
|
||||
return $container;
|
||||
}
|
||||
if (create) {
|
||||
$container = createContainer(options);
|
||||
}
|
||||
return $container;
|
||||
}
|
||||
|
||||
function info(message, title, optionsOverride) {
|
||||
return notify({
|
||||
type: toastType.info,
|
||||
iconClass: getOptions().iconClasses.info,
|
||||
message: message,
|
||||
optionsOverride: optionsOverride,
|
||||
title: title
|
||||
});
|
||||
}
|
||||
|
||||
function subscribe(callback) {
|
||||
listener = callback;
|
||||
}
|
||||
|
||||
function success(message, title, optionsOverride) {
|
||||
return notify({
|
||||
type: toastType.success,
|
||||
iconClass: getOptions().iconClasses.success,
|
||||
message: message,
|
||||
optionsOverride: optionsOverride,
|
||||
title: title
|
||||
});
|
||||
}
|
||||
|
||||
function warning(message, title, optionsOverride) {
|
||||
return notify({
|
||||
type: toastType.warning,
|
||||
iconClass: getOptions().iconClasses.warning,
|
||||
message: message,
|
||||
optionsOverride: optionsOverride,
|
||||
title: title
|
||||
});
|
||||
}
|
||||
|
||||
function clear($toastElement, clearOptions) {
|
||||
var options = getOptions();
|
||||
if (!$container) { getContainer(options); }
|
||||
if (!clearToast($toastElement, options, clearOptions)) {
|
||||
clearContainer(options);
|
||||
}
|
||||
}
|
||||
|
||||
function remove($toastElement) {
|
||||
var options = getOptions();
|
||||
if (!$container) { getContainer(options); }
|
||||
if ($toastElement && $(':focus', $toastElement).length === 0) {
|
||||
removeToast($toastElement);
|
||||
return;
|
||||
}
|
||||
if ($container.children().length) {
|
||||
$container.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// internal functions
|
||||
|
||||
function clearContainer (options) {
|
||||
var toastsToClear = $container.children();
|
||||
for (var i = toastsToClear.length - 1; i >= 0; i--) {
|
||||
clearToast($(toastsToClear[i]), options);
|
||||
}
|
||||
}
|
||||
|
||||
function clearToast ($toastElement, options, clearOptions) {
|
||||
var force = clearOptions && clearOptions.force ? clearOptions.force : false;
|
||||
if ($toastElement && (force || $(':focus', $toastElement).length === 0)) {
|
||||
$toastElement[options.hideMethod]({
|
||||
duration: options.hideDuration,
|
||||
easing: options.hideEasing,
|
||||
complete: function () { removeToast($toastElement); }
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function createContainer(options) {
|
||||
$container = $('<div/>')
|
||||
.attr('id', options.containerId)
|
||||
.addClass(options.positionClass);
|
||||
|
||||
$container.appendTo($(options.target));
|
||||
return $container;
|
||||
}
|
||||
|
||||
function getDefaults() {
|
||||
return {
|
||||
tapToDismiss: true,
|
||||
toastClass: 'toast',
|
||||
containerId: 'toast-container',
|
||||
debug: false,
|
||||
|
||||
showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery
|
||||
showDuration: 300,
|
||||
showEasing: 'swing', //swing and linear are built into jQuery
|
||||
onShown: undefined,
|
||||
hideMethod: 'fadeOut',
|
||||
hideDuration: 1000,
|
||||
hideEasing: 'swing',
|
||||
onHidden: undefined,
|
||||
closeMethod: false,
|
||||
closeDuration: false,
|
||||
closeEasing: false,
|
||||
closeOnHover: true,
|
||||
|
||||
extendedTimeOut: 1000,
|
||||
iconClasses: {
|
||||
error: 'toast-error',
|
||||
info: 'toast-info',
|
||||
success: 'toast-success',
|
||||
warning: 'toast-warning'
|
||||
},
|
||||
iconClass: 'toast-info',
|
||||
positionClass: 'toast-top-right',
|
||||
timeOut: 5000, // Set timeOut and extendedTimeOut to 0 to make it sticky
|
||||
titleClass: 'toast-title',
|
||||
messageClass: 'toast-message',
|
||||
escapeHtml: false,
|
||||
target: 'body',
|
||||
closeHtml: '<button type="button">×</button>',
|
||||
closeClass: 'toast-close-button',
|
||||
newestOnTop: true,
|
||||
preventDuplicates: false,
|
||||
progressBar: false,
|
||||
progressClass: 'toast-progress',
|
||||
rtl: false
|
||||
};
|
||||
}
|
||||
|
||||
function publish(args) {
|
||||
if (!listener) { return; }
|
||||
listener(args);
|
||||
}
|
||||
|
||||
function notify(map) {
|
||||
var options = getOptions();
|
||||
var iconClass = map.iconClass || options.iconClass;
|
||||
|
||||
if (typeof (map.optionsOverride) !== 'undefined') {
|
||||
options = $.extend(options, map.optionsOverride);
|
||||
iconClass = map.optionsOverride.iconClass || iconClass;
|
||||
}
|
||||
|
||||
if (shouldExit(options, map)) { return; }
|
||||
|
||||
toastId++;
|
||||
|
||||
$container = getContainer(options, true);
|
||||
|
||||
var intervalId = null;
|
||||
var $toastElement = $('<div/>');
|
||||
var $titleElement = $('<div/>');
|
||||
var $messageElement = $('<div/>');
|
||||
var $progressElement = $('<div/>');
|
||||
var $closeElement = $(options.closeHtml);
|
||||
var progressBar = {
|
||||
intervalId: null,
|
||||
hideEta: null,
|
||||
maxHideTime: null
|
||||
};
|
||||
var response = {
|
||||
toastId: toastId,
|
||||
state: 'visible',
|
||||
startTime: new Date(),
|
||||
options: options,
|
||||
map: map
|
||||
};
|
||||
|
||||
personalizeToast();
|
||||
|
||||
displayToast();
|
||||
|
||||
handleEvents();
|
||||
|
||||
publish(response);
|
||||
|
||||
if (options.debug && console) {
|
||||
console.log(response);
|
||||
}
|
||||
|
||||
return $toastElement;
|
||||
|
||||
function escapeHtml(source) {
|
||||
if (source == null) {
|
||||
source = '';
|
||||
}
|
||||
|
||||
return source
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
function personalizeToast() {
|
||||
setIcon();
|
||||
setTitle();
|
||||
setMessage();
|
||||
setCloseButton();
|
||||
setProgressBar();
|
||||
setRTL();
|
||||
setSequence();
|
||||
setAria();
|
||||
}
|
||||
|
||||
function setAria() {
|
||||
var ariaValue = '';
|
||||
switch (map.iconClass) {
|
||||
case 'toast-success':
|
||||
case 'toast-info':
|
||||
ariaValue = 'polite';
|
||||
break;
|
||||
default:
|
||||
ariaValue = 'assertive';
|
||||
}
|
||||
$toastElement.attr('aria-live', ariaValue);
|
||||
}
|
||||
|
||||
function handleEvents() {
|
||||
if (options.closeOnHover) {
|
||||
$toastElement.hover(stickAround, delayedHideToast);
|
||||
}
|
||||
|
||||
if (!options.onclick && options.tapToDismiss) {
|
||||
$toastElement.click(hideToast);
|
||||
}
|
||||
|
||||
if (options.closeButton && $closeElement) {
|
||||
$closeElement.click(function (event) {
|
||||
if (event.stopPropagation) {
|
||||
event.stopPropagation();
|
||||
} else if (event.cancelBubble !== undefined && event.cancelBubble !== true) {
|
||||
event.cancelBubble = true;
|
||||
}
|
||||
|
||||
if (options.onCloseClick) {
|
||||
options.onCloseClick(event);
|
||||
}
|
||||
|
||||
hideToast(true);
|
||||
});
|
||||
}
|
||||
|
||||
if (options.onclick) {
|
||||
$toastElement.click(function (event) {
|
||||
options.onclick(event);
|
||||
hideToast();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function displayToast() {
|
||||
$toastElement.hide();
|
||||
|
||||
$toastElement[options.showMethod](
|
||||
{duration: options.showDuration, easing: options.showEasing, complete: options.onShown}
|
||||
);
|
||||
|
||||
if (options.timeOut > 0) {
|
||||
intervalId = setTimeout(hideToast, options.timeOut);
|
||||
progressBar.maxHideTime = parseFloat(options.timeOut);
|
||||
progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime;
|
||||
if (options.progressBar) {
|
||||
progressBar.intervalId = setInterval(updateProgress, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setIcon() {
|
||||
if (map.iconClass) {
|
||||
$toastElement.addClass(options.toastClass).addClass(iconClass);
|
||||
}
|
||||
}
|
||||
|
||||
function setSequence() {
|
||||
if (options.newestOnTop) {
|
||||
$container.prepend($toastElement);
|
||||
} else {
|
||||
$container.append($toastElement);
|
||||
}
|
||||
}
|
||||
|
||||
function setTitle() {
|
||||
if (map.title) {
|
||||
var suffix = map.title;
|
||||
if (options.escapeHtml) {
|
||||
suffix = escapeHtml(map.title);
|
||||
}
|
||||
$titleElement.append(suffix).addClass(options.titleClass);
|
||||
$toastElement.append($titleElement);
|
||||
}
|
||||
}
|
||||
|
||||
function setMessage() {
|
||||
if (map.message) {
|
||||
var suffix = map.message;
|
||||
if (options.escapeHtml) {
|
||||
suffix = escapeHtml(map.message);
|
||||
}
|
||||
$messageElement.append(suffix).addClass(options.messageClass);
|
||||
$toastElement.append($messageElement);
|
||||
}
|
||||
}
|
||||
|
||||
function setCloseButton() {
|
||||
if (options.closeButton) {
|
||||
$closeElement.addClass(options.closeClass).attr('role', 'button');
|
||||
$toastElement.prepend($closeElement);
|
||||
}
|
||||
}
|
||||
|
||||
function setProgressBar() {
|
||||
if (options.progressBar) {
|
||||
$progressElement.addClass(options.progressClass);
|
||||
$toastElement.prepend($progressElement);
|
||||
}
|
||||
}
|
||||
|
||||
function setRTL() {
|
||||
if (options.rtl) {
|
||||
$toastElement.addClass('rtl');
|
||||
}
|
||||
}
|
||||
|
||||
function shouldExit(options, map) {
|
||||
if (options.preventDuplicates) {
|
||||
if (map.message === previousToast) {
|
||||
return true;
|
||||
} else {
|
||||
previousToast = map.message;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function hideToast(override) {
|
||||
var method = override && options.closeMethod !== false ? options.closeMethod : options.hideMethod;
|
||||
var duration = override && options.closeDuration !== false ?
|
||||
options.closeDuration : options.hideDuration;
|
||||
var easing = override && options.closeEasing !== false ? options.closeEasing : options.hideEasing;
|
||||
// if ($(':focus', $toastElement).length && !override) {
|
||||
// return;
|
||||
// }
|
||||
clearTimeout(progressBar.intervalId);
|
||||
return $toastElement[method]({
|
||||
duration: duration,
|
||||
easing: easing,
|
||||
complete: function () {
|
||||
removeToast($toastElement);
|
||||
clearTimeout(intervalId);
|
||||
if (options.onHidden && response.state !== 'hidden') {
|
||||
options.onHidden();
|
||||
}
|
||||
response.state = 'hidden';
|
||||
response.endTime = new Date();
|
||||
publish(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function delayedHideToast() {
|
||||
if (options.timeOut > 0 || options.extendedTimeOut > 0) {
|
||||
intervalId = setTimeout(hideToast, options.extendedTimeOut);
|
||||
progressBar.maxHideTime = parseFloat(options.extendedTimeOut);
|
||||
progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime;
|
||||
}
|
||||
}
|
||||
|
||||
function stickAround() {
|
||||
clearTimeout(intervalId);
|
||||
progressBar.hideEta = 0;
|
||||
$toastElement.stop(true, true)[options.showMethod](
|
||||
{duration: options.showDuration, easing: options.showEasing}
|
||||
);
|
||||
}
|
||||
|
||||
function updateProgress() {
|
||||
var percentage = ((progressBar.hideEta - (new Date().getTime())) / progressBar.maxHideTime) * 100;
|
||||
$progressElement.width(percentage + '%');
|
||||
}
|
||||
}
|
||||
|
||||
function getOptions() {
|
||||
return $.extend({}, getDefaults(), toastr.options);
|
||||
}
|
||||
|
||||
function removeToast($toastElement) {
|
||||
if (!$container) { $container = getContainer(); }
|
||||
if ($toastElement.is(':visible')) {
|
||||
return;
|
||||
}
|
||||
$toastElement.remove();
|
||||
$toastElement = null;
|
||||
if ($container.children().length === 0) {
|
||||
$container.remove();
|
||||
previousToast = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
});
|
||||
}(typeof define === 'function' && define.amd ? define : function (deps, factory) {
|
||||
if (typeof module !== 'undefined' && module.exports) { //Node
|
||||
module.exports = factory(require('jquery'));
|
||||
} else {
|
||||
window.toastr = factory(window.jQuery);
|
||||
}
|
||||
}));
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
'''
|
||||
API for Eye Mask - Stable Diffusion Web UI extension for mark and redraw eyes/faces.
|
||||
Core logic is in eyemask.api.
|
||||
|
||||
Author: ilian.iliev
|
||||
Since: 09.01.2023
|
||||
'''
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from modules import scripts
|
||||
sys.path.append(os.path.join(scripts.basedir(), 'scripts'))
|
||||
|
||||
import modules.script_callbacks as script_callbacks
|
||||
from eyemask.api import EyeMaskApi
|
||||
|
||||
try:
|
||||
api = EyeMaskApi()
|
||||
script_callbacks.on_app_started(api.start)
|
||||
except:
|
||||
pass
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
'''
|
||||
Eye Mask - Stable Diffusion Web UI extension for mark and redraw eyes/faces.
|
||||
Core logic for 'run' method is in eyemask.script.
|
||||
|
||||
Author: ilian.iliev
|
||||
Since: 09.01.2023
|
||||
'''
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from modules import scripts
|
||||
sys.path.append(os.path.join(scripts.basedir(), 'scripts'))
|
||||
|
||||
from eyemask import constants, ui, script as eye_mask_script
|
||||
|
||||
|
||||
class EyeMaskScript(scripts.Script):
|
||||
|
||||
def __init__(self, *k, **kw):
|
||||
self.eye_mask_core = eye_mask_script.EyeMasksCore()
|
||||
self.eye_mask_ui = ui.EyeMaskUI(self)
|
||||
super().__init__()
|
||||
|
||||
def title(self):
|
||||
return constants.script_name
|
||||
|
||||
def show(self, is_img2img):
|
||||
return True
|
||||
|
||||
def ui(self, is_img2img):
|
||||
return self.eye_mask_ui.render(is_img2img)
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
return self.eye_mask_core.execute(*args, **kwargs)
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
'''
|
||||
Eye Mask - Stable Diffusion Web UI extension (embedded version) for mark and redraw eyes/faces.
|
||||
Core logic is in eyemask.script_embedded.
|
||||
|
||||
Author: ilian.iliev
|
||||
Since: 09.01.2023
|
||||
'''
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from modules import scripts
|
||||
sys.path.append(os.path.join(scripts.basedir(), 'scripts'))
|
||||
|
||||
from eyemask import constants, ui, script_embedded as eye_mask_script
|
||||
import modules.shared as shared
|
||||
|
||||
|
||||
class EyeMaskEmbeddedScript(scripts.Script):
|
||||
|
||||
def __init__(self, *k, **kw):
|
||||
self.eye_mask_core = eye_mask_script.EyeMasksEmbeddedCore()
|
||||
self.eye_mask_ui = ui.EyeMaskUI(self)
|
||||
super().__init__()
|
||||
|
||||
def title(self):
|
||||
return constants.script_name
|
||||
|
||||
def show(self, is_img2img):
|
||||
try:
|
||||
return scripts.AlwaysVisible if shared.opts.em_show_embedded_version else False
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
def ui(self, is_img2img):
|
||||
return self.eye_mask_ui.render(is_img2img)
|
||||
|
||||
def process(self, p, *args):
|
||||
return self.eye_mask_core.execute_process(p, *args)
|
||||
|
||||
def postprocess(self, p, processed, *args):
|
||||
return self.eye_mask_core.execute_postprocess(p, processed, *args)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import modules.shared as shared
|
||||
from modules import scripts
|
||||
|
||||
def on_ui_settings():
|
||||
section = ('eyemask', 'Eye Mask')
|
||||
options = [
|
||||
('em_show_embedded_version', False, 'Show embedded version'),
|
||||
('em_save_masks', False, 'Save masks'),
|
||||
('em_outdir_masks', 'extensions/eyemask/outputs/masks', 'Output directory for masks'),
|
||||
('em_wildcards_in_original', True, 'Replace wildcards in original prompt'),
|
||||
('em_save_prompts', False, 'Save last prompt'),
|
||||
('em_save_neg_prompts', False, 'Save last negative prompt'),
|
||||
('em_save_em_prompts', False, 'Save last mask prompt'),
|
||||
('em_save_em_neg_prompts', False, 'Save last mask negative prompt'),
|
||||
('em_save_last_script', False, 'Save last script'),
|
||||
('em_save_settings', False, 'Save all settings'),
|
||||
('em_dev_mode', False, 'Dev mode'),
|
||||
]
|
||||
for opt in options:
|
||||
shared.opts.add_option(opt[0], shared.OptionInfo(opt[1], opt[2], section=section))
|
||||
|
||||
scripts.script_callbacks.on_ui_settings(on_ui_settings)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
from os.path import dirname, basename, isfile, join
|
||||
import glob
|
||||
modules = glob.glob(join(dirname(__file__), "*.py"))
|
||||
__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import models
|
||||
import utils
|
||||
from . api import EyeMaskApi
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import os
|
||||
|
||||
import gradio as gr
|
||||
from fastapi import FastAPI, Body, HTTPException, Request, Response
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from .. constants import script_static_dir
|
||||
from .. import script as eye_mask_script
|
||||
from . utils import encode_pil_to_base64, decode_base64_to_image
|
||||
from . models import *
|
||||
|
||||
|
||||
class EyeMaskApi():
|
||||
|
||||
def __init__(self):
|
||||
self.core = eye_mask_script.EyeMasksCore()
|
||||
|
||||
BASE_PATH = '/sdapi/v1/eyemask'
|
||||
VERSION = 1
|
||||
|
||||
def get_path(self, path):
|
||||
return f"{self.BASE_PATH}/v{self.VERSION}{path}"
|
||||
|
||||
def add_api_route(self, path: str, endpoint, **kwargs):
|
||||
# authenticated requests can be implemented here
|
||||
return self.app.add_api_route(self.get_path(path), endpoint, **kwargs)
|
||||
|
||||
def start(self, _: gr.Blocks, app: FastAPI):
|
||||
|
||||
self.app = app
|
||||
|
||||
self.add_api_route('/mask_list', self.mask_list, methods=['GET'])
|
||||
self.add_api_route('/generate_mask', self.generate_mask, methods=['POST'], response_model=SingleImageResponse)
|
||||
self.add_api_route('/static/{path:path}', self.static, methods=['GET'])
|
||||
self.add_api_route('/config.json', self.get_config, methods=['GET'])
|
||||
|
||||
def mask_list(self):
|
||||
''' Get masks list '''
|
||||
return { 'mask_list': list(eye_mask_script.EyeMasksCore.MASK_TYPES) }
|
||||
|
||||
def generate_mask(self, req: GenerateMaskRequest):
|
||||
''' Generate mask by type '''
|
||||
image = decode_base64_to_image(req.image)
|
||||
mask, mask_success = self.core.generate_mask(image, int(req.mask_type))
|
||||
return SingleImageResponse(image=encode_pil_to_base64(mask))
|
||||
|
||||
def static(self, path: str):
|
||||
''' Serve static files '''
|
||||
static_file = os.path.join(script_static_dir, path)
|
||||
if static_file is not None:
|
||||
return FileResponse(static_file)
|
||||
raise HTTPException(status_code=404, detail='Static file not found')
|
||||
|
||||
def get_config(self):
|
||||
return FileResponse('config.json')
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class SingleImageRequest(BaseModel):
|
||||
image: str = Field(default="", title="Image", description="Image to work on, must be a Base64 string containing the image's data.")
|
||||
|
||||
class SingleImageResponse(BaseModel):
|
||||
image: str = Field(default="", title="Image", description="Generated image, a Base64 string containing the image's data.")
|
||||
|
||||
class GenerateMaskRequest(SingleImageRequest):
|
||||
mask_type: int = Field(default="", title="Mask Type", description="Mask type to work with")
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import base64
|
||||
import io
|
||||
from io import BytesIO
|
||||
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, Response
|
||||
from modules.api.models import *
|
||||
from PIL import PngImagePlugin,Image
|
||||
import piexif
|
||||
import piexif.helper
|
||||
|
||||
|
||||
def decode_base64_to_image(encoding):
|
||||
if encoding.startswith("data:image/"):
|
||||
encoding = encoding.split(";")[1].split(",")[1]
|
||||
try:
|
||||
image = Image.open(BytesIO(base64.b64decode(encoding)))
|
||||
return image
|
||||
except Exception as err:
|
||||
raise HTTPException(status_code=500, detail="Invalid encoded image")
|
||||
|
||||
def encode_pil_to_base64(image):
|
||||
with io.BytesIO() as output_bytes:
|
||||
|
||||
if opts.samples_format.lower() == 'png':
|
||||
use_metadata = False
|
||||
metadata = PngImagePlugin.PngInfo()
|
||||
for key, value in image.info.items():
|
||||
if isinstance(key, str) and isinstance(value, str):
|
||||
metadata.add_text(key, value)
|
||||
use_metadata = True
|
||||
image.save(output_bytes, format="PNG", pnginfo=(metadata if use_metadata else None), quality=opts.jpeg_quality)
|
||||
|
||||
elif opts.samples_format.lower() in ("jpg", "jpeg", "webp"):
|
||||
parameters = image.info.get('parameters', None)
|
||||
exif_bytes = piexif.dump({
|
||||
"Exif": { piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(parameters or "", encoding="unicode") }
|
||||
})
|
||||
if opts.samples_format.lower() in ("jpg", "jpeg"):
|
||||
image.save(output_bytes, format="JPEG", exif = exif_bytes, quality=opts.jpeg_quality)
|
||||
else:
|
||||
image.save(output_bytes, format="WEBP", exif = exif_bytes, quality=opts.jpeg_quality)
|
||||
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Invalid image format")
|
||||
|
||||
bytes_data = output_bytes.getvalue()
|
||||
|
||||
return base64.b64encode(bytes_data)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import os
|
||||
from modules import scripts
|
||||
|
||||
script_name = 'Eye Mask'
|
||||
|
||||
script_base_dir = scripts.basedir()
|
||||
script_static_dir = os.path.join(script_base_dir, 'static')
|
||||
script_models_dir = os.path.join(script_base_dir, 'models')
|
||||
script_wildcards_dir = os.path.join(script_base_dir, 'wildcards')
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
import os
|
||||
import dlib
|
||||
import cv2
|
||||
import copy
|
||||
import numpy as np
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
from .constants import script_models_dir
|
||||
from .utils import expand_polygon, calculate_distance
|
||||
from .modules import depthmap, mmdetdd
|
||||
|
||||
depthmap_generator = depthmap.SimpleDepthMapGenerator()
|
||||
|
||||
try:
|
||||
landmark_detector = dlib.shape_predictor(
|
||||
os.path.join(script_models_dir, 'shape_predictor_68_face_landmarks.dat')
|
||||
)
|
||||
except Exception as e:
|
||||
# when reloading the module, landmark_detector is already loaded
|
||||
# and the file cant be opened again
|
||||
pass
|
||||
|
||||
def _get_image_mask(image):
|
||||
width, height = image.size
|
||||
return np.full((height, width, 3), 0, dtype=np.uint8)
|
||||
|
||||
def _get_detected_faces_dlib(image):
|
||||
|
||||
# Load the pre-trained model for detecting facial landmarks
|
||||
face_detector = dlib.get_frontal_face_detector()
|
||||
|
||||
# Convert the image to grayscale
|
||||
frame = np.array(image)
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# Detect faces in the image
|
||||
return face_detector(gray, 1), gray
|
||||
|
||||
def _calculate_mask_padding(p1, p2, percents):
|
||||
distance = calculate_distance((p1.x, p1.y), (p2.x, p2.y))
|
||||
value = int(distance * (percents / 100))
|
||||
return value
|
||||
|
||||
def get_eyes_mask_dlib(init_image, padding=20, in_pixels=False):
|
||||
|
||||
mask = _get_image_mask(init_image)
|
||||
faces, gray = _get_detected_faces_dlib(init_image)
|
||||
padding_original = padding
|
||||
|
||||
if not len(faces):
|
||||
return Image.fromarray(mask), False
|
||||
|
||||
for face in faces:
|
||||
# Use the landmark detector to find facial landmarks
|
||||
landmarks = landmark_detector(gray, face)
|
||||
# Extract the coordinates of the left and right eyes
|
||||
left_eye_x = [landmarks.part(i).x for i in range(36, 42)]
|
||||
left_eye_y = [landmarks.part(i).y for i in range(36, 42)]
|
||||
right_eye_x = [landmarks.part(i).x for i in range(42, 48)]
|
||||
right_eye_y = [landmarks.part(i).y for i in range(42, 48)]
|
||||
# Calculate mask padding
|
||||
if not in_pixels:
|
||||
padding = _calculate_mask_padding(landmarks.part(36), landmarks.part(39), padding_original)
|
||||
# Draw a filled white polygon around the left eye
|
||||
left_eye_points = []
|
||||
for i in range(len(left_eye_x)):
|
||||
left_eye_points.append([left_eye_x[i], left_eye_y[i]])
|
||||
left_eye_points = expand_polygon(left_eye_points, padding)
|
||||
left_eye_points = np.array(left_eye_points, np.int32)
|
||||
left_eye_points = left_eye_points.reshape((-1, 1, 2))
|
||||
cv2.fillPoly(mask, [left_eye_points], (255, 255, 255))
|
||||
# Calculate mask padding
|
||||
if not in_pixels:
|
||||
padding = _calculate_mask_padding(landmarks.part(42), landmarks.part(45), padding_original)
|
||||
# Draw a filled white polygon around the right eye
|
||||
right_eye_points = []
|
||||
for i in range(len(right_eye_x)):
|
||||
right_eye_points.append([right_eye_x[i], right_eye_y[i]])
|
||||
right_eye_points = expand_polygon(right_eye_points, padding)
|
||||
right_eye_points = np.array(right_eye_points, np.int32)
|
||||
right_eye_points = right_eye_points.reshape((-1, 1, 2))
|
||||
cv2.fillPoly(mask, [right_eye_points], (255, 255, 255))
|
||||
|
||||
return Image.fromarray(mask), True
|
||||
|
||||
def get_face_mask_dlib(init_image, padding=20, in_pixels=False):
|
||||
|
||||
mask = _get_image_mask(init_image)
|
||||
faces, gray = _get_detected_faces_dlib(init_image)
|
||||
padding_original = padding
|
||||
|
||||
if not len(faces):
|
||||
return Image.fromarray(mask), False
|
||||
|
||||
for face in faces:
|
||||
# Use the landmark detector to find facial landmarks
|
||||
landmarks = landmark_detector(gray, face)
|
||||
face_x = [landmarks.part(i).x for i in range(0, 17)] + [landmarks.part(i).x for i in reversed(range(17, 27))]
|
||||
face_y = [landmarks.part(i).y for i in range(0, 17)] + [landmarks.part(i).y for i in reversed(range(17, 27))]
|
||||
# Calculate mask padding
|
||||
if not in_pixels:
|
||||
padding = _calculate_mask_padding(landmarks.part(0), landmarks.part(16), padding_original)
|
||||
# Draw a filled white polygon around the face
|
||||
face_points = []
|
||||
for i in range(len(face_x)):
|
||||
face_points.append([face_x[i], face_y[i]])
|
||||
face_points = expand_polygon(face_points, padding)
|
||||
face_points = np.array(face_points, np.int32)
|
||||
face_points = face_points.reshape((-1, 1, 2))
|
||||
cv2.fillPoly(mask, [face_points], (255, 255, 255))
|
||||
|
||||
return Image.fromarray(mask), True
|
||||
|
||||
def get_face_mask_depth(init_image):
|
||||
|
||||
mask = _get_image_mask(init_image)
|
||||
faces, gray = _get_detected_faces_dlib(init_image)
|
||||
|
||||
if len(faces) != 1:
|
||||
return Image.fromarray(mask), False
|
||||
|
||||
body_mask, body_mask_success = get_body_mask_depth(init_image)
|
||||
|
||||
if not body_mask_success:
|
||||
return Image.fromarray(mask), False
|
||||
|
||||
face = faces[0]
|
||||
landmarks = landmark_detector(gray, face)
|
||||
lowest_point = None
|
||||
|
||||
for i in range(0, 17):
|
||||
point = landmarks.part(i)
|
||||
if lowest_point is None or point.y > lowest_point.y:
|
||||
lowest_point = point
|
||||
|
||||
width, height = body_mask.size
|
||||
|
||||
for x in range(0, width):
|
||||
for y in range(lowest_point.y, height):
|
||||
body_mask.putpixel((x, y), (0, 0, 0))
|
||||
|
||||
return body_mask, True
|
||||
|
||||
def get_body_mask_depth(init_image):
|
||||
|
||||
def remap_range(value, minIn, MaxIn, minOut, maxOut):
|
||||
if value > MaxIn: value = MaxIn;
|
||||
if value < minIn: value = minIn;
|
||||
finalValue = ((value - minIn) / (MaxIn - minIn)) * (maxOut - minOut) + minOut
|
||||
return finalValue
|
||||
|
||||
def create_depth_mask_from_depth_map(img, treshold, clean_cut):
|
||||
img = copy.deepcopy(img.convert("RGBA"))
|
||||
mask_img = copy.deepcopy(img.convert("L"))
|
||||
mask_datas = mask_img.getdata()
|
||||
datas = img.getdata()
|
||||
newData = []
|
||||
maxD = max(mask_datas)
|
||||
if clean_cut and treshold == 0:
|
||||
treshold = 128
|
||||
for i in range(len(mask_datas)):
|
||||
if clean_cut and mask_datas[i] > treshold:
|
||||
newrgb = 255
|
||||
elif mask_datas[i] > treshold and not clean_cut:
|
||||
newrgb = int(remap_range(mask_datas[i],treshold,255,0,255))
|
||||
else:
|
||||
newrgb = 0
|
||||
newData.append((newrgb,newrgb,newrgb,255))
|
||||
img.putdata(newData)
|
||||
return img
|
||||
|
||||
try:
|
||||
d_m = depthmap_generator.calculate_depth_maps(init_image, init_image.width, init_image.height, 1, False)
|
||||
d_m = create_depth_mask_from_depth_map(d_m, 128, True)
|
||||
return d_m, True
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return _get_image_mask(init_image), False
|
||||
|
||||
def get_face_mask_mmdet(init_image):
|
||||
|
||||
mask = _get_image_mask(init_image)
|
||||
faces, gray = _get_detected_faces_dlib(init_image)
|
||||
|
||||
if len(faces) != 1:
|
||||
return Image.fromarray(mask), False
|
||||
|
||||
body_mask, body_mask_success = get_body_mask_mmdet(init_image)
|
||||
|
||||
if not body_mask_success:
|
||||
return Image.fromarray(mask), False
|
||||
|
||||
face = faces[0]
|
||||
landmarks = landmark_detector(gray, face)
|
||||
lowest_point = None
|
||||
|
||||
for i in range(0, 17):
|
||||
point = landmarks.part(i)
|
||||
if lowest_point is None or point.y > lowest_point.y:
|
||||
lowest_point = point
|
||||
|
||||
width, height = body_mask.size
|
||||
|
||||
for x in range(0, width):
|
||||
for y in range(lowest_point.y, height):
|
||||
body_mask.putpixel((x, y), 0)
|
||||
|
||||
return body_mask, True
|
||||
|
||||
def get_body_mask_mmdet(init_image):
|
||||
try:
|
||||
mask = mmdetdd.get_person_mask(init_image)
|
||||
return mask, True
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return _get_image_mask(init_image), False
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
from os.path import dirname, basename, isfile, join
|
||||
import glob
|
||||
modules = glob.glob(join(dirname(__file__), "*.py"))
|
||||
__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
import torch, gc
|
||||
import cv2
|
||||
import requests
|
||||
import os.path
|
||||
import contextlib
|
||||
from PIL import Image
|
||||
from modules.shared import opts, cmd_opts
|
||||
from modules import processing, images, shared, devices
|
||||
|
||||
from torchvision.transforms import Compose
|
||||
from repositories.midas.midas.dpt_depth import DPTDepthModel
|
||||
from repositories.midas.midas.midas_net import MidasNet
|
||||
from repositories.midas.midas.midas_net_custom import MidasNet_small
|
||||
from repositories.midas.midas.transforms import Resize, NormalizeImage, PrepareForNet
|
||||
|
||||
import numpy as np
|
||||
|
||||
class SimpleDepthMapGenerator(object):
|
||||
def calculate_depth_maps(self,image,img_x,img_y,model_type,invert_depth):
|
||||
try:
|
||||
def download_file(filename, url):
|
||||
# print("Downloading midas model weights to %s" % filename)
|
||||
with open(filename, 'wb') as fout:
|
||||
response = requests.get(url, stream=True)
|
||||
response.raise_for_status()
|
||||
# Write response data to file
|
||||
for block in response.iter_content(4096):
|
||||
fout.write(block)
|
||||
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
# model path and name
|
||||
model_dir = "./models/midas"
|
||||
# create path to model if not present
|
||||
os.makedirs(model_dir, exist_ok=True)
|
||||
# print("Loading midas model weights ..")
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
|
||||
#"dpt_large"
|
||||
if model_type == 0:
|
||||
model_path = f"{model_dir}/dpt_large-midas-2f21e586.pt"
|
||||
# print(model_path)
|
||||
if not os.path.exists(model_path):
|
||||
download_file(model_path,"https://github.com/intel-isl/DPT/releases/download/1_0/dpt_large-midas-2f21e586.pt")
|
||||
model = DPTDepthModel(
|
||||
path=model_path,
|
||||
backbone="vitl16_384",
|
||||
non_negative=True,
|
||||
)
|
||||
net_w, net_h = 384, 384
|
||||
resize_mode = "minimal"
|
||||
normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
|
||||
|
||||
#"midas_v21"
|
||||
elif model_type == 1:
|
||||
model_path = f"{model_dir}/midas_v21-f6b98070.pt"
|
||||
# print(model_path)
|
||||
if not os.path.exists(model_path):
|
||||
download_file(model_path,"https://github.com/AlexeyAB/MiDaS/releases/download/midas_dpt/midas_v21-f6b98070.pt")
|
||||
model = MidasNet(model_path, non_negative=True)
|
||||
net_w, net_h = 384, 384
|
||||
resize_mode="upper_bound"
|
||||
normalization = NormalizeImage(
|
||||
mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
|
||||
)
|
||||
|
||||
#"midas_v21_small"
|
||||
elif model_type == 2:
|
||||
model_path = f"{model_dir}/midas_v21_small-70d6b9c8.pt"
|
||||
# print(model_path)
|
||||
if not os.path.exists(model_path):
|
||||
download_file(model_path,"https://github.com/AlexeyAB/MiDaS/releases/download/midas_dpt/midas_v21_small-70d6b9c8.pt")
|
||||
model = MidasNet_small(model_path, features=64, backbone="efficientnet_lite3", exportable=True, non_negative=True, blocks={'expand': True})
|
||||
net_w, net_h = 256, 256
|
||||
resize_mode="upper_bound"
|
||||
normalization = NormalizeImage(
|
||||
mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
|
||||
)
|
||||
|
||||
# init transform
|
||||
transform = Compose(
|
||||
[
|
||||
Resize(
|
||||
img_x,
|
||||
img_y,
|
||||
resize_target=None,
|
||||
keep_aspect_ratio=True,
|
||||
ensure_multiple_of=32,
|
||||
resize_method=resize_mode,
|
||||
image_interpolation_method=cv2.INTER_CUBIC,
|
||||
),
|
||||
normalization,
|
||||
PrepareForNet(),
|
||||
]
|
||||
)
|
||||
model.eval()
|
||||
# optimize
|
||||
if device == torch.device("cuda"):
|
||||
model = model.to(memory_format=torch.channels_last)
|
||||
if not cmd_opts.no_half:
|
||||
model = model.half()
|
||||
model.to(device)
|
||||
|
||||
img = cv2.cvtColor(np.asarray(image), cv2.COLOR_BGR2RGB) / 255.0
|
||||
img_input = transform({"image": img})["image"]
|
||||
precision_scope = torch.autocast if shared.cmd_opts.precision == "autocast" and device == torch.device("cuda") else contextlib.nullcontext
|
||||
# compute
|
||||
with torch.no_grad(), precision_scope("cuda"):
|
||||
sample = torch.from_numpy(img_input).to(device).unsqueeze(0)
|
||||
if device == torch.device("cuda"):
|
||||
sample = sample.to(memory_format=torch.channels_last)
|
||||
if not cmd_opts.no_half:
|
||||
sample = sample.half()
|
||||
prediction = model.forward(sample)
|
||||
prediction = (
|
||||
torch.nn.functional.interpolate(
|
||||
prediction.unsqueeze(1),
|
||||
size=img.shape[:2],
|
||||
mode="bicubic",
|
||||
align_corners=False,
|
||||
)
|
||||
.squeeze()
|
||||
.cpu()
|
||||
.numpy()
|
||||
)
|
||||
# output
|
||||
depth = prediction
|
||||
numbytes=2
|
||||
depth_min = depth.min()
|
||||
depth_max = depth.max()
|
||||
max_val = (2**(8*numbytes))-1
|
||||
|
||||
# check output before normalizing and mapping to 16 bit
|
||||
if depth_max - depth_min > np.finfo("float").eps:
|
||||
out = max_val * (depth - depth_min) / (depth_max - depth_min)
|
||||
else:
|
||||
out = np.zeros(depth.shape)
|
||||
# single channel, 16 bit image
|
||||
img_output = out.astype("uint16")
|
||||
|
||||
# # invert depth map
|
||||
if invert_depth:
|
||||
img_output = cv2.bitwise_not(img_output)
|
||||
|
||||
# three channel, 8 bits per channel image
|
||||
img_output2 = np.zeros_like(image)
|
||||
img_output2[:,:,0] = img_output / 256.0
|
||||
img_output2[:,:,1] = img_output / 256.0
|
||||
img_output2[:,:,2] = img_output / 256.0
|
||||
img = Image.fromarray(img_output2)
|
||||
return img
|
||||
except Exception:
|
||||
raise
|
||||
finally:
|
||||
del model
|
||||
gc.collect()
|
||||
devices.torch_gc()
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
import os
|
||||
import cv2
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
|
||||
from modules import shared, modelloader
|
||||
from modules.sd_models import model_hash
|
||||
from modules.paths import models_path
|
||||
|
||||
dd_models_path = os.path.join(models_path, "mmdet")
|
||||
|
||||
|
||||
def get_person_mask(image):
|
||||
results_a = inference_mmdet_segm(image, 'segm\mmdet_dd-person_mask2former.pth [1c8dbe8d]', 30/100.0, 'A')
|
||||
masks_a = create_segmasks(results_a)
|
||||
masks_a = dilate_masks(masks_a, 4, 1)
|
||||
masks_a = offset_masks(masks_a, 0, 0)
|
||||
return masks_a[0]
|
||||
|
||||
def list_models(model_path):
|
||||
model_list = modelloader.load_models(model_path=model_path, ext_filter=[".pth"])
|
||||
|
||||
def modeltitle(path, shorthash):
|
||||
abspath = os.path.abspath(path)
|
||||
|
||||
if abspath.startswith(model_path):
|
||||
name = abspath.replace(model_path, '')
|
||||
else:
|
||||
name = os.path.basename(path)
|
||||
|
||||
if name.startswith("\\") or name.startswith("/"):
|
||||
name = name[1:]
|
||||
|
||||
shortname = os.path.splitext(name.replace("/", "_").replace("\\", "_"))[0]
|
||||
|
||||
return f'{name} [{shorthash}]', shortname
|
||||
|
||||
models = []
|
||||
for filename in model_list:
|
||||
h = model_hash(filename)
|
||||
title, short_model_name = modeltitle(filename, h)
|
||||
models.append(title)
|
||||
|
||||
return models
|
||||
|
||||
def modeldataset(model_shortname):
|
||||
path = modelpath(model_shortname)
|
||||
if ("mmdet" in path and "segm" in path):
|
||||
dataset = 'coco'
|
||||
else:
|
||||
dataset = 'bbox'
|
||||
return dataset
|
||||
|
||||
def modelpath(model_shortname):
|
||||
model_list = modelloader.load_models(model_path=dd_models_path, ext_filter=[".pth"])
|
||||
model_h = model_shortname.split("[")[-1].split("]")[0]
|
||||
for path in model_list:
|
||||
if ( model_hash(path) == model_h):
|
||||
return path
|
||||
|
||||
def update_result_masks(results, masks):
|
||||
for i in range(len(masks)):
|
||||
boolmask = np.array(masks[i], dtype=bool)
|
||||
results[2][i] = boolmask
|
||||
return results
|
||||
|
||||
def create_segmask_preview(results, image):
|
||||
labels = results[0]
|
||||
bboxes = results[1]
|
||||
segms = results[2]
|
||||
|
||||
cv2_image = np.array(image)
|
||||
cv2_image = cv2_image[:, :, ::-1].copy()
|
||||
|
||||
for i in range(len(segms)):
|
||||
color = np.full_like(cv2_image, np.random.randint(100, 256, (1, 3), dtype=np.uint8))
|
||||
alpha = 0.2
|
||||
color_image = cv2.addWeighted(cv2_image, alpha, color, 1-alpha, 0)
|
||||
cv2_mask = segms[i].astype(np.uint8) * 255
|
||||
cv2_mask_bool = np.array(segms[i], dtype=bool)
|
||||
centroid = np.mean(np.argwhere(cv2_mask_bool),axis=0)
|
||||
centroid_x, centroid_y = int(centroid[1]), int(centroid[0])
|
||||
|
||||
cv2_mask_rgb = cv2.merge((cv2_mask, cv2_mask, cv2_mask))
|
||||
cv2_image = np.where(cv2_mask_rgb == 255, color_image, cv2_image)
|
||||
text_color = tuple([int(x) for x in ( color[0][0] - 100 )])
|
||||
name = labels[i]
|
||||
score = bboxes[i][4]
|
||||
score = str(score)[:4]
|
||||
text = name + ":" + score
|
||||
cv2.putText(cv2_image, text, (centroid_x - 30, centroid_y), cv2.FONT_HERSHEY_DUPLEX, 0.4, text_color, 1, cv2.LINE_AA)
|
||||
|
||||
if ( len(segms) > 0):
|
||||
preview_image = Image.fromarray(cv2.cvtColor(cv2_image, cv2.COLOR_BGR2RGB))
|
||||
else:
|
||||
preview_image = image
|
||||
|
||||
return preview_image
|
||||
|
||||
def is_allblack(mask):
|
||||
cv2_mask = np.array(mask)
|
||||
return cv2.countNonZero(cv2_mask) == 0
|
||||
|
||||
def bitwise_and_masks(mask1, mask2):
|
||||
cv2_mask1 = np.array(mask1)
|
||||
cv2_mask2 = np.array(mask2)
|
||||
cv2_mask = cv2.bitwise_and(cv2_mask1, cv2_mask2)
|
||||
mask = Image.fromarray(cv2_mask)
|
||||
return mask
|
||||
|
||||
def subtract_masks(mask1, mask2):
|
||||
cv2_mask1 = np.array(mask1)
|
||||
cv2_mask2 = np.array(mask2)
|
||||
cv2_mask = cv2.subtract(cv2_mask1, cv2_mask2)
|
||||
mask = Image.fromarray(cv2_mask)
|
||||
return mask
|
||||
|
||||
def dilate_masks(masks, dilation_factor, iter=1):
|
||||
if dilation_factor == 0:
|
||||
return masks
|
||||
dilated_masks = []
|
||||
kernel = np.ones((dilation_factor,dilation_factor), np.uint8)
|
||||
for i in range(len(masks)):
|
||||
cv2_mask = np.array(masks[i])
|
||||
dilated_mask = cv2.dilate(cv2_mask, kernel, iter)
|
||||
dilated_masks.append(Image.fromarray(dilated_mask))
|
||||
return dilated_masks
|
||||
|
||||
def offset_masks(masks, offset_x, offset_y):
|
||||
if (offset_x == 0 and offset_y == 0):
|
||||
return masks
|
||||
offset_masks = []
|
||||
for i in range(len(masks)):
|
||||
cv2_mask = np.array(masks[i])
|
||||
offset_mask = cv2_mask.copy()
|
||||
offset_mask = np.roll(offset_mask, -offset_y, axis=0)
|
||||
offset_mask = np.roll(offset_mask, offset_x, axis=1)
|
||||
|
||||
offset_masks.append(Image.fromarray(offset_mask))
|
||||
return offset_masks
|
||||
|
||||
def combine_masks(masks):
|
||||
initial_cv2_mask = np.array(masks[0])
|
||||
combined_cv2_mask = initial_cv2_mask
|
||||
for i in range(1, len(masks)):
|
||||
cv2_mask = np.array(masks[i])
|
||||
combined_cv2_mask = cv2.bitwise_or(combined_cv2_mask, cv2_mask)
|
||||
|
||||
combined_mask = Image.fromarray(combined_cv2_mask)
|
||||
return combined_mask
|
||||
|
||||
def create_segmasks(results):
|
||||
segms = results[2]
|
||||
segmasks = []
|
||||
for i in range(len(segms)):
|
||||
cv2_mask = segms[i].astype(np.uint8) * 255
|
||||
mask = Image.fromarray(cv2_mask)
|
||||
segmasks.append(mask)
|
||||
|
||||
return segmasks
|
||||
|
||||
import mmcv
|
||||
from mmdet.core import get_classes
|
||||
from mmdet.apis import (inference_detector,
|
||||
init_detector)
|
||||
|
||||
def get_device():
|
||||
device_id = shared.cmd_opts.device_id
|
||||
if device_id is not None:
|
||||
cuda_device = f"cuda:{device_id}"
|
||||
else:
|
||||
cuda_device = "cpu"
|
||||
return cuda_device
|
||||
|
||||
def inference(image, modelname, conf_thres, label):
|
||||
path = modelpath(modelname)
|
||||
if ( "mmdet" in path and "bbox" in path ):
|
||||
results = inference_mmdet_bbox(image, modelname, conf_thres, label)
|
||||
elif ( "mmdet" in path and "segm" in path):
|
||||
results = inference_mmdet_segm(image, modelname, conf_thres, label)
|
||||
return results
|
||||
|
||||
def inference_mmdet_segm(image, modelname, conf_thres, label):
|
||||
model_checkpoint = modelpath(modelname)
|
||||
model_config = os.path.splitext(model_checkpoint)[0] + ".py"
|
||||
model_device = get_device()
|
||||
model = init_detector(model_config, model_checkpoint, device=model_device)
|
||||
mmdet_results = inference_detector(model, np.array(image))
|
||||
bbox_results, segm_results = mmdet_results
|
||||
dataset = modeldataset(modelname)
|
||||
classes = get_classes(dataset)
|
||||
labels = [
|
||||
np.full(bbox.shape[0], i, dtype=np.int32)
|
||||
for i, bbox in enumerate(bbox_results)
|
||||
]
|
||||
n,m = bbox_results[0].shape
|
||||
if (n == 0):
|
||||
return [[],[],[]]
|
||||
labels = np.concatenate(labels)
|
||||
bboxes = np.vstack(bbox_results)
|
||||
segms = mmcv.concat_list(segm_results)
|
||||
filter_inds = np.where(bboxes[:,-1] > conf_thres)[0]
|
||||
results = [[],[],[]]
|
||||
for i in filter_inds:
|
||||
results[0].append(label + "-" + classes[labels[i]])
|
||||
results[1].append(bboxes[i])
|
||||
results[2].append(segms[i])
|
||||
|
||||
return results
|
||||
|
||||
def inference_mmdet_bbox(image, modelname, conf_thres, label):
|
||||
model_checkpoint = modelpath(modelname)
|
||||
model_config = os.path.splitext(model_checkpoint)[0] + ".py"
|
||||
model_device = get_device()
|
||||
model = init_detector(model_config, model_checkpoint, device=model_device)
|
||||
results = inference_detector(model, np.array(image))
|
||||
cv2_image = np.array(image)
|
||||
cv2_image = cv2_image[:, :, ::-1].copy()
|
||||
cv2_gray = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
segms = []
|
||||
for (x0, y0, x1, y1, conf) in results[0]:
|
||||
cv2_mask = np.zeros((cv2_gray.shape), np.uint8)
|
||||
cv2.rectangle(cv2_mask, (int(x0), int(y0)), (int(x1), int(y1)), 255, -1)
|
||||
cv2_mask_bool = cv2_mask.astype(bool)
|
||||
segms.append(cv2_mask_bool)
|
||||
|
||||
n,m = results[0].shape
|
||||
if (n == 0):
|
||||
return [[],[],[]]
|
||||
bboxes = np.vstack(results[0])
|
||||
filter_inds = np.where(bboxes[:,-1] > conf_thres)[0]
|
||||
results = [[],[],[]]
|
||||
for i in filter_inds:
|
||||
results[0].append(label)
|
||||
results[1].append(bboxes[i])
|
||||
results[2].append(segms[i])
|
||||
|
||||
return results
|
||||
|
|
@ -0,0 +1,300 @@
|
|||
import gc
|
||||
import re
|
||||
|
||||
import modules.shared as shared
|
||||
from modules import devices, images
|
||||
from modules.processing import fix_seed, process_images, Processed, StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img
|
||||
from . import mask_generator, utils, widlcards
|
||||
from .state import SharedSettingsContext
|
||||
|
||||
|
||||
class EyeMasksCore():
|
||||
|
||||
# Just comment mask type to disable it
|
||||
MASK_TYPES = [
|
||||
'Eyes dlib',
|
||||
'Face dlib',
|
||||
'Face depth',
|
||||
'Body depth',
|
||||
'Face mmdet',
|
||||
'Body mmdet'
|
||||
]
|
||||
|
||||
MASK_TYPE_EYES_DLIB = utils.index(MASK_TYPES, 'Eyes dlib')
|
||||
MASK_TYPE_FACE_DLIB = utils.index(MASK_TYPES, 'Face dlib')
|
||||
MASK_TYPE_FACE_DEPTH = utils.index(MASK_TYPES, 'Face depth')
|
||||
MASK_TYPE_BODY_DEPTH = utils.index(MASK_TYPES, 'Body depth')
|
||||
MASK_TYPE_FACE_MMDET = utils.index(MASK_TYPES, 'Face mmdet')
|
||||
MASK_TYPE_BODY_MMDET = utils.index(MASK_TYPES, 'Body mmdet')
|
||||
|
||||
# Replaced in original image generation info with regex on each iteration
|
||||
EM_DYNAMIC_PARAMS = [
|
||||
'em_mask_prompt_final'
|
||||
]
|
||||
|
||||
def execute(self, p,
|
||||
em_redraw_original,
|
||||
em_mask_type,
|
||||
em_mask_prompt,
|
||||
em_mask_negative_prompt,
|
||||
em_mask_padding,
|
||||
em_mask_padding_in_px,
|
||||
em_mask_steps,
|
||||
em_include_mask,
|
||||
em_mask_blur,
|
||||
em_denoising_strength,
|
||||
em_cfg_scale,
|
||||
em_width,
|
||||
em_height,
|
||||
em_inpaint_full_res,
|
||||
em_inpaint_full_res_padding,
|
||||
em_use_other_model,
|
||||
em_model
|
||||
):
|
||||
em_params = {
|
||||
'em_mask_prompt': em_mask_prompt,
|
||||
'em_mask_negative_prompt': em_mask_negative_prompt,
|
||||
'em_mask_type': em_mask_type,
|
||||
'em_mask_padding': em_mask_padding,
|
||||
'em_mask_steps': em_mask_steps,
|
||||
'em_mask_blur': em_mask_blur,
|
||||
'em_denoising_strength': em_denoising_strength,
|
||||
'em_cfg_scale': em_cfg_scale,
|
||||
'em_width': em_width,
|
||||
'em_height': em_height,
|
||||
'em_inpaint_full_res': em_inpaint_full_res,
|
||||
'em_inpaint_full_res_padding': em_inpaint_full_res_padding
|
||||
}
|
||||
|
||||
fix_seed(p)
|
||||
seed = p.seed
|
||||
iterations = p.n_iter
|
||||
p.n_iter = 1
|
||||
p.batch_size = 1
|
||||
p.do_not_save_grid = True
|
||||
p.do_not_save_samples = True
|
||||
|
||||
initial_info = None
|
||||
orig_image_info = None
|
||||
new_txt2img_info = None
|
||||
new_img2img_info = None
|
||||
|
||||
is_txt2img = isinstance(p, StableDiffusionProcessingTxt2Img)
|
||||
is_img2img = not is_txt2img
|
||||
|
||||
wildcards_generator_original = widlcards.WildcardsGenerator()
|
||||
wildcards_generator_mask = widlcards.WildcardsGenerator()
|
||||
|
||||
if (is_img2img):
|
||||
orig_image = p.init_images[0]
|
||||
|
||||
if orig_image.info is not None and 'parameters' in orig_image.info:
|
||||
orig_image_info = orig_image.info['parameters']
|
||||
init_orig_prompt = p.prompt or ''
|
||||
else:
|
||||
p_txt = p
|
||||
p = StableDiffusionProcessingImg2Img(
|
||||
init_images = None,
|
||||
resize_mode = 0,
|
||||
denoising_strength = em_denoising_strength,
|
||||
mask = None,
|
||||
mask_blur= em_mask_blur,
|
||||
inpainting_fill = 1,
|
||||
inpaint_full_res = em_inpaint_full_res,
|
||||
inpaint_full_res_padding= em_inpaint_full_res_padding,
|
||||
inpainting_mask_invert= 0,
|
||||
sd_model=p_txt.sd_model,
|
||||
outpath_samples=p_txt.outpath_samples,
|
||||
outpath_grids=p_txt.outpath_grids,
|
||||
prompt=p_txt.prompt,
|
||||
negative_prompt=p_txt.negative_prompt,
|
||||
styles=p_txt.styles,
|
||||
seed=p_txt.seed,
|
||||
subseed=p_txt.subseed,
|
||||
subseed_strength=p_txt.subseed_strength,
|
||||
seed_resize_from_h=p_txt.seed_resize_from_h,
|
||||
seed_resize_from_w=p_txt.seed_resize_from_w,
|
||||
sampler_name=p_txt.sampler_name,
|
||||
n_iter=p_txt.n_iter,
|
||||
steps=p_txt.steps,
|
||||
cfg_scale=p_txt.cfg_scale,
|
||||
width=p_txt.width,
|
||||
height=p_txt.height,
|
||||
tiling=p_txt.tiling,
|
||||
)
|
||||
p.do_not_save_grid = True
|
||||
p.do_not_save_samples = True
|
||||
init_orig_prompt = p_txt.prompt or ''
|
||||
|
||||
output_images = []
|
||||
init_image = None
|
||||
mask = None
|
||||
mask_success = False
|
||||
shared.state.job_count = 0
|
||||
|
||||
changing_model = em_use_other_model and em_model != 'None'
|
||||
|
||||
if changing_model:
|
||||
em_params['em_mask_model'] = em_model
|
||||
|
||||
with SharedSettingsContext(changing_model) as context:
|
||||
|
||||
for n in range(iterations):
|
||||
|
||||
devices.torch_gc()
|
||||
gc.collect()
|
||||
|
||||
start_seed = seed + n
|
||||
new_image_generated = False
|
||||
|
||||
mask_prompt = em_mask_prompt
|
||||
if em_mask_prompt is not None and len(em_mask_prompt.strip()) > 0:
|
||||
mask_prompt = wildcards_generator_mask.build_prompt(em_mask_prompt)
|
||||
|
||||
em_params['em_mask_prompt_final'] = mask_prompt
|
||||
|
||||
if is_txt2img:
|
||||
if init_image is None or em_redraw_original:
|
||||
p_txt.seed = start_seed
|
||||
init_image, new_txt2img_info, new_image_generated = self.create_new_image(
|
||||
p_txt, em_params, init_orig_prompt, changing_model, context, wildcards_generator_original
|
||||
)
|
||||
else:
|
||||
if init_image is None:
|
||||
init_image, new_img2img_info, new_image_generated = self.create_new_image(
|
||||
p, em_params, init_orig_prompt, changing_model, context, wildcards_generator_original
|
||||
)
|
||||
|
||||
p.seed = start_seed
|
||||
p.init_images = [init_image]
|
||||
p.prompt = mask_prompt
|
||||
p.negative_prompt = em_mask_negative_prompt
|
||||
|
||||
if new_image_generated:
|
||||
mask, mask_success = self.get_mask(
|
||||
em_mask_type, em_mask_padding, em_mask_padding_in_px, init_image,
|
||||
p, start_seed, mask_prompt, initial_info
|
||||
)
|
||||
|
||||
if mask_success:
|
||||
p.image_mask = mask
|
||||
|
||||
p.steps = em_mask_steps
|
||||
p.denoising_strength = em_denoising_strength
|
||||
p.mask_blur = em_mask_blur
|
||||
p.cfg_scale = em_cfg_scale
|
||||
p.width = em_width
|
||||
p.height = em_height
|
||||
p.inpaint_full_res = em_inpaint_full_res
|
||||
p.inpaint_full_res_padding= em_inpaint_full_res_padding
|
||||
p.inpainting_mask_invert = 0
|
||||
|
||||
print(f"Processing {n + 1} / {iterations}.")
|
||||
|
||||
if changing_model:
|
||||
context.apply_checkpoint(em_model)
|
||||
|
||||
shared.state.job_count += 1
|
||||
processed = process_images(p)
|
||||
save_prompt = p.prompt
|
||||
|
||||
if is_txt2img:
|
||||
initial_info = new_txt2img_info
|
||||
save_prompt = p_txt.prompt
|
||||
elif not is_txt2img:
|
||||
initial_info = new_img2img_info
|
||||
try:
|
||||
save_prompt = orig_image_info.split('\n')[0]
|
||||
except Exception as e:
|
||||
print(e)
|
||||
save_prompt = orig_image_info
|
||||
|
||||
output_images.append(processed.images[0])
|
||||
if em_include_mask and (n == iterations - 1 or (is_txt2img and em_redraw_original)):
|
||||
output_images.append(mask)
|
||||
|
||||
shared.state.current_image = processed.images[0]
|
||||
|
||||
images.save_image(
|
||||
processed.images[0],
|
||||
p.outpath_samples,
|
||||
"",
|
||||
start_seed,
|
||||
save_prompt,
|
||||
shared.opts.samples_format,
|
||||
info=self.update_info(initial_info, em_params),
|
||||
p=p
|
||||
)
|
||||
|
||||
devices.torch_gc()
|
||||
gc.collect()
|
||||
|
||||
return Processed(p, output_images, seed, initial_info)
|
||||
|
||||
def generate_mask(self, init_image, em_mask_type, em_mask_padding=20, em_mask_padding_in_px=False):
|
||||
if em_mask_type == self.MASK_TYPE_FACE_DLIB:
|
||||
return mask_generator.get_face_mask_dlib(init_image, em_mask_padding, em_mask_padding_in_px)
|
||||
elif em_mask_type == self.MASK_TYPE_FACE_DEPTH:
|
||||
return mask_generator.get_face_mask_depth(init_image)
|
||||
elif em_mask_type == self.MASK_TYPE_BODY_DEPTH:
|
||||
return mask_generator.get_body_mask_depth(init_image)
|
||||
elif em_mask_type == self.MASK_TYPE_FACE_MMDET:
|
||||
return mask_generator.get_face_mask_mmdet(init_image)
|
||||
elif em_mask_type == self.MASK_TYPE_BODY_MMDET:
|
||||
return mask_generator.get_body_mask_mmdet(init_image)
|
||||
else:
|
||||
return mask_generator.get_eyes_mask_dlib(init_image, em_mask_padding, em_mask_padding_in_px)
|
||||
|
||||
def get_mask(self,
|
||||
em_mask_type, em_mask_padding, em_mask_padding_in_px,
|
||||
init_image, p, start_seed, mask_prompt, initial_info
|
||||
):
|
||||
mask, mask_success = self.generate_mask(init_image, em_mask_type, em_mask_padding, em_mask_padding_in_px)
|
||||
|
||||
if shared.opts.em_save_masks:
|
||||
images.save_image(
|
||||
mask,
|
||||
shared.opts.em_outdir_masks,
|
||||
"",
|
||||
start_seed,
|
||||
mask_prompt,
|
||||
shared.opts.samples_format,
|
||||
info=initial_info,
|
||||
p=p
|
||||
)
|
||||
|
||||
return mask, mask_success
|
||||
|
||||
def update_info(self, info, em_params):
|
||||
reg_ex = ':\s[0-9a-zA-Z\-\.\s]+'
|
||||
for param in self.EM_DYNAMIC_PARAMS:
|
||||
if param in em_params:
|
||||
info = re.sub(param + reg_ex, '%s: %s' % (param, em_params[param]), info)
|
||||
return info
|
||||
|
||||
##############################
|
||||
##### Creating new image #####
|
||||
##############################
|
||||
|
||||
def create_new_image(self, p, em_params, init_orig_prompt, changing_model, context, wildcards_generator):
|
||||
em_params = utils.removeEmptyStringValues(em_params)
|
||||
if changing_model:
|
||||
context.restore_original_checkpoint()
|
||||
self.build_original_prompt(p, init_orig_prompt, em_params, wildcards_generator)
|
||||
return self.generate_initial_image_with_extra_params(p, em_params)
|
||||
|
||||
def build_original_prompt(self, p, init_orig_prompt, em_params, wildcards_generator):
|
||||
if not shared.opts.em_wildcards_in_original:
|
||||
return
|
||||
new_prompt = wildcards_generator.build_prompt(init_orig_prompt)
|
||||
if new_prompt != init_orig_prompt:
|
||||
em_params['em_prompt'] = init_orig_prompt
|
||||
em_params['em_prompt_final'] = new_prompt
|
||||
p.prompt = new_prompt
|
||||
|
||||
def generate_initial_image_with_extra_params(self, p, extra_params):
|
||||
p.extra_generation_params = p.extra_generation_params or {}
|
||||
p.extra_generation_params.update(extra_params)
|
||||
shared.state.job_count += 1
|
||||
processed = process_images(p)
|
||||
return processed.images[0], processed.info, True
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
import gc
|
||||
|
||||
import modules.shared as shared
|
||||
from modules import devices, images
|
||||
from modules.processing import fix_seed, process_images, StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img
|
||||
from . import widlcards
|
||||
from .state import SharedSettingsContext
|
||||
|
||||
from .script import EyeMasksCore
|
||||
|
||||
|
||||
class EyeMasksEmbeddedCore(EyeMasksCore):
|
||||
|
||||
def execute_process(self, *args):
|
||||
|
||||
p, em_enabled = args[:2]
|
||||
|
||||
if not em_enabled:
|
||||
return
|
||||
|
||||
p.batch_size = 1
|
||||
p.do_not_save_grid = True
|
||||
p.do_not_save_samples = True
|
||||
|
||||
def execute_postprocess(self, p, processed,
|
||||
em_enabled,
|
||||
em_n_iter,
|
||||
em_mask_type,
|
||||
em_mask_prompt,
|
||||
em_mask_negative_prompt,
|
||||
em_mask_padding,
|
||||
em_mask_padding_in_px,
|
||||
em_mask_steps,
|
||||
em_include_mask,
|
||||
em_mask_blur,
|
||||
em_denoising_strength,
|
||||
em_cfg_scale,
|
||||
em_width,
|
||||
em_height,
|
||||
em_inpaint_full_res,
|
||||
em_inpaint_full_res_padding,
|
||||
em_use_other_model,
|
||||
em_model
|
||||
):
|
||||
if not em_enabled:
|
||||
return
|
||||
|
||||
em_params = {
|
||||
'em_mask_prompt': em_mask_prompt,
|
||||
'em_mask_negative_prompt': em_mask_negative_prompt,
|
||||
'em_mask_type': em_mask_type,
|
||||
'em_mask_padding': em_mask_padding,
|
||||
'em_mask_steps': em_mask_steps,
|
||||
'em_mask_blur': em_mask_blur,
|
||||
'em_denoising_strength': em_denoising_strength,
|
||||
'em_cfg_scale': em_cfg_scale,
|
||||
'em_width': em_width,
|
||||
'em_height': em_height,
|
||||
'em_inpaint_full_res': em_inpaint_full_res,
|
||||
'em_inpaint_full_res_padding': em_inpaint_full_res_padding
|
||||
}
|
||||
|
||||
fix_seed(p)
|
||||
seed = p.seed
|
||||
iterations = em_n_iter
|
||||
|
||||
initial_info = None
|
||||
orig_image_info = None
|
||||
new_img2img_info = None
|
||||
|
||||
is_txt2img = isinstance(p, StableDiffusionProcessingTxt2Img)
|
||||
|
||||
wildcards_generator_original = widlcards.WildcardsGenerator()
|
||||
wildcards_generator_mask = widlcards.WildcardsGenerator()
|
||||
|
||||
p_em = StableDiffusionProcessingImg2Img(
|
||||
init_images=[processed.images[0]],
|
||||
resize_mode=0,
|
||||
denoising_strength=em_denoising_strength,
|
||||
mask=None,
|
||||
mask_blur=em_mask_blur,
|
||||
inpainting_fill=1,
|
||||
inpaint_full_res=em_inpaint_full_res,
|
||||
inpaint_full_res_padding=em_inpaint_full_res_padding,
|
||||
inpainting_mask_invert=0,
|
||||
sd_model=p.sd_model,
|
||||
outpath_samples=p.outpath_samples,
|
||||
outpath_grids=p.outpath_grids,
|
||||
prompt=p.prompt,
|
||||
negative_prompt=p.negative_prompt,
|
||||
styles=p.styles,
|
||||
seed=p.seed,
|
||||
subseed=p.subseed,
|
||||
subseed_strength=p.subseed_strength,
|
||||
seed_resize_from_h=p.seed_resize_from_h,
|
||||
seed_resize_from_w=p.seed_resize_from_w,
|
||||
sampler_name=p.sampler_name,
|
||||
n_iter=p.n_iter,
|
||||
steps=p.steps,
|
||||
cfg_scale=p.cfg_scale,
|
||||
width=p.width,
|
||||
height=p.height,
|
||||
tiling=p.tiling,
|
||||
)
|
||||
p_em.do_not_save_grid = True
|
||||
p_em.do_not_save_samples = True
|
||||
|
||||
init_orig_prompt = p.prompt or ''
|
||||
initial_info = processed.info
|
||||
|
||||
shared.state.job_count = 0
|
||||
|
||||
changing_model = em_use_other_model and em_model != 'None'
|
||||
|
||||
if changing_model:
|
||||
em_params['em_mask_model'] = em_model
|
||||
|
||||
with SharedSettingsContext(changing_model) as context:
|
||||
|
||||
for i in range(len(processed.images)):
|
||||
|
||||
orig_image = processed.images[i]
|
||||
init_image = None
|
||||
|
||||
if orig_image.info is not None and 'parameters' in orig_image.info:
|
||||
orig_image_info = orig_image.info['parameters']
|
||||
|
||||
shared.state.job_count += 1
|
||||
|
||||
mask, mask_success = self.get_mask(
|
||||
em_mask_type, em_mask_padding, em_mask_padding_in_px, orig_image,
|
||||
p_em, seed, em_mask_prompt, initial_info
|
||||
)
|
||||
|
||||
if mask_success:
|
||||
p_em.image_mask = mask
|
||||
|
||||
for n in range(iterations):
|
||||
|
||||
devices.torch_gc()
|
||||
gc.collect()
|
||||
|
||||
start_seed = seed + n
|
||||
|
||||
mask_prompt = em_mask_prompt
|
||||
if em_mask_prompt is not None and len(em_mask_prompt.strip()) > 0:
|
||||
mask_prompt = wildcards_generator_mask.build_prompt(em_mask_prompt)
|
||||
|
||||
em_params['em_mask_prompt_final'] = mask_prompt
|
||||
|
||||
if init_image is None:
|
||||
init_image, new_img2img_info, new_image_generated = self.create_new_image(
|
||||
p_em, em_params, init_orig_prompt, changing_model, context, wildcards_generator_original
|
||||
)
|
||||
|
||||
p_em.seed = start_seed
|
||||
p_em.init_images = [orig_image]
|
||||
p_em.prompt = mask_prompt
|
||||
p_em.negative_prompt = em_mask_negative_prompt
|
||||
p_em.steps = em_mask_steps
|
||||
p_em.denoising_strength = em_denoising_strength
|
||||
p_em.mask_blur = em_mask_blur
|
||||
p_em.cfg_scale = em_cfg_scale
|
||||
p_em.width = em_width
|
||||
p_em.height = em_height
|
||||
p_em.inpaint_full_res = em_inpaint_full_res
|
||||
p_em.inpaint_full_res_padding= em_inpaint_full_res_padding
|
||||
p_em.inpainting_mask_invert = 0
|
||||
|
||||
print(f"Processing {n + 1} / {iterations}.")
|
||||
|
||||
if changing_model:
|
||||
context.apply_checkpoint(em_model)
|
||||
|
||||
shared.state.job_count += 1
|
||||
processed_em = process_images(p_em)
|
||||
|
||||
lines = new_img2img_info.splitlines(keepends=True)
|
||||
lines[0] = p.prompt + '\n'
|
||||
new_img2img_info = ''.join(lines)
|
||||
|
||||
if is_txt2img:
|
||||
initial_info = new_img2img_info
|
||||
save_prompt = p.prompt
|
||||
elif not is_txt2img:
|
||||
initial_info = new_img2img_info
|
||||
try:
|
||||
save_prompt = orig_image_info.split('\n')[0]
|
||||
except Exception as e:
|
||||
print(e)
|
||||
save_prompt = orig_image_info
|
||||
|
||||
processed.images.append(processed_em.images[0])
|
||||
if em_include_mask and (n == iterations - 1):
|
||||
processed.images.append(mask)
|
||||
|
||||
shared.state.current_image = processed_em.images[0]
|
||||
|
||||
images.save_image(
|
||||
processed_em.images[0],
|
||||
p_em.outpath_samples,
|
||||
"",
|
||||
start_seed,
|
||||
save_prompt,
|
||||
shared.opts.samples_format,
|
||||
info=self.update_info(initial_info, em_params),
|
||||
p=p_em
|
||||
)
|
||||
|
||||
devices.torch_gc()
|
||||
gc.collect()
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import modules.shared as shared
|
||||
import modules.sd_samplers
|
||||
import modules.sd_models
|
||||
import modules.sd_vae
|
||||
|
||||
|
||||
class SharedSettingsContext(object):
|
||||
|
||||
def __init__(self, changing_model):
|
||||
self.changing_model = changing_model
|
||||
|
||||
def __enter__(self):
|
||||
if self.changing_model:
|
||||
self.CLIP_stop_at_last_layers = shared.opts.CLIP_stop_at_last_layers
|
||||
self.sd_model_checkpoint = shared.opts.sd_model_checkpoint
|
||||
self.vae = shared.opts.sd_vae
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
if self.changing_model:
|
||||
shared.opts.data["sd_vae"] = self.vae
|
||||
shared.opts.data["CLIP_stop_at_last_layers"] = self.CLIP_stop_at_last_layers
|
||||
self.apply_checkpoint(self.sd_model_checkpoint)
|
||||
modules.sd_vae.reload_vae_weights()
|
||||
|
||||
def apply_checkpoint(self, x):
|
||||
info = modules.sd_models.get_closet_checkpoint_match(x)
|
||||
if info is None:
|
||||
raise RuntimeError(f"Unknown checkpoint: {x}")
|
||||
modules.sd_models.reload_model_weights(shared.sd_model, info)
|
||||
|
||||
def restore_original_checkpoint(self):
|
||||
self.apply_checkpoint(self.sd_model_checkpoint)
|
||||
|
||||
def find_vae(self, name: str):
|
||||
if name.lower() in ['auto', 'automatic']:
|
||||
return modules.sd_vae.unspecified
|
||||
if name.lower() == 'none':
|
||||
return None
|
||||
else:
|
||||
choices = [x for x in sorted(modules.sd_vae.vae_dict, key=lambda x: len(x)) if name.lower().strip() in x.lower()]
|
||||
if len(choices) == 0:
|
||||
print(f"No VAE found for {name}; using automatic")
|
||||
return modules.sd_vae.unspecified
|
||||
else:
|
||||
return modules.sd_vae.vae_dict[choices[0]]
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
import datetime
|
||||
import importlib
|
||||
import gradio as gr
|
||||
|
||||
import modules.sd_models
|
||||
from modules.ui_components import ToolButton
|
||||
|
||||
from . import constants, script, script_embedded, utils, widlcards, state, mask_generator
|
||||
from modules import shared
|
||||
|
||||
|
||||
class EyeMaskUI():
|
||||
|
||||
def __init__(self, eye_mask_script):
|
||||
self.eye_mask_script = eye_mask_script
|
||||
self.eye_mask_core = eye_mask_script.eye_mask_core
|
||||
self.is_embedded = isinstance(self.eye_mask_core, script_embedded.EyeMasksEmbeddedCore)
|
||||
|
||||
def restart(self):
|
||||
# interrupt current image processing
|
||||
shared.state.interrupt()
|
||||
# reimport all dynamic packs
|
||||
importlib.reload(utils)
|
||||
importlib.reload(state)
|
||||
importlib.reload(widlcards)
|
||||
importlib.reload(mask_generator)
|
||||
# instantiate again core logic
|
||||
if self.is_embedded:
|
||||
importlib.reload(script_embedded)
|
||||
self.eye_mask_script.eye_mask_core = script_embedded.EyeMasksEmbeddedCore()
|
||||
else:
|
||||
importlib.reload(script)
|
||||
self.eye_mask_script.eye_mask_core = script.EyeMasksCore()
|
||||
# update UI
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
print(f'{constants.script_name} reloaded. Last reload {now}')
|
||||
return f'<div style="text-align:center; margin-bottom: 8px;">Last reload: {now}</div>'
|
||||
|
||||
def get_elem_id_prefix(self):
|
||||
result = 'em-'
|
||||
if self.is_embedded:
|
||||
result += 'emb-'
|
||||
return result
|
||||
|
||||
def get_id(self, id, is_img2img):
|
||||
return "%s%s-%s" % (self.get_elem_id_prefix(), id, "img2img" if is_img2img else "txt2img")
|
||||
|
||||
def render(self, is_img2img):
|
||||
if self.is_embedded:
|
||||
with gr.Group():
|
||||
with gr.Accordion(constants.script_name, open=False):
|
||||
with gr.Group(elem_id=self.get_id("eye-mask-container", is_img2img)):
|
||||
result = self.render_inner(is_img2img)
|
||||
else:
|
||||
with gr.Blocks():
|
||||
with gr.Group(elem_id=self.get_id("eye-mask-container", is_img2img)):
|
||||
result = self.render_inner(is_img2img)
|
||||
return result
|
||||
|
||||
def render_inner(self, is_img2img):
|
||||
|
||||
def get_id(id):
|
||||
return self.get_id(id, is_img2img)
|
||||
|
||||
result = []
|
||||
|
||||
if self.is_embedded:
|
||||
with gr.Row():
|
||||
with gr.Column(scale=2):
|
||||
em_enabled = gr.Checkbox(elem_id=get_id("enabled"), label="Enable", value=False, visible=True)
|
||||
result.append(em_enabled)
|
||||
with gr.Column(scale=1):
|
||||
em_n_iter = gr.Slider(elem_id=get_id("count"), label="Batch count", minimum=1, maximum=100, step=1, value=1, visible=True)
|
||||
result.append(em_n_iter)
|
||||
|
||||
with gr.Row():
|
||||
em_mask_prompt = gr.Textbox(
|
||||
elem_id=get_id("prompt"),
|
||||
show_label=False,
|
||||
lines=1,
|
||||
placeholder="Mask prompt",
|
||||
visible=True
|
||||
)
|
||||
with gr.Row():
|
||||
em_mask_negative_prompt = gr.Textbox(
|
||||
elem_id=get_id("negative-prompt"),
|
||||
show_label=False,
|
||||
lines=1,
|
||||
placeholder="Negative mask prompt",
|
||||
visible=True
|
||||
)
|
||||
with gr.Row():
|
||||
em_mask_type = gr.Radio(
|
||||
elem_id=get_id("mask-type"),
|
||||
label="Mask type",
|
||||
choices=self.eye_mask_core.MASK_TYPES,
|
||||
value=self.eye_mask_core.MASK_TYPES[0],
|
||||
type="index"
|
||||
)
|
||||
em_info = ToolButton(value="\u2139\uFE0F", elem_id="eye-info-button", full_width=False)
|
||||
em_info.click(
|
||||
fn=lambda: '',
|
||||
_js="() => EyeMaskController.showInfo()",
|
||||
show_progress=False
|
||||
)
|
||||
with gr.Row(elem_id=get_id("mask-type-row")):
|
||||
with gr.Accordion("Mask Preview", open=False):
|
||||
with gr.Row():
|
||||
em_mp_input_image = gr.Image(source="upload", mirror_webcam=False, type="pil")
|
||||
em_mp_generated_image = gr.Image(label="Mask result", visible=True)
|
||||
with gr.Row():
|
||||
def on_generate_mask_click(input_image, em_mask_type):
|
||||
if input_image is None:
|
||||
return
|
||||
mask, mask_success = self.eye_mask_core.generate_mask(input_image, em_mask_type)
|
||||
return gr.update(value=mask, visible=mask_success, interactive=False)
|
||||
em_mp_generate_button = gr.Button(value="Generate mask")
|
||||
em_mp_generate_button.click(fn=on_generate_mask_click, inputs=[em_mp_input_image, em_mask_type], outputs=[em_mp_generated_image])
|
||||
with gr.Row():
|
||||
em_mask_padding = gr.Slider(elem_id=get_id("mask-padding"), label="Mask padding (dlib only)", minimum=0, maximum=100, step=1, value=20, visible=True)
|
||||
em_mask_steps = gr.Slider(elem_id=get_id("mask-steps"), minimum=1, maximum=150, step=1, label="Sampling steps", value=20)
|
||||
with gr.Row():
|
||||
em_mask_blur = gr.Slider(elem_id=get_id("mask-blur"), label="Mask blur", minimum=0, maximum=64, step=1, value=4, visible=True)
|
||||
em_denoising_strength = gr.Slider(elem_id=get_id("denoising-strength"), label="Denoising strength (Inpaint)", minimum=0.0, maximum=1.0, step=0.01, value=0.4, visible=True)
|
||||
with gr.Row():
|
||||
em_inpaint_full_res = gr.Checkbox(label="Inpaint at full resolution", value=True, visible=False)
|
||||
em_inpaint_full_res_padding = gr.Slider(elem_id=get_id("full-res-padding"), label="Inpaint at full resolution padding, pixels", minimum=0, maximum=256, step=4, value=88, visible=True)
|
||||
em_cfg_scale = gr.Slider(elem_id=get_id("cfg"), minimum=1.0, maximum=30.0, step=0.5, label="CFG Scale", value=7.0)
|
||||
with gr.Row():
|
||||
with gr.Column(scale=4):
|
||||
em_width = gr.Slider(elem_id=get_id("width"), minimum=64, maximum=2048, step=8, label="Width", value=512)
|
||||
em_height = gr.Slider(elem_id=get_id("height"), minimum=64, maximum=2048, step=8, label="Height", value=512)
|
||||
with gr.Row():
|
||||
em_include_mask = gr.Checkbox(elem_id=get_id("include-mask"), label="Include mask", value=True, visible=True)
|
||||
|
||||
if not self.is_embedded:
|
||||
em_redraw_original = gr.Checkbox(elem_id=get_id("redraw-original"), label="Redraw original", value=True, visible=(not is_img2img))
|
||||
result.append(em_redraw_original)
|
||||
|
||||
em_mask_padding_in_px = gr.Checkbox(elem_id=get_id("padding-in-px"), label="Padding in px", value=True, visible=True)
|
||||
em_use_other_model = gr.Checkbox(elem_id=get_id("use-other-model"), label="Use other model", value=False, visible=True)
|
||||
with gr.Row():
|
||||
em_model = gr.Dropdown(
|
||||
elem_id=get_id('mask-model'),
|
||||
label="Mask model",
|
||||
choices=["None"] + list(modules.sd_models.checkpoints_list.keys()),
|
||||
value="None",
|
||||
visible=False,
|
||||
type="value",
|
||||
)
|
||||
em_use_other_model.change(
|
||||
lambda visible: {"visible": visible, "__type__": "update"},
|
||||
inputs=[em_use_other_model],
|
||||
outputs=[em_model]
|
||||
)
|
||||
with gr.Row():
|
||||
reload_info = gr.HTML()
|
||||
with gr.Row():
|
||||
reload_button = gr.Button(
|
||||
elem_id=get_id("reload-extension"),
|
||||
value="Reload Extension",
|
||||
full_width=True,
|
||||
visible=utils.get_opt("em_dev_mode")
|
||||
)
|
||||
reload_button.click(
|
||||
fn=self.restart,
|
||||
_js="() => toastr.success('Extension reloaded')",
|
||||
outputs=[reload_info],
|
||||
show_progress=False
|
||||
)
|
||||
|
||||
return result + [
|
||||
em_mask_type,
|
||||
em_mask_prompt,
|
||||
em_mask_negative_prompt,
|
||||
em_mask_padding,
|
||||
em_mask_padding_in_px,
|
||||
em_mask_steps,
|
||||
em_include_mask,
|
||||
em_mask_blur,
|
||||
em_denoising_strength,
|
||||
em_cfg_scale,
|
||||
em_width,
|
||||
em_height,
|
||||
em_inpaint_full_res,
|
||||
em_inpaint_full_res_padding,
|
||||
em_use_other_model,
|
||||
em_model
|
||||
]
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import numpy as np
|
||||
import importlib.util
|
||||
|
||||
from modules import shared
|
||||
|
||||
|
||||
def get_opt(key, default=False):
|
||||
try:
|
||||
return shared.opts.__getattr__(key) or default
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
def expand_polygon(points, distance):
|
||||
center = np.mean(points, axis=0)
|
||||
new_points = []
|
||||
for point in points:
|
||||
vec = point - center
|
||||
vec = vec / np.linalg.norm(vec)
|
||||
new_points.append(point + vec * distance)
|
||||
return new_points
|
||||
|
||||
def calculate_distance(p1, p2):
|
||||
x1, y1 = p1
|
||||
x2, y2 = p2
|
||||
return ((x1 - x2)**2 + (y1 - y2)**2)**0.5
|
||||
|
||||
def load_module_from_file(module_name, file_path):
|
||||
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
def index(list, element):
|
||||
if element not in list:
|
||||
return -1
|
||||
return list.index(element)
|
||||
|
||||
def removeEmptyStringValues(dct):
|
||||
return { k: v for k, v in dct.items() if v }
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import os
|
||||
import sys
|
||||
import re
|
||||
import random
|
||||
|
||||
from .constants import script_wildcards_dir
|
||||
|
||||
|
||||
class WildcardsGenerator():
|
||||
|
||||
def __init__(self):
|
||||
self.wildcards_warned_about_files = {}
|
||||
self.wildcard_indexes = {}
|
||||
|
||||
def build_prompt(self, prompt):
|
||||
if re.search("_{2}[a-z_]+_{2}", prompt):
|
||||
return "".join(self.replace_wildcard(chunk) for chunk in prompt.split("__"))
|
||||
return prompt
|
||||
|
||||
def get_index(self, file_name, max_index):
|
||||
if not file_name in self.wildcard_indexes or self.wildcard_indexes[file_name] == max_index:
|
||||
self.wildcard_indexes[file_name] = 0
|
||||
else:
|
||||
self.wildcard_indexes[file_name] += 1
|
||||
return self.wildcard_indexes[file_name]
|
||||
|
||||
def replace_wildcard(self, text):
|
||||
|
||||
if " " in text or len(text) == 0:
|
||||
return text
|
||||
|
||||
replacement_file = os.path.join(script_wildcards_dir, f"{text}.txt")
|
||||
if os.path.exists(replacement_file):
|
||||
with open(replacement_file, encoding="utf8") as f:
|
||||
lines = f.read().splitlines()
|
||||
if text[-5:] == '_each':
|
||||
return lines[self.get_index(text, len(lines) - 1)]
|
||||
else:
|
||||
return random.Random().choice(lines)
|
||||
else:
|
||||
if replacement_file not in self.wildcards_warned_about_files:
|
||||
print(f"File {replacement_file} not found for the __{text}__ wildcard.", file=sys.stderr)
|
||||
self.wildcards_warned_about_files[replacement_file] = 1
|
||||
|
||||
return text
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"sourceRoot":"","sources":["style/_mixins.scss","style/_main.scss","style/_dialog.scss","style/_toastr.scss"],"names":[],"mappings":"AAmBgB,oMAEQ,OCpBc,MDelC,wHASY,cClBmB,KDS/B,oGASY,WCdc,KDK1B,gHASY,cCViB,KDUjB,QCViB,ODUjB,UCViB,KDC7B,4HASY,OCJoB,iBClBpC,mBACI,kBACA,SACA,+BACA,gCAGJ,wBACI,eACA,MACA,OACA,WACA,YACA,aACA,qCAGJ,6BACI,aACA,eACA,QACA,OACA,QACA,YACA,cACA,YACA,YACA,WACA,kBACA,yBACA,yBACA,4CACA,gBACA,aAGJ,uCACI,eACA,gBACA,UACA,QAGJ,sDACI,iBAGJ,4DACI,gBAGJ,kEACI,kBAGJ,iCACI,kBAGJ,oCACI,SACA,UACA,eACA,mBAGJ,iCACI,iBAGJ,wCACI,cACA,mBACA,iBACA,eACA,mBACA,0BACA,WAGJ,+CACI,mBAGJ,kCACI,6BACA,gCACA,aACA,iBACA,kBAGJ,gDACI,cAGJ,qDACI,UACA,WClGJ,aACI,iBAEJ,eACI,yBACA,qBAEJ,sCAEI,WAEJ,uBACI,WACA,qBAEJ,oBACI,kBACA,aACA,WACA,YACA,eACA,iBACA,WACA,iCACA,yBACA,WACA,+DACA,yBACA,cAEJ,oDAEI,WACA,qBACA,eACA,WACA,+DACA,yBAEJ,yBACI,YACA,WACA,WAKJ,0BACI,UACA,eACA,yBACA,SACA,wBAEJ,kBACI,MACA,QACA,WAEJ,qBACI,SACA,QACA,WAEJ,sBACI,MACA,QACA,WAEJ,yBACI,SACA,QACA,WAEJ,gBACI,SACA,UAEJ,iBACI,SACA,WAEJ,oBACI,WACA,YAEJ,mBACI,YACA,UAEJ,iBACI,eACA,eACA,oBAGJ,mBACI,2BACA,8BACA,sBAEJ,qBACI,kBACA,oBACA,gBACA,eACA,4BACA,YACA,mCACA,sCACA,8BACA,gCACA,4BACA,8BACA,iCACA,yBACA,WACA,WACA,+DACA,yBAEJ,yBACI,cACA,4BACA,sCAEJ,2BACI,8BACA,iCACA,yBACA,UACA,gEACA,0BACA,eAEJ,6BACI,0wBAEJ,8BACI,kzBAEJ,gCACI,sgBAEJ,gCACI,0uBAEJ,+EAEI,YACA,iBACA,kBAEJ,uFAEI,UACA,iBACA,kBAEJ,OACI,yBAEJ,eACI,yBAEJ,aACI,yBAEJ,YACI,yBAEJ,eACI,yBAEJ,gBACI,kBACA,OACA,SACA,WACA,sBACA,WACA,+DACA,yBAGJ,kCACI,qBACI,yBACA,WAEJ,yBACI,yBAEJ,qCACI,aACA,WAEJ,0CACI,YACA,YAGR,wDACI,qBACI,yBACA,WAEJ,yBACI,yBAEJ,qCACI,aACA,WAEJ,0CACI,YACA,YAGR,wDACI,qBACI,4BACA,WAEJ,yBACI","file":"style.css"}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
|
||||
/***** Dialog styles *****/
|
||||
|
||||
#dialogs-container {
|
||||
position: relative;
|
||||
height: 0;
|
||||
background-color: transparent;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
#dialogs-container.open {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1000;
|
||||
background-color: rgba(41, 111, 180, 0.3);
|
||||
}
|
||||
|
||||
#dialogs-container > div.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 30%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 300px;
|
||||
max-width: 90%;
|
||||
height: auto;
|
||||
margin: auto;
|
||||
color: #000;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #b1dde4;
|
||||
background-color: rgba(241, 248, 255, 1);
|
||||
box-shadow: 0 0 35px 3px rgba(41, 111, 180, 0.5);
|
||||
overflow: hidden;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
#dialogs-container > div.modal.modal-big {
|
||||
font-size: 14px;
|
||||
max-width: 700px;
|
||||
width: 95%;
|
||||
top: 10%;
|
||||
}
|
||||
|
||||
#dialogs-container > div.modal.modal-big .modal-content {
|
||||
max-height: 350px;
|
||||
}
|
||||
|
||||
#dialogs-container > div.modal.modal-big .modal-footer button {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
#dialogs-container .modal-header, #dialogs-container .modal-footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#dialogs-container .modal-header {
|
||||
padding: 10px 10px;
|
||||
}
|
||||
|
||||
#dialogs-container .modal-header h5 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#dialogs-container .modal-footer {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
#dialogs-container .modal-footer button {
|
||||
min-width: 40%;
|
||||
border-radius: 30px;
|
||||
padding: 5px 15px;
|
||||
margin: 5px 7px;
|
||||
background: #296fb4;
|
||||
transition: all 0.2s linear;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#dialogs-container .modal-footer button.danger {
|
||||
background: #df4c73;
|
||||
}
|
||||
|
||||
#dialogs-container .modal-content {
|
||||
border-top: 1px solid #b1dde4;
|
||||
border-bottom: 1px solid #b1dde4;
|
||||
padding: 20px;
|
||||
max-height: 250px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#dialogs-container .modal-content a.simple-link {
|
||||
color: #296fb4;
|
||||
}
|
||||
|
||||
#dialogs-container .modal-content::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
@include select('eye-mask-container', (
|
||||
'.gr-button-tool': (
|
||||
margin: 0.55em
|
||||
)
|
||||
));
|
||||
|
||||
@include select('negative-prompt', (
|
||||
margin-bottom: 15px
|
||||
));
|
||||
|
||||
@include select('mask-model', (
|
||||
margin-top: 10px
|
||||
));
|
||||
|
||||
@include select('mask-type-row', (
|
||||
margin-bottom: 20px,
|
||||
padding: 0 12px,
|
||||
font-size: 12px
|
||||
));
|
||||
|
||||
@include select('reload-extension', (
|
||||
margin: 10px 12px 0 12px
|
||||
));
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
@mixin select($id, $rules) {
|
||||
|
||||
$selectors: (
|
||||
'#em-#{ $id }-txt2img',
|
||||
'#em-#{ $id }-img2img',
|
||||
'#em-emb-#{ $id }-txt2img',
|
||||
'#em-emb-#{ $id }-img2img'
|
||||
);
|
||||
|
||||
$selector-string: '';
|
||||
|
||||
@each $selector in $selectors {
|
||||
$selector-string: #{$selector-string}#{$selector}', ' ;
|
||||
}
|
||||
|
||||
#{ $selector-string } {
|
||||
@each $property, $value in $rules {
|
||||
@if type-of($value) == map {
|
||||
#{ $property } {
|
||||
@each $subproperty, $subvalue in $value {
|
||||
#{ $subproperty }: $subvalue;
|
||||
}
|
||||
}
|
||||
} @else {
|
||||
#{ $property }: $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
|
||||
/***** Toastr styles *****/
|
||||
|
||||
.toast-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
.toast-message {
|
||||
-ms-word-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.toast-message a,
|
||||
.toast-message label {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.toast-message a:hover {
|
||||
color: #CCCCCC;
|
||||
text-decoration: none;
|
||||
}
|
||||
.toast-close-button {
|
||||
position: relative;
|
||||
right: -0.3em;
|
||||
top: -0.3em;
|
||||
float: right;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
-webkit-text-shadow: 0 1px 0 #ffffff;
|
||||
text-shadow: 0 1px 0 #ffffff;
|
||||
opacity: 0.8;
|
||||
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
|
||||
filter: alpha(opacity=80);
|
||||
line-height: 1;
|
||||
}
|
||||
.toast-close-button:hover,
|
||||
.toast-close-button:focus {
|
||||
color: #000000;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
opacity: 0.4;
|
||||
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
|
||||
filter: alpha(opacity=40);
|
||||
}
|
||||
.rtl .toast-close-button {
|
||||
left: -0.3em;
|
||||
float: left;
|
||||
right: 0.3em;
|
||||
}
|
||||
/*Additional properties for button version
|
||||
iOS requires the button element instead of an anchor tag.
|
||||
If you want the anchor version, it requires `href="#"`.*/
|
||||
button.toast-close-button {
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
.toast-top-center {
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.toast-bottom-center {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.toast-top-full-width {
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.toast-bottom-full-width {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.toast-top-left {
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
}
|
||||
.toast-top-right {
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
}
|
||||
.toast-bottom-right {
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
}
|
||||
.toast-bottom-left {
|
||||
bottom: 12px;
|
||||
left: 12px;
|
||||
}
|
||||
#toast-container {
|
||||
position: fixed;
|
||||
z-index: 999999;
|
||||
pointer-events: none;
|
||||
/*overrides*/
|
||||
}
|
||||
#toast-container * {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#toast-container > div {
|
||||
position: relative;
|
||||
pointer-events: auto;
|
||||
overflow: hidden;
|
||||
margin: 0 0 6px;
|
||||
padding: 15px 15px 15px 50px;
|
||||
width: 300px;
|
||||
-moz-border-radius: 3px 3px 3px 3px;
|
||||
-webkit-border-radius: 3px 3px 3px 3px;
|
||||
border-radius: 3px 3px 3px 3px;
|
||||
background-position: 15px center;
|
||||
background-repeat: no-repeat;
|
||||
-moz-box-shadow: 0 0 12px #999999;
|
||||
-webkit-box-shadow: 0 0 12px #999999;
|
||||
box-shadow: 0 0 12px #999999;
|
||||
color: #FFFFFF;
|
||||
opacity: 0.8;
|
||||
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
|
||||
filter: alpha(opacity=80);
|
||||
}
|
||||
#toast-container > div.rtl {
|
||||
direction: rtl;
|
||||
padding: 15px 50px 15px 15px;
|
||||
background-position: right 15px center;
|
||||
}
|
||||
#toast-container > div:hover {
|
||||
-moz-box-shadow: 0 0 12px #000000;
|
||||
-webkit-box-shadow: 0 0 12px #000000;
|
||||
box-shadow: 0 0 12px #000000;
|
||||
opacity: 1;
|
||||
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
|
||||
filter: alpha(opacity=100);
|
||||
cursor: pointer;
|
||||
}
|
||||
#toast-container > .toast-info {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important;
|
||||
}
|
||||
#toast-container > .toast-error {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important;
|
||||
}
|
||||
#toast-container > .toast-success {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important;
|
||||
}
|
||||
#toast-container > .toast-warning {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important;
|
||||
}
|
||||
#toast-container.toast-top-center > div,
|
||||
#toast-container.toast-bottom-center > div {
|
||||
width: 300px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
#toast-container.toast-top-full-width > div,
|
||||
#toast-container.toast-bottom-full-width > div {
|
||||
width: 96%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.toast {
|
||||
background-color: #030303;
|
||||
}
|
||||
.toast-success {
|
||||
background-color: #51A351;
|
||||
}
|
||||
.toast-error {
|
||||
background-color: #BD362F;
|
||||
}
|
||||
.toast-info {
|
||||
background-color: #2F96B4;
|
||||
}
|
||||
.toast-warning {
|
||||
background-color: #F89406;
|
||||
}
|
||||
.toast-progress {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 4px;
|
||||
background-color: #000000;
|
||||
opacity: 0.4;
|
||||
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
|
||||
filter: alpha(opacity=40);
|
||||
}
|
||||
/*Responsive Design*/
|
||||
@media all and (max-width: 240px) {
|
||||
#toast-container > div {
|
||||
padding: 8px 8px 8px 50px;
|
||||
width: 11em;
|
||||
}
|
||||
#toast-container > div.rtl {
|
||||
padding: 8px 50px 8px 8px;
|
||||
}
|
||||
#toast-container .toast-close-button {
|
||||
right: -0.2em;
|
||||
top: -0.2em;
|
||||
}
|
||||
#toast-container .rtl .toast-close-button {
|
||||
left: -0.2em;
|
||||
right: 0.2em;
|
||||
}
|
||||
}
|
||||
@media all and (min-width: 241px) and (max-width: 480px) {
|
||||
#toast-container > div {
|
||||
padding: 8px 8px 8px 50px;
|
||||
width: 18em;
|
||||
}
|
||||
#toast-container > div.rtl {
|
||||
padding: 8px 50px 8px 8px;
|
||||
}
|
||||
#toast-container .toast-close-button {
|
||||
right: -0.2em;
|
||||
top: -0.2em;
|
||||
}
|
||||
#toast-container .rtl .toast-close-button {
|
||||
left: -0.2em;
|
||||
right: 0.2em;
|
||||
}
|
||||
}
|
||||
@media all and (min-width: 481px) and (max-width: 768px) {
|
||||
#toast-container > div {
|
||||
padding: 15px 15px 15px 50px;
|
||||
width: 25em;
|
||||
}
|
||||
#toast-container > div.rtl {
|
||||
padding: 15px 50px 15px 15px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
@import "mixins";
|
||||
@import "main";
|
||||
@import "dialog";
|
||||
@import "toastr";
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
Earth
|
||||
Water
|
||||
Wind
|
||||
Fire
|
||||
Thunder
|
||||
Ice
|
||||
Force
|
||||
Time
|
||||
Flower
|
||||
Shadow
|
||||
Light
|
||||
Darkness
|
||||
Metal
|
||||
Moon
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
Earth
|
||||
Water
|
||||
Wind
|
||||
Fire
|
||||
Thunder
|
||||
Ice
|
||||
Force
|
||||
Time
|
||||
Flower
|
||||
Shadow
|
||||
Light
|
||||
Darkness
|
||||
Metal
|
||||
Moon
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
red
|
||||
green
|
||||
blue
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
red
|
||||
green
|
||||
blue
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Brad Pitt
|
||||
George Clooney
|
||||
Jim Carrey
|
||||
Johnny Depp
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Brad Pitt
|
||||
George Clooney
|
||||
Jim Carrey
|
||||
Johnny Depp
|
||||
Loading…
Reference in New Issue