mirror of
https://git.datalinker.icu/kijai/ComfyUI-KJNodes.git
synced 2026-05-27 10:59:09 +08:00
Proper point sampling for the spline editor
This commit is contained in:
parent
0843356c78
commit
1bb4b9bd26
@ -93,6 +93,7 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
"MaskOrImageToWeight": MaskOrImageToWeight,
|
"MaskOrImageToWeight": MaskOrImageToWeight,
|
||||||
"WeightScheduleConvert": WeightScheduleConvert,
|
"WeightScheduleConvert": WeightScheduleConvert,
|
||||||
"FloatToMask": FloatToMask,
|
"FloatToMask": FloatToMask,
|
||||||
|
"FloatToSigmas": FloatToSigmas,
|
||||||
#experimental
|
#experimental
|
||||||
"StabilityAPI_SD3": StabilityAPI_SD3,
|
"StabilityAPI_SD3": StabilityAPI_SD3,
|
||||||
"SoundReactive": SoundReactive,
|
"SoundReactive": SoundReactive,
|
||||||
@ -189,6 +190,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
|||||||
"MaskOrImageToWeight": "Mask Or Image To Weight",
|
"MaskOrImageToWeight": "Mask Or Image To Weight",
|
||||||
"WeightScheduleConvert": "Weight Schedule Convert",
|
"WeightScheduleConvert": "Weight Schedule Convert",
|
||||||
"FloatToMask": "Float To Mask",
|
"FloatToMask": "Float To Mask",
|
||||||
|
"FloatToSigmas": "Float To Sigmas",
|
||||||
"CustomSigmas": "Custom Sigmas",
|
"CustomSigmas": "Custom Sigmas",
|
||||||
"ImagePass": "ImagePass",
|
"ImagePass": "ImagePass",
|
||||||
#curve nodes
|
#curve nodes
|
||||||
|
|||||||
@ -34,7 +34,6 @@ class SplineEditor:
|
|||||||
"float_output_type": (
|
"float_output_type": (
|
||||||
[
|
[
|
||||||
'list',
|
'list',
|
||||||
'list of lists',
|
|
||||||
'pandas series',
|
'pandas series',
|
||||||
'tensor',
|
'tensor',
|
||||||
],
|
],
|
||||||
@ -76,8 +75,6 @@ output types:
|
|||||||
example compatible nodes: anything that takes masks
|
example compatible nodes: anything that takes masks
|
||||||
- list of floats
|
- list of floats
|
||||||
example compatible nodes: IPAdapter weights
|
example compatible nodes: IPAdapter weights
|
||||||
- list of lists
|
|
||||||
example compatible nodes: unknown
|
|
||||||
- pandas series
|
- pandas series
|
||||||
example compatible nodes: anything that takes Fizz'
|
example compatible nodes: anything that takes Fizz'
|
||||||
nodes Batch Value Schedule
|
nodes Batch Value Schedule
|
||||||
@ -92,14 +89,13 @@ output types:
|
|||||||
for coord in coordinates:
|
for coord in coordinates:
|
||||||
coord['x'] = int(round(coord['x']))
|
coord['x'] = int(round(coord['x']))
|
||||||
coord['y'] = int(round(coord['y']))
|
coord['y'] = int(round(coord['y']))
|
||||||
|
|
||||||
normalized_y_values = [
|
normalized_y_values = [
|
||||||
(1.0 - (point['y'] / 512) - 0.0) * (max_value - min_value) + min_value
|
(1.0 - (point['y'] / 512) - 0.0) * (max_value - min_value) + min_value
|
||||||
for point in coordinates
|
for point in coordinates
|
||||||
]
|
]
|
||||||
if float_output_type == 'list':
|
if float_output_type == 'list':
|
||||||
out_floats = normalized_y_values * repeat_output
|
out_floats = normalized_y_values * repeat_output
|
||||||
elif float_output_type == 'list of lists':
|
|
||||||
out_floats = ([[value] for value in normalized_y_values] * repeat_output),
|
|
||||||
elif float_output_type == 'pandas series':
|
elif float_output_type == 'pandas series':
|
||||||
try:
|
try:
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@ -207,7 +203,6 @@ class MaskOrImageToWeight:
|
|||||||
"output_type": (
|
"output_type": (
|
||||||
[
|
[
|
||||||
'list',
|
'list',
|
||||||
'list of lists',
|
|
||||||
'pandas series',
|
'pandas series',
|
||||||
'tensor',
|
'tensor',
|
||||||
],
|
],
|
||||||
@ -243,8 +238,6 @@ and returns that as the selected output type.
|
|||||||
# Convert mean_values to the specified output_type
|
# Convert mean_values to the specified output_type
|
||||||
if output_type == 'list':
|
if output_type == 'list':
|
||||||
return mean_values,
|
return mean_values,
|
||||||
elif output_type == 'list of lists':
|
|
||||||
return [[value] for value in mean_values],
|
|
||||||
elif output_type == 'pandas series':
|
elif output_type == 'pandas series':
|
||||||
try:
|
try:
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@ -267,7 +260,6 @@ class WeightScheduleConvert:
|
|||||||
[
|
[
|
||||||
'match_input',
|
'match_input',
|
||||||
'list',
|
'list',
|
||||||
'list of lists',
|
|
||||||
'pandas series',
|
'pandas series',
|
||||||
'tensor',
|
'tensor',
|
||||||
],
|
],
|
||||||
@ -298,8 +290,6 @@ Converts different value lists/series to another type.
|
|||||||
return 'pandas series'
|
return 'pandas series'
|
||||||
elif isinstance(input_values, torch.Tensor):
|
elif isinstance(input_values, torch.Tensor):
|
||||||
return 'tensor'
|
return 'tensor'
|
||||||
elif isinstance(input_values, list) and all(isinstance(sub, list) for sub in input_values):
|
|
||||||
return 'list of lists'
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unsupported input type")
|
raise ValueError("Unsupported input type")
|
||||||
|
|
||||||
@ -307,9 +297,7 @@ Converts different value lists/series to another type.
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
input_type = self.detect_input_type(input_values)
|
input_type = self.detect_input_type(input_values)
|
||||||
|
|
||||||
if input_type == 'list of lists':
|
if input_type == 'pandas series':
|
||||||
float_values = [item for sublist in input_values for item in sublist]
|
|
||||||
elif input_type == 'pandas series':
|
|
||||||
float_values = input_values.tolist()
|
float_values = input_values.tolist()
|
||||||
elif input_type == 'tensor':
|
elif input_type == 'tensor':
|
||||||
float_values = input_values
|
float_values = input_values
|
||||||
@ -345,13 +333,13 @@ Converts different value lists/series to another type.
|
|||||||
|
|
||||||
if output_type == 'list':
|
if output_type == 'list':
|
||||||
return float_values,
|
return float_values,
|
||||||
elif output_type == 'list of lists':
|
|
||||||
return [[value] for value in float_values],
|
|
||||||
elif output_type == 'pandas series':
|
elif output_type == 'pandas series':
|
||||||
return pd.Series(float_values),
|
return pd.Series(float_values),
|
||||||
elif output_type == 'tensor':
|
elif output_type == 'tensor':
|
||||||
if input_type == 'pandas series':
|
if input_type == 'pandas series':
|
||||||
return torch.tensor(input_values.values, dtype=torch.float32),
|
return torch.tensor(float_values.values, dtype=torch.float32),
|
||||||
|
else:
|
||||||
|
return torch.tensor(float_values, dtype=torch.float32),
|
||||||
elif output_type == 'match_input':
|
elif output_type == 'match_input':
|
||||||
return float_values,
|
return float_values,
|
||||||
else:
|
else:
|
||||||
@ -408,7 +396,6 @@ class WeightScheduleExtend:
|
|||||||
[
|
[
|
||||||
'match_input',
|
'match_input',
|
||||||
'list',
|
'list',
|
||||||
'list of lists',
|
|
||||||
'pandas series',
|
'pandas series',
|
||||||
'tensor',
|
'tensor',
|
||||||
],
|
],
|
||||||
@ -433,8 +420,6 @@ Extends, and converts if needed, different value lists/series
|
|||||||
return 'pandas series'
|
return 'pandas series'
|
||||||
elif isinstance(input_values, torch.Tensor):
|
elif isinstance(input_values, torch.Tensor):
|
||||||
return 'tensor'
|
return 'tensor'
|
||||||
elif isinstance(input_values, list) and all(isinstance(sub, list) for sub in input_values):
|
|
||||||
return 'list of lists'
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unsupported input type")
|
raise ValueError("Unsupported input type")
|
||||||
|
|
||||||
@ -445,10 +430,7 @@ Extends, and converts if needed, different value lists/series
|
|||||||
# Convert input_values_2 to the same format as input_values_1 if they do not match
|
# Convert input_values_2 to the same format as input_values_1 if they do not match
|
||||||
if not input_type_1 == input_type_2:
|
if not input_type_1 == input_type_2:
|
||||||
print("Converting input_values_2 to the same format as input_values_1")
|
print("Converting input_values_2 to the same format as input_values_1")
|
||||||
if input_type_1 == 'list of lists':
|
if input_type_1 == 'pandas series':
|
||||||
# Assuming input_values_2 is a flat list, convert it to a list of lists
|
|
||||||
float_values_2 = [[item] for item in input_values_2]
|
|
||||||
elif input_type_1 == 'pandas series':
|
|
||||||
# Convert input_values_2 to a pandas Series
|
# Convert input_values_2 to a pandas Series
|
||||||
float_values_2 = pd.Series(input_values_2)
|
float_values_2 = pd.Series(input_values_2)
|
||||||
elif input_type_1 == 'tensor':
|
elif input_type_1 == 'tensor':
|
||||||
@ -463,14 +445,33 @@ Extends, and converts if needed, different value lists/series
|
|||||||
|
|
||||||
if output_type == 'list':
|
if output_type == 'list':
|
||||||
return float_values,
|
return float_values,
|
||||||
elif output_type == 'list of lists':
|
|
||||||
return [[value] for value in float_values],
|
|
||||||
elif output_type == 'pandas series':
|
elif output_type == 'pandas series':
|
||||||
return pd.Series(float_values),
|
return pd.Series(float_values),
|
||||||
elif output_type == 'tensor':
|
elif output_type == 'tensor':
|
||||||
if input_type_1 == 'pandas series':
|
if input_type_1 == 'pandas series':
|
||||||
return torch.tensor(input_values_1.values, dtype=torch.float32),
|
return torch.tensor(float_values.values, dtype=torch.float32),
|
||||||
|
else:
|
||||||
|
return torch.tensor(float_values, dtype=torch.float32),
|
||||||
elif output_type == 'match_input':
|
elif output_type == 'match_input':
|
||||||
return float_values,
|
return float_values,
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported output_type: {output_type}")
|
raise ValueError(f"Unsupported output_type: {output_type}")
|
||||||
|
|
||||||
|
class FloatToSigmas:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{
|
||||||
|
"float_list": ("FLOAT", {"default": 0.0, "forceInput": True}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RETURN_TYPES = ("SIGMAS",)
|
||||||
|
RETURN_NAMES = ("SIGMAS",)
|
||||||
|
CATEGORY = "KJNodes/noise"
|
||||||
|
FUNCTION = "customsigmas"
|
||||||
|
DESCRIPTION = """
|
||||||
|
Creates a sigmas tensor from list of float values.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def customsigmas(self, float_list):
|
||||||
|
return torch.tensor(float_list, dtype=torch.float32),
|
||||||
@ -148,23 +148,27 @@ app.registerExtension({
|
|||||||
this.menuItem2.textContent = "Display sample points";
|
this.menuItem2.textContent = "Display sample points";
|
||||||
styleMenuItem(this.menuItem2);
|
styleMenuItem(this.menuItem2);
|
||||||
|
|
||||||
// Add hover effect to menu items
|
this.menuItem3 = document.createElement("a");
|
||||||
this.menuItem1.addEventListener('mouseover', function() {
|
this.menuItem3.href = "#";
|
||||||
this.style.backgroundColor = "gray";
|
this.menuItem3.id = "menu-item-2";
|
||||||
|
this.menuItem3.textContent = "Switch sampling method";
|
||||||
|
styleMenuItem(this.menuItem3);
|
||||||
|
|
||||||
|
const menuItems = [this.menuItem1, this.menuItem2, this.menuItem3];
|
||||||
|
|
||||||
|
menuItems.forEach(menuItem => {
|
||||||
|
menuItem.addEventListener('mouseover', function() {
|
||||||
|
this.style.backgroundColor = "gray";
|
||||||
|
});
|
||||||
|
menuItem.addEventListener('mouseout', function() {
|
||||||
|
this.style.backgroundColor = "#202020";
|
||||||
});
|
});
|
||||||
this.menuItem1.addEventListener('mouseout', function() {
|
|
||||||
this.style.backgroundColor = "#202020";
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.menuItem2.addEventListener('mouseover', function() {
|
// Append menu items to the context menu
|
||||||
this.style.backgroundColor = "gray";
|
menuItems.forEach(menuItem => {
|
||||||
|
this.contextMenu.appendChild(menuItem);
|
||||||
});
|
});
|
||||||
this.menuItem2.addEventListener('mouseout', function() {
|
|
||||||
this.style.backgroundColor = "#202020";
|
|
||||||
});
|
|
||||||
|
|
||||||
this.contextMenu.appendChild(this.menuItem1);
|
|
||||||
this.contextMenu.appendChild(this.menuItem2);
|
|
||||||
|
|
||||||
document.body.appendChild( this.contextMenu);
|
document.body.appendChild( this.contextMenu);
|
||||||
|
|
||||||
@ -241,15 +245,26 @@ function createSplineEditor(context, reset=false) {
|
|||||||
context.menuItem2.addEventListener('click', function(e) {
|
context.menuItem2.addEventListener('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
drawSamplePoints = !drawSamplePoints;
|
drawSamplePoints = !drawSamplePoints;
|
||||||
|
|
||||||
updatePath();
|
updatePath();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context.menuItem3.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (pointSamplingMethod == samplePointsTime) {
|
||||||
|
pointSamplingMethod = samplePointsPath
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pointSamplingMethod = samplePointsTime
|
||||||
|
}
|
||||||
|
updatePath();
|
||||||
|
});
|
||||||
|
|
||||||
var drawSamplePoints = false;
|
var drawSamplePoints = false;
|
||||||
|
var pointSamplingMethod = samplePointsTime
|
||||||
|
|
||||||
function updatePath() {
|
function updatePath() {
|
||||||
points_to_sample = pointsWidget.value
|
points_to_sample = pointsWidget.value
|
||||||
let coords = samplePoints(pathElements[0], points_to_sample);
|
let coords = pointSamplingMethod(pathElements[0], points_to_sample);
|
||||||
if (drawSamplePoints) {
|
if (drawSamplePoints) {
|
||||||
if (pointsLayer) {
|
if (pointsLayer) {
|
||||||
// Update the data of the existing points layer
|
// Update the data of the existing points layer
|
||||||
@ -315,9 +330,11 @@ function createSplineEditor(context, reset=false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
minValueWidget.callback = () => {
|
minValueWidget.callback = () => {
|
||||||
|
rangeMin = minValueWidget.value
|
||||||
updatePath();
|
updatePath();
|
||||||
}
|
}
|
||||||
maxValueWidget.callback = () => {
|
maxValueWidget.callback = () => {
|
||||||
|
rangeMax = maxValueWidget.value
|
||||||
updatePath();
|
updatePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -444,7 +461,6 @@ function createSplineEditor(context, reset=false) {
|
|||||||
|
|
||||||
.font(12 + "px sans-serif")
|
.font(12 + "px sans-serif")
|
||||||
.text(d => {
|
.text(d => {
|
||||||
// Normalize y to range 0.0 to 1.0, considering the inverted y-axis
|
|
||||||
let normalizedY = (1.0 - (d.y / h) - 0.0) * (rangeMax - rangeMin) + rangeMin;
|
let normalizedY = (1.0 - (d.y / h) - 0.0) * (rangeMax - rangeMin) + rangeMin;
|
||||||
let normalizedX = (d.x / w);
|
let normalizedX = (d.x / w);
|
||||||
let frame = Math.round((d.x / w) * points_to_sample);
|
let frame = Math.round((d.x / w) * points_to_sample);
|
||||||
@ -461,13 +477,14 @@ function createSplineEditor(context, reset=false) {
|
|||||||
updatePath();
|
updatePath();
|
||||||
|
|
||||||
}
|
}
|
||||||
function samplePoints(svgPathElement, numSamples) {
|
function samplePointsPath(svgPathElement, numSamples) {
|
||||||
var pathLength = svgPathElement.getTotalLength();
|
var pathLength = svgPathElement.getTotalLength();
|
||||||
var points = [];
|
var points = [];
|
||||||
|
|
||||||
for (var i = 0; i < numSamples; i++) {
|
for (var i = 0; i < numSamples; i++) {
|
||||||
// Calculate the distance along the path for the current sample
|
// Calculate the distance along the path for the current sample
|
||||||
var distance = (pathLength / (numSamples - 1)) * i;
|
var distance = (pathLength / (numSamples - 1)) * i;
|
||||||
|
console.log(distance)
|
||||||
|
|
||||||
// Get the point at the current distance
|
// Get the point at the current distance
|
||||||
var point = svgPathElement.getPointAtLength(distance);
|
var point = svgPathElement.getPointAtLength(distance);
|
||||||
@ -475,10 +492,57 @@ function samplePoints(svgPathElement, numSamples) {
|
|||||||
// Add the point to the array of points
|
// Add the point to the array of points
|
||||||
points.push({ x: point.x, y: point.y });
|
points.push({ x: point.x, y: point.y });
|
||||||
}
|
}
|
||||||
//console.log(points);
|
console.log(points);
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function samplePointsTime(svgPathElement, numSamples) {
|
||||||
|
var svgWidth = 512; // Fixed width of the SVG element
|
||||||
|
var pathLength = svgPathElement.getTotalLength();
|
||||||
|
var points = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < numSamples; i++) {
|
||||||
|
// Calculate the x-coordinate for the current sample based on the SVG's width
|
||||||
|
var x = (svgWidth / (numSamples - 1)) * i;
|
||||||
|
|
||||||
|
// Find the point on the path that intersects the vertical line at the calculated x-coordinate
|
||||||
|
var point = findPointAtX(svgPathElement, x, pathLength);
|
||||||
|
|
||||||
|
// Add the point to the array of points
|
||||||
|
points.push({ x: point.x, y: point.y });
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPointAtX(svgPathElement, targetX, pathLength) {
|
||||||
|
let low = 0;
|
||||||
|
let high = pathLength;
|
||||||
|
let bestPoint = svgPathElement.getPointAtLength(0);
|
||||||
|
|
||||||
|
while (low <= high) {
|
||||||
|
let mid = low + (high - low) / 2;
|
||||||
|
let point = svgPathElement.getPointAtLength(mid);
|
||||||
|
|
||||||
|
if (Math.abs(point.x - targetX) < 1) {
|
||||||
|
return point; // The point is close enough to the target
|
||||||
|
}
|
||||||
|
|
||||||
|
if (point.x < targetX) {
|
||||||
|
low = mid + 1;
|
||||||
|
} else {
|
||||||
|
high = mid - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep track of the closest point found so far
|
||||||
|
if (Math.abs(point.x - targetX) < Math.abs(bestPoint.x - targetX)) {
|
||||||
|
bestPoint = point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the closest point found
|
||||||
|
return bestPoint;
|
||||||
|
}
|
||||||
|
|
||||||
//from melmass
|
//from melmass
|
||||||
export function hideWidgetForGood(node, widget, suffix = '') {
|
export function hideWidgetForGood(node, widget, suffix = '') {
|
||||||
widget.origType = widget.type
|
widget.origType = widget.type
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user