Merge branch 'comfyanonymous:master' into master

This commit is contained in:
sylym 2023-03-29 20:37:36 +08:00 committed by GitHub
commit e65b693545
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 439 additions and 38 deletions

View File

@ -244,7 +244,10 @@ def ddim_scheduler(model, steps):
sigs = [] sigs = []
ddim_timesteps = make_ddim_timesteps(ddim_discr_method="uniform", num_ddim_timesteps=steps, num_ddpm_timesteps=model.inner_model.inner_model.num_timesteps, verbose=False) ddim_timesteps = make_ddim_timesteps(ddim_discr_method="uniform", num_ddim_timesteps=steps, num_ddpm_timesteps=model.inner_model.inner_model.num_timesteps, verbose=False)
for x in range(len(ddim_timesteps) - 1, -1, -1): for x in range(len(ddim_timesteps) - 1, -1, -1):
sigs.append(model.t_to_sigma(torch.tensor(ddim_timesteps[x]))) ts = ddim_timesteps[x]
if ts > 999:
ts = 999
sigs.append(model.t_to_sigma(torch.tensor(ts)))
sigs += [0.0] sigs += [0.0]
return torch.FloatTensor(sigs) return torch.FloatTensor(sigs)
@ -375,7 +378,7 @@ class KSampler:
def set_steps(self, steps, denoise=None): def set_steps(self, steps, denoise=None):
self.steps = steps self.steps = steps
if denoise is None: if denoise is None or denoise > 0.9999:
self.sigmas = self._calculate_sigmas(steps) self.sigmas = self._calculate_sigmas(steps)
else: else:
new_steps = int(steps/denoise) new_steps = int(steps/denoise)

View File

@ -439,9 +439,14 @@ class VAE:
model_management.unload_model() model_management.unload_model()
self.first_stage_model = self.first_stage_model.to(self.device) self.first_stage_model = self.first_stage_model.to(self.device)
try: try:
samples = samples_in.to(self.device) free_memory = model_management.get_free_memory(self.device)
pixel_samples = self.first_stage_model.decode(1. / self.scale_factor * samples) batch_number = int((free_memory * 0.7) / (2562 * samples_in.shape[2] * samples_in.shape[3] * 64))
pixel_samples = torch.clamp((pixel_samples + 1.0) / 2.0, min=0.0, max=1.0) batch_number = max(1, batch_number)
pixel_samples = torch.empty((samples_in.shape[0], 3, round(samples_in.shape[2] * 8), round(samples_in.shape[3] * 8)), device="cpu")
for x in range(0, samples_in.shape[0], batch_number):
samples = samples_in[x:x+batch_number].to(self.device)
pixel_samples[x:x+batch_number] = torch.clamp((self.first_stage_model.decode(1. / self.scale_factor * samples) + 1.0) / 2.0, min=0.0, max=1.0).cpu()
except model_management.OOM_EXCEPTION as e: except model_management.OOM_EXCEPTION as e:
print("Warning: Ran out of memory when regular VAE decoding, retrying with tiled VAE decoding.") print("Warning: Ran out of memory when regular VAE decoding, retrying with tiled VAE decoding.")
pixel_samples = self.decode_tiled_(samples_in) pixel_samples = self.decode_tiled_(samples_in)

View File

@ -10,7 +10,7 @@ import gc
import torch import torch
import nodes import nodes
def get_input_data(inputs, class_def, outputs={}, prompt={}, extra_data={}): def get_input_data(inputs, class_def, unique_id, outputs={}, prompt={}, extra_data={}):
valid_inputs = class_def.INPUT_TYPES() valid_inputs = class_def.INPUT_TYPES()
input_data_all = {} input_data_all = {}
for x in inputs: for x in inputs:
@ -34,6 +34,8 @@ def get_input_data(inputs, class_def, outputs={}, prompt={}, extra_data={}):
if h[x] == "EXTRA_PNGINFO": if h[x] == "EXTRA_PNGINFO":
if "extra_pnginfo" in extra_data: if "extra_pnginfo" in extra_data:
input_data_all[x] = extra_data['extra_pnginfo'] input_data_all[x] = extra_data['extra_pnginfo']
if h[x] == "UNIQUE_ID":
input_data_all[x] = unique_id
return input_data_all return input_data_all
def recursive_execute(server, prompt, outputs, current_item, extra_data={}): def recursive_execute(server, prompt, outputs, current_item, extra_data={}):
@ -55,7 +57,7 @@ def recursive_execute(server, prompt, outputs, current_item, extra_data={}):
if input_unique_id not in outputs: if input_unique_id not in outputs:
executed += recursive_execute(server, prompt, outputs, input_unique_id, extra_data) executed += recursive_execute(server, prompt, outputs, input_unique_id, extra_data)
input_data_all = get_input_data(inputs, class_def, outputs, prompt, extra_data) input_data_all = get_input_data(inputs, class_def, unique_id, outputs, prompt, extra_data)
if server.client_id is not None: if server.client_id is not None:
server.last_node_id = unique_id server.last_node_id = unique_id
server.send_sync("executing", { "node": unique_id }, server.client_id) server.send_sync("executing", { "node": unique_id }, server.client_id)
@ -96,7 +98,7 @@ def recursive_output_delete_if_changed(prompt, old_prompt, outputs, current_item
if unique_id in old_prompt and 'is_changed' in old_prompt[unique_id]: if unique_id in old_prompt and 'is_changed' in old_prompt[unique_id]:
is_changed_old = old_prompt[unique_id]['is_changed'] is_changed_old = old_prompt[unique_id]['is_changed']
if 'is_changed' not in prompt[unique_id]: if 'is_changed' not in prompt[unique_id]:
input_data_all = get_input_data(inputs, class_def, outputs) input_data_all = get_input_data(inputs, class_def, unique_id, outputs)
if input_data_all is not None: if input_data_all is not None:
is_changed = class_def.IS_CHANGED(**input_data_all) is_changed = class_def.IS_CHANGED(**input_data_all)
prompt[unique_id]['is_changed'] = is_changed prompt[unique_id]['is_changed'] = is_changed

14
main.py
View File

@ -12,7 +12,7 @@ if os.name == "nt":
if __name__ == "__main__": if __name__ == "__main__":
if '--help' in sys.argv: if '--help' in sys.argv:
print("Valid Command line Arguments:") print("Valid Command line Arguments:")
print("\t--listen\t\t\tListen on 0.0.0.0 so the UI can be accessed from other computers.") print("\t--listen [ip]\t\t\tListen on ip or 0.0.0.0 if none given so the UI can be accessed from other computers.")
print("\t--port 8188\t\t\tSet the listen port.") print("\t--port 8188\t\t\tSet the listen port.")
print("\t--dont-upcast-attention\t\tDisable upcasting of attention \n\t\t\t\t\tcan boost speed but increase the chances of black images.\n") print("\t--dont-upcast-attention\t\tDisable upcasting of attention \n\t\t\t\t\tcan boost speed but increase the chances of black images.\n")
print("\t--use-split-cross-attention\tUse the split cross attention optimization instead of the sub-quadratic one.\n\t\t\t\t\tIgnored when xformers is used.") print("\t--use-split-cross-attention\tUse the split cross attention optimization instead of the sub-quadratic one.\n\t\t\t\t\tIgnored when xformers is used.")
@ -92,11 +92,19 @@ if __name__ == "__main__":
hijack_progress(server) hijack_progress(server)
threading.Thread(target=prompt_worker, daemon=True, args=(q,server,)).start() threading.Thread(target=prompt_worker, daemon=True, args=(q,server,)).start()
if '--listen' in sys.argv: try:
address = '0.0.0.0' address = '0.0.0.0'
else: p_index = sys.argv.index('--listen')
try:
ip = sys.argv[p_index + 1]
if ip[:2] != '--':
address = ip
except:
pass
except:
address = '127.0.0.1' address = '127.0.0.1'
dont_print = False dont_print = False
if '--dont-print-server' in sys.argv: if '--dont-print-server' in sys.argv:
dont_print = True dont_print = True

View File

@ -47,7 +47,7 @@
" !git pull\n", " !git pull\n",
"\n", "\n",
"!echo -= Install dependencies =-\n", "!echo -= Install dependencies =-\n",
"!pip -q install xformers -r requirements.txt" "!pip -q install xformers==0.0.16 -r requirements.txt"
] ]
}, },
{ {

View File

@ -0,0 +1,351 @@
import { app } from "/scripts/app.js";
import { $el } from "/scripts/ui.js";
import { api } from "/scripts/api.js";
// Manage color palettes
const colorPalettes = {
"palette_1": {
"id": "palette_1",
"name": "Palette 1",
"colors": {
"node_slot": {
"CLIP": "#FFD500", // bright yellow
"CLIP_VISION": "#A8DADC", // light blue-gray
"CLIP_VISION_OUTPUT": "#ad7452", // rusty brown-orange
"CONDITIONING": "#FFA931", // vibrant orange-yellow
"CONTROL_NET": "#6EE7B7", // soft mint green
"IMAGE": "#64B5F6", // bright sky blue
"LATENT": "#FF9CF9", // light pink-purple
"MASK": "#81C784", // muted green
"MODEL": "#B39DDB", // light lavender-purple
"STYLE_MODEL": "#C2FFAE", // light green-yellow
"VAE": "#FF6E6E", // bright red
}
}
},
"palette_2": {
"id": "palette_2",
"name": "Palette 2",
"colors": {
"node_slot": {
"CLIP": "#556B2F", // Dark Olive Green
"CLIP_VISION": "#4B0082", // Indigo
"CLIP_VISION_OUTPUT": "#006400", // Green
"CONDITIONING": "#FF1493", // Deep Pink
"CONTROL_NET": "#8B4513", // Saddle Brown
"IMAGE": "#8B0000", // Dark Red
"LATENT": "#00008B", // Dark Blue
"MASK": "#2F4F4F", // Dark Slate Grey
"MODEL": "#FF8C00", // Dark Orange
"STYLE_MODEL": "#004A4A", // Sherpa Blue
"UPSCALE_MODEL": "#4A004A", // Tyrian Purple
"VAE": "#4F394F", // Loulou
}
}
}
};
const id = "Comfy.ColorPalette";
const idCustomColorPalettes = "Comfy.CustomColorPalettes";
const defaultColorPaletteId = "palette_1";
const els = {}
// const ctxMenu = LiteGraph.ContextMenu;
app.registerExtension({
name: id,
init() {
const sortObjectKeys = (unordered) => {
return Object.keys(unordered).sort().reduce((obj, key) => {
obj[key] = unordered[key];
return obj;
}, {});
};
const getSlotTypes = async () => {
var types = [];
const defs = await api.getNodeDefs();
for (const nodeId in defs) {
const nodeData = defs[nodeId];
var inputs = nodeData["input"]["required"];
if (nodeData["input"]["optional"] != undefined){
inputs = Object.assign({}, nodeData["input"]["required"], nodeData["input"]["optional"])
}
for (const inputName in inputs) {
const inputData = inputs[inputName];
const type = inputData[0];
if (!Array.isArray(type)) {
types.push(type);
}
}
for (const o in nodeData["output"]) {
const output = nodeData["output"][o];
types.push(output);
}
}
return types;
};
const completeColorPalette = async (colorPalette) => {
var types = await getSlotTypes();
for (const type of types) {
if (!colorPalette.colors.node_slot[type]) {
colorPalette.colors.node_slot[type] = "";
}
}
colorPalette.colors.node_slot = sortObjectKeys(colorPalette.colors.node_slot);
return colorPalette;
};
const getColorPaletteTemplate = async () => {
let colorPalette = {
"id": "my_color_palette_unique_id",
"name": "My Color Palette",
"colors": {
"node_slot": {
}
}
};
return completeColorPalette(colorPalette);
};
const getCustomColorPalettes = () => {
return app.ui.settings.getSettingValue(idCustomColorPalettes, {});
};
const setCustomColorPalettes = (customColorPalettes) => {
return app.ui.settings.setSettingValue(idCustomColorPalettes, customColorPalettes);
};
const addCustomColorPalette = async (colorPalette) => {
if (typeof(colorPalette) !== "object") {
app.ui.dialog.show("Invalid color palette");
return;
}
if (!colorPalette.id) {
app.ui.dialog.show("Color palette missing id");
return;
}
if (!colorPalette.name) {
app.ui.dialog.show("Color palette missing name");
return;
}
if (!colorPalette.colors) {
app.ui.dialog.show("Color palette missing colors");
return;
}
if (colorPalette.colors.node_slot && typeof(colorPalette.colors.node_slot) !== "object") {
app.ui.dialog.show("Invalid color palette colors.node_slot");
return;
}
let customColorPalettes = getCustomColorPalettes();
customColorPalettes[colorPalette.id] = colorPalette;
setCustomColorPalettes(customColorPalettes);
for (const option of els.select.childNodes) {
if (option.value === "custom_" + colorPalette.id) {
els.select.removeChild(option);
}
}
els.select.append($el("option", { textContent: colorPalette.name + " (custom)", value: "custom_" + colorPalette.id, selected: true }));
setColorPalette("custom_" + colorPalette.id);
await loadColorPalette(colorPalette);
};
const deleteCustomColorPalette = async (colorPaletteId) => {
let customColorPalettes = getCustomColorPalettes();
delete customColorPalettes[colorPaletteId];
setCustomColorPalettes(customColorPalettes);
for (const option of els.select.childNodes) {
if (option.value === defaultColorPaletteId) {
option.selected = true;
}
if (option.value === "custom_" + colorPaletteId) {
els.select.removeChild(option);
}
}
setColorPalette(defaultColorPaletteId);
await loadColorPalette(getColorPalette());
};
const loadColorPalette = async (colorPalette) => {
colorPalette = await completeColorPalette(colorPalette);
if (colorPalette.colors) {
if (colorPalette.colors.node_slot) {
Object.assign(app.canvas.default_connection_color_byType, colorPalette.colors.node_slot);
app.canvas.draw(true, true);
}
}
};
const getColorPalette = (colorPaletteId) => {
if (!colorPaletteId) {
colorPaletteId = app.ui.settings.getSettingValue(id, defaultColorPaletteId);
}
if (colorPaletteId.startsWith("custom_")) {
colorPaletteId = colorPaletteId.substr(7);
let customColorPalettes = getCustomColorPalettes();
if (customColorPalettes[colorPaletteId]) {
return customColorPalettes[colorPaletteId];
}
}
return colorPalettes[colorPaletteId];
};
const setColorPalette = (colorPaletteId) => {
app.ui.settings.setSettingValue(id, colorPaletteId);
};
const fileInput = $el("input", {
type: "file",
accept: ".json",
style: { display: "none" },
parent: document.body,
onchange: () => {
let file = fileInput.files[0];
if (file.type === "application/json" || file.name.endsWith(".json")) {
const reader = new FileReader();
reader.onload = async () => {
await addCustomColorPalette(JSON.parse(reader.result));
};
reader.readAsText(file);
}
},
});
app.ui.settings.addSetting({
id,
name: "Color Palette",
type: (name, setter, value) => {
let options = [];
for (const c in colorPalettes) {
const colorPalette = colorPalettes[c];
options.push($el("option", { textContent: colorPalette.name, value: colorPalette.id, selected: colorPalette.id === value }));
}
let customColorPalettes = getCustomColorPalettes();
for (const c in customColorPalettes) {
const colorPalette = customColorPalettes[c];
options.push($el("option", { textContent: colorPalette.name + " (custom)", value: "custom_" + colorPalette.id, selected: "custom_" + colorPalette.id === value }));
}
return $el("div", [
$el("label", { textContent: name || id }, [
els.select = $el("select", {
onchange: (e) => {
setter(e.target.value);
}
}, options)
]),
$el("input", {
type: "button",
value: "Export",
onclick: async () => {
const colorPaletteId = app.ui.settings.getSettingValue(id, defaultColorPaletteId);
const colorPalette = await completeColorPalette(getColorPalette(colorPaletteId));
const json = JSON.stringify(colorPalette, null, 2); // convert the data to a JSON string
const blob = new Blob([json], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = $el("a", {
href: url,
download: colorPaletteId + ".json",
style: { display: "none" },
parent: document.body,
});
a.click();
setTimeout(function () {
a.remove();
window.URL.revokeObjectURL(url);
}, 0);
},
}),
$el("input", {
type: "button",
value: "Import",
onclick: () => {
fileInput.click();
}
}),
$el("input", {
type: "button",
value: "Template",
onclick: async () => {
const colorPalette = await getColorPaletteTemplate();
const json = JSON.stringify(colorPalette, null, 2); // convert the data to a JSON string
const blob = new Blob([json], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = $el("a", {
href: url,
download: "color_palette.json",
style: { display: "none" },
parent: document.body,
});
a.click();
setTimeout(function () {
a.remove();
window.URL.revokeObjectURL(url);
}, 0);
}
}),
$el("input", {
type: "button",
value: "Delete",
onclick: async () => {
let colorPaletteId = app.ui.settings.getSettingValue(id, defaultColorPaletteId);
if (colorPalettes[colorPaletteId]) {
app.ui.dialog.show("You cannot delete built-in color palette");
return;
}
if (colorPaletteId.startsWith("custom_")) {
colorPaletteId = colorPaletteId.substr(7);
}
await deleteCustomColorPalette(colorPaletteId);
}
}),
]);
},
defaultValue: defaultColorPaletteId,
async onChange(value) {
if (!value) {
return;
}
if (colorPalettes[value]) {
await loadColorPalette(colorPalettes[value]);
} else if (value.startsWith("custom_")) {
value = value.substr(7);
let customColorPalettes = getCustomColorPalettes();
if (customColorPalettes[value]) {
await loadColorPalette(customColorPalettes[value]);
}
}
},
});
},
});

View File

@ -576,27 +576,6 @@ class ComfyApp {
} }
} }
/**
* Setup slot colors for types
*/
setupSlotColors() {
let colors = {
"CLIP": "#FFD500", // bright yellow
"CLIP_VISION": "#A8DADC", // light blue-gray
"CLIP_VISION_OUTPUT": "#ad7452", // rusty brown-orange
"CONDITIONING": "#FFA931", // vibrant orange-yellow
"CONTROL_NET": "#6EE7B7", // soft mint green
"IMAGE": "#64B5F6", // bright sky blue
"LATENT": "#FF9CF9", // light pink-purple
"MASK": "#81C784", // muted green
"MODEL": "#B39DDB", // light lavender-purple
"STYLE_MODEL": "#C2FFAE", // light green-yellow
"VAE": "#FF6E6E", // bright red
};
Object.assign(this.canvas.default_connection_color_byType, colors);
}
/** /**
* Set up the app on the page * Set up the app on the page
*/ */
@ -614,8 +593,6 @@ class ComfyApp {
const canvas = (this.canvas = new LGraphCanvas(canvasEl, this.graph)); const canvas = (this.canvas = new LGraphCanvas(canvasEl, this.graph));
this.ctx = canvasEl.getContext("2d"); this.ctx = canvasEl.getContext("2d");
this.setupSlotColors();
this.graph.start(); this.graph.start();
function resizeCanvas() { function resizeCanvas() {
@ -744,6 +721,8 @@ class ComfyApp {
* @param {*} graphData A serialized graph object * @param {*} graphData A serialized graph object
*/ */
loadGraphData(graphData) { loadGraphData(graphData) {
this.clean();
if (!graphData) { if (!graphData) {
graphData = defaultGraph; graphData = defaultGraph;
} }
@ -901,6 +880,38 @@ class ComfyApp {
} }
this.extensions.push(extension); this.extensions.push(extension);
} }
/**
* Refresh combo list on whole nodes
*/
async refreshComboInNodes() {
const defs = await api.getNodeDefs();
for(let nodeNum in this.graph._nodes) {
const node = this.graph._nodes[nodeNum];
const def = defs[node.type];
for(const widgetNum in node.widgets) {
const widget = node.widgets[widgetNum]
if(widget.type == "combo" && def["input"]["required"][widget.name] !== undefined) {
widget.options.values = def["input"]["required"][widget.name][0];
if(!widget.options.values.includes(widget.value)) {
widget.value = widget.options.values[0];
}
}
}
}
}
/**
* Clean current state
*/
clean() {
this.nodeOutputs = {};
}
} }
export const app = new ComfyApp(); export const app = new ComfyApp();

View File

@ -1,6 +1,6 @@
import { api } from "./api.js"; import { api } from "./api.js";
function $el(tag, propsOrChildren, children) { export function $el(tag, propsOrChildren, children) {
const split = tag.split("."); const split = tag.split(".");
const element = document.createElement(split.shift()); const element = document.createElement(split.shift());
element.classList.add(...split); element.classList.add(...split);
@ -114,6 +114,17 @@ class ComfySettingsDialog extends ComfyDialog {
this.settings = []; this.settings = [];
} }
getSettingValue(id, defaultValue) {
const settingId = "Comfy.Settings." + id;
const v = localStorage[settingId];
return v == null ? defaultValue : JSON.parse(v);
}
setSettingValue(id, value) {
const settingId = "Comfy.Settings." + id;
localStorage[settingId] = JSON.stringify(value);
}
addSetting({ id, name, type, defaultValue, onChange }) { addSetting({ id, name, type, defaultValue, onChange }) {
if (!id) { if (!id) {
throw new Error("Settings must have an ID"); throw new Error("Settings must have an ID");
@ -142,7 +153,7 @@ class ComfySettingsDialog extends ComfyDialog {
}; };
if (typeof type === "function") { if (typeof type === "function") {
return type(name, setter); return type(name, setter, value);
} }
switch (type) { switch (type) {
@ -376,7 +387,11 @@ export class ComfyUI {
}, },
}), }),
$el("button", { textContent: "Load", onclick: () => fileInput.click() }), $el("button", { textContent: "Load", onclick: () => fileInput.click() }),
$el("button", { textContent: "Clear", onclick: () => app.graph.clear() }), $el("button", { textContent: "Refresh", onclick: () => app.refreshComboInNodes() }),
$el("button", { textContent: "Clear", onclick: () => {
app.clean();
app.graph.clear();
}}),
$el("button", { textContent: "Load Default", onclick: () => app.loadGraphData() }), $el("button", { textContent: "Load Default", onclick: () => app.loadGraphData() }),
]); ]);

View File

@ -64,6 +64,12 @@ body {
margin-bottom: 20px; /* Add some margin between the text and the close button*/ margin-bottom: 20px; /* Add some margin between the text and the close button*/
} }
.comfy-modal select,
.comfy-modal input[type=button],
.comfy-modal input[type=checkbox] {
margin: 3px 3px 3px 4px;
}
.comfy-modal button { .comfy-modal button {
cursor: pointer; cursor: pointer;
color: #aaaaaa; color: #aaaaaa;