image look up

+examples
This commit is contained in:
siraxe
2025-10-13 04:52:07 +01:00
parent edaa843d7d
commit 6f35b540d7
6 changed files with 5085 additions and 4981 deletions
+2 -1
View File
@@ -4,4 +4,5 @@ nodes/__pycache__/
.env
.env2
.env 22
wanvideo_cached_samples.pt
wanvideo_cached_samples.pt
ref_image.jpg
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because one or more lines are too long
+45 -14
View File
@@ -48,9 +48,6 @@ Locations are center locations. Allows coordinates outside the frame for 'fly-in
),
"bg_image": ("IMAGE",),
"coordinates": ("STRING", {"forceInput": True}),
"easing_function": (["linear", "in", "out", "in_out", "out_in"], {"default": "in_out"}),
"easing_path": (["each", "full"], {"default": "full"}),
"easing_strength": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 10.0, "step": 0.05}),
"shape_width": ("INT", {"default": 40, "min": 2, "max": 1000, "step": 5}),
"shape_height": ("INT", {"default": 40, "min": 2, "max": 1000, "step": 5}),
"shape_color": ("STRING", {"default": 'white'}),
@@ -82,7 +79,8 @@ Locations are center locations. Allows coordinates outside the frame for 'fly-in
p_driver_path: Optional[Path] = None,
p_coords_pause_frames: Tuple[int, int] = (0, 0),
coords_driver_info_list: Optional[List[Optional[Dict[str, Any]]]] = None,
scales_list: Optional[List[float]] = None) -> Image.Image:
scales_list: Optional[List[float]] = None,
static_points_scale: float = 1.0) -> Image.Image:
"""
Draw one frame using PIL.
This function is thread-safe and used by ThreadPoolExecutor in drawshapemask.
@@ -121,6 +119,10 @@ Locations are center locations. Allows coordinates outside the frame for 'fly-in
driver_offset_x = current_x - ref_x
driver_offset_y = current_y - ref_y
# Apply scale to shape dimensions for static points using the dedicated parameter
static_width = float(shape_width) * static_points_scale
static_height = float(shape_height) * static_points_scale
# Draw each static point with driver offset applied
for point in static_points:
try:
@@ -130,8 +132,8 @@ Locations are center locations. Allows coordinates outside the frame for 'fly-in
continue
if shape in ('circle', 'square'):
left_up_point = (location_x - current_width / 2.0, location_y - current_height / 2.0)
right_down_point = (location_x + current_width / 2.0, location_y + current_height / 2.0)
left_up_point = (location_x - static_width / 2.0, location_y - static_height / 2.0)
right_down_point = (location_x + static_width / 2.0, location_y + static_height / 2.0)
two_points = [left_up_point, right_down_point]
if shape == 'circle':
@@ -146,9 +148,9 @@ Locations are center locations. Allows coordinates outside the frame for 'fly-in
draw.rectangle(two_points, fill=shape_color)
elif shape == 'triangle':
left = (location_x - current_width / 2.0, location_y + current_height / 2.0)
right = (location_x + current_width / 2.0, location_y + current_height / 2.0)
top = (location_x, location_y - current_height / 2.0)
left = (location_x - static_width / 2.0, location_y + static_height / 2.0)
right = (location_x + static_width / 2.0, location_y + static_height / 2.0)
top = (location_x, location_y - static_height / 2.0)
poly_points = [top, left, right]
if border_width > 0:
try:
@@ -871,7 +873,7 @@ Locations are center locations. Allows coordinates outside the frame for 'fly-in
# ----------------------------
# Main Node Method
# ----------------------------
def drawshapemask(self, coordinates, bg_image, easing_function, easing_path, easing_strength,
def drawshapemask(self, coordinates, bg_image,
shape_width, shape_height, shape_color, bg_color, blur_radius, shape, intensity,
trailing=1.0, border_width=0, border_color='black', frames=None, preview_enabled=True):
"""
@@ -919,7 +921,10 @@ Locations are center locations. Allows coordinates outside the frame for 'fly-in
static_points, p_driver_path_processed = self._scale_points_and_drivers(static_points, p_driver_path_raw, coord_width, coord_height, frame_width, frame_height)
# Interpolate p_driver_path to total_frames and apply smoothing
# For p_coordinates, use the global parameters passed to the node
# For p_coordinates, use the easing parameters from metadata
easing_function = meta.get("easing_functions", "in_out")
easing_path = meta.get("easing_paths", "full")
easing_strength = meta.get("easing_strengths", 1.0)
if p_coords_use_driver and p_driver_path_processed:
p_driver_path_processed = self._process_p_driver_path(p_driver_path_processed, total_frames, p_driver_smooth, easing_function, easing_path, easing_strength)
@@ -999,9 +1004,9 @@ Locations are center locations. Allows coordinates outside the frame for 'fly-in
# ----- Build interpolated/resampled animated paths -----
# Handle different formats for easing parameters - could be single values, lists, or objects
easing_functions_meta = meta.get("easing_functions", easing_function)
easing_paths_meta = meta.get("easing_paths", easing_path)
easing_strengths_meta = meta.get("easing_strengths", easing_strength)
easing_functions_meta = meta.get("easing_functions", "in_out")
easing_paths_meta = meta.get("easing_paths", "full")
easing_strengths_meta = meta.get("easing_strengths", 1.0)
processed_coords_list, path_pause_frames, coords_driver_info_list, scales_list = self._build_interpolated_paths(
coords_list_raw, total_frames,
@@ -1014,6 +1019,23 @@ Locations are center locations. Allows coordinates outside the frame for 'fly-in
meta.get("scales", 1.0),
coord_width, coord_height, frame_width, frame_height
)
# Extract scale for static points (p_coordinates) from scales metadata
scales_meta = meta.get("scales", 1.0)
static_points_scale = 1.0
if isinstance(scales_meta, dict):
# New format: {"p": [...], "c": [...]} where "p" is for static points
p_scales = scales_meta.get("p", [1.0])
if isinstance(p_scales, list) and len(p_scales) > 0:
static_points_scale = float(p_scales[0])
elif isinstance(p_scales, (int, float)):
static_points_scale = float(p_scales)
elif isinstance(scales_meta, (int, float)):
# Single value for both static and animated (fallback)
static_points_scale = float(scales_meta)
elif isinstance(scales_meta, list):
# List format: first value for static points
static_points_scale = float(scales_meta[0]) if scales_meta else 1.0
# Special cases: no animated coords but static points exist -> set batch size accordingly
if not processed_coords_list:
@@ -1072,6 +1094,15 @@ Locations are center locations. Allows coordinates outside the frame for 'fly-in
))
try:
# Update args_list to include the static_points_scale parameter
updated_args_list = []
for args in args_list:
# args is a tuple that includes all the parameters for _draw_single_frame_pil
# We need to add static_points_scale to the end
updated_args = args + (static_points_scale,)
updated_args_list.append(updated_args)
args_list = updated_args_list
with concurrent.futures.ThreadPoolExecutor() as executor:
results = list(executor.map(lambda p: self._draw_single_frame_pil(*p), args_list))
pil_images = results
+196 -6
View File
@@ -190,6 +190,19 @@ function findConnectedSourceNode(currentNode, inputName) {
target_slot: link.target_slot
});
// If the directly connected source node is not an image node, try the deep search approach
if (!isImageNode(sourceNode)) {
console.log('Direct source node is not an image node, trying deep search...');
const deepResult = findDeepSourceNode(currentNode, inputName);
if (deepResult) {
console.log('Deep search found an image node:', {
sourceNodeId: deepResult.node.id,
sourceNodeType: deepResult.node.type
});
return deepResult;
}
}
return {
node: sourceNode,
origin_slot: link.origin_slot,
@@ -319,11 +332,40 @@ async function extractImageFromSourceNode(sourceNodeObj) {
}
break;
case 'ImageResizeKJv2': // Handle image processing nodes
case 'ImageScale':
case 'ImageScaleBy':
case 'ImageUpscale':
case 'ImageCrop':
// These nodes might pass through image data from their inputs
// Look for image widgets that might contain processed image data
if (sourceNode.widgets) {
for (const widget of sourceNode.widgets) {
if ((widget.type === 'image' || widget.name?.includes('image')) && widget.value) {
console.log(`Found image in ${sourceNode.type} widget:`, widget.value);
if (typeof widget.value === 'string') {
if (widget.value.startsWith('data:') || widget.value.startsWith('http')) {
return widget.value;
}
return `/view?filename=${encodeURIComponent(widget.value)}&type=temp`;
} else if (typeof widget.value === 'object') {
if (widget.value.filename) {
return `/view?filename=${encodeURIComponent(widget.value.filename)}&type=temp`;
}
}
}
}
}
// Even if no direct image found in widgets, the node might still be connected
// to image data that can be accessed through other means
break;
default:
// For other node types, try to find image-related widgets or properties
if (sourceNode.widgets) {
for (const widget of sourceNode.widgets) {
if ((widget.type === 'image' || widget.name.includes('image') ||
if ((widget.type === 'image' || widget.name?.includes('image') ||
widget.type === 'img' || widget.type === 'file') && widget.value) {
console.log(`Found potential image in ${sourceNode.type} widget:`, widget.value);
@@ -428,16 +470,44 @@ async function getReferenceImageFromConnectedNode(currentNode) {
console.log('Starting reference image query for node:', currentNode);
// Step 1: Find the connected source node for ref_image input
const sourceNodeObj = findConnectedSourceNode(currentNode, 'ref_image');
let sourceNodeObj = findConnectedSourceNode(currentNode, 'ref_image');
if (!sourceNodeObj) {
console.log('No source node found for ref_image input');
console.log('No source node found for ref_image input, trying deep search...');
// Try deep search as fallback
sourceNodeObj = findDeepSourceNode(currentNode, 'ref_image');
if (sourceNodeObj) {
console.log('Deep search found a potential image source node:', {
sourceNodeId: sourceNodeObj.node.id,
sourceNodeType: sourceNodeObj.node.type
});
}
}
if (!sourceNodeObj) {
console.log('No source node found for ref_image input after deep search');
return null;
}
// Step 2: Extract image data from the source node
const imageDataUrl = await extractImageFromSourceNode(sourceNodeObj);
let imageDataUrl = await extractImageFromSourceNode(sourceNodeObj);
if (!imageDataUrl) {
console.log('No image data found in source node');
console.log('No image data found in source node, trying deep search for original image source...');
// If we couldn't extract image from the found source node, try to find the original image source via deep search
const deepSourceNodeObj = findDeepSourceNode(currentNode, 'ref_image');
if (deepSourceNodeObj && deepSourceNodeObj.node.id !== sourceNodeObj.node.id) {
console.log('Deep search found alternative source node:', {
sourceNodeId: deepSourceNodeObj.node.id,
sourceNodeType: deepSourceNodeObj.node.type
});
imageDataUrl = await extractImageFromSourceNode(deepSourceNodeObj);
if (imageDataUrl) {
sourceNodeObj = deepSourceNodeObj; // Update the source node for potential logging
}
}
}
if (!imageDataUrl) {
console.log('No image data found in source node after trying alternatives');
return null;
}
@@ -452,5 +522,125 @@ async function getReferenceImageFromConnectedNode(currentNode) {
return base64Image;
}
/**
* Recursively find the original source node connected to a specific input
* @param {Object} currentNode - The node to start from
* @param {string} inputName - Name of the input to trace (e.g., "ref_image")
* @param {Set<number>} visited - Internal set to prevent infinite loops
* @returns {Object|null} The ultimate source node or null
*/
function findDeepSourceNode(currentNode, inputName, visited = new Set()) {
const graph = app.graph;
if (!graph || !graph.links) return null;
if (visited.has(currentNode.id)) return null;
visited.add(currentNode.id);
// Get the link object for the input
const inputIndex = currentNode.inputs?.findIndex(i => i.name === inputName) ?? -1;
if (inputIndex < 0) return null;
let link = null;
// Find the link connected to this input (graph.links is a Map, not an array)
if (graph.links && graph.links instanceof Map) {
for (const [linkId, linkObj] of graph.links) {
if (!linkObj) continue;
if (linkObj.target_id === currentNode.id && linkObj.target_slot === inputIndex) {
link = linkObj;
break;
}
}
} else if (Array.isArray(graph.links)) {
// Fallback if links is an array (older format)
link = graph.links.find(linkObj => {
if (!linkObj) return false;
return linkObj.target_id === currentNode.id && linkObj.target_slot === inputIndex;
});
}
if (!link) return null;
const sourceNode = graph._nodes?.find(node => node.id === link.origin_id);
if (!sourceNode) return null;
// Check if the source node contains image data
if (isImageNode(sourceNode)) {
// Prioritize true image loaders over processors
const imageLoaderTypes = ['LoadImage', 'LoadImageUpload'];
if (imageLoaderTypes.some(type => sourceNode.type?.includes(type))) {
return {
node: sourceNode,
origin_slot: link.origin_slot,
target_slot: link.target_slot
};
}
// If this is an image processor, continue looking upstream for the original source
if (sourceNode.inputs) {
for (const input of sourceNode.inputs) {
const deeper = findDeepSourceNode(sourceNode, input.name, new Set(visited)); // Use a copy of visited set for each branch
if (deeper) {
return deeper; // Return the original source found upstream
}
}
}
// If no original source found upstream, return this processor as fallback
return {
node: sourceNode,
origin_slot: link.origin_slot,
target_slot: link.target_slot
};
}
// If not an image node, recursively check each input of the source node for possible image connections
if (sourceNode.inputs) {
for (const input of sourceNode.inputs) {
const deeper = findDeepSourceNode(sourceNode, input.name, new Set(visited)); // Use a copy of visited set for each branch
if (deeper) return deeper;
}
}
return null;
}
/**
* Check if a node is likely to contain image data
* @param {Object} node - The node to check
* @returns {boolean} Whether the node is likely to contain image data
*/
function isImageNode(node) {
// Check node type for common image-related nodes
const imageNodeTypes = [
'LoadImage', 'LoadImageUpload', 'VAEDecode', 'PreviewImage', 'SaveImage',
'ImageScale', 'ImageScaleBy', 'ImageResizeKJv2', 'ImageUpscale', 'ImageCrop',
'ImagePadForOutpaint', 'ImageBatch', 'ImageBlend', 'ImageBlur', 'ImageColorToBW',
'ImageFlip', 'ImageOnlyCheckpointLoader', 'ImageApplyProcessing'
];
if (imageNodeTypes.some(type => node.type && node.type.includes(type))) {
return true;
}
// Check if node has IMAGE type outputs
if (node.outputs) {
for (const output of node.outputs) {
if (output.type === 'IMAGE' || (output.name && (output.name.includes('IMAGE') || output.name.toLowerCase().includes('image')))) {
return true;
}
}
}
// Check if node has image-related widgets
if (node.widgets) {
for (const widget of node.widgets) {
if (widget.type === 'image' || (widget.name && widget.name.toLowerCase().includes('image'))) {
return true;
}
}
}
return false;
}
// Export functions for use in other modules
export { findConnectedSourceNode, extractImageFromSourceNode, loadImageAsBase64, getReferenceImageFromConnectedNode };
export { findConnectedSourceNode, findDeepSourceNode, extractImageFromSourceNode, loadImageAsBase64, getReferenceImageFromConnectedNode };