-
Notifications
You must be signed in to change notification settings - Fork 139
Description
https://docs.comfy.org/custom-nodes/backend/lists#list-processing
I have difficulty understanding this page
(please excuse the seemingly angry tone, I'm just explaining my thoughts :) )
Internally, the Comfy server represents data flowing from one node to the next as a Python list, normally length 1, of the relevant datatype. In normal operation, when a node returns an output, each element in the output tuple is separately wrapped in a list (length 1); then when the next node is called, the data is unwrapped and passed to the main function.
why are you explaining this? please state the goal. (the first sentence of the next section makes more sense at this point).
does this have anything to do with handling lists in nodes?
do I have to use this if I want to handle lists of ints?
do I have to use this if I want to handle image batches?
what is the "main function" in this case?
when I debug into the node's run function, all the types are scalars, not lists of length=1.
You generally don’t need to worry about this, since Comfy does the wrapping and unwrapping.
why is it explained then? please state the goal.
I don't need to worry as a node user, or not worry as a node developer?
In some circumstance, multiple data instances are processed in a single workflow, in which case the internal data will be a list containing the data instances.
what are "data instances" in this case?
An example of this might be processing a series of images one at a time to avoid running out of VRAM, or handling images of different sizes.
does this mean I cannot rely on the batch length of images in my node, because comfy rebatches them as it pleases?
does this have anything to do with execution inversion? (btw: execution inversion is not explained anywhere)
By default, Comfy will process the values in the list sequentially:
if the inputs are lists of different lengths, the shorter ones are padded by repeating the last value
the main method is called once for each value in the input lists
the outputs are lists, each of which is the same length as the longest input
I made two nodes "input: image0, image1, int", one time with and one time without INPUT_IS_LIST and found this not to be the case. if I hook it up with a image0 (batch_size=3) and image1 (batch_size=5), they still have batch_sizes 3 and 5 (as a single entry or a list len=1). If I use a node which constructs image lists (OUTPUT_IS_LIST), the list length are 3 and 5. no padding happens.
the padding seems useful for implementing image processing nodes which takes two images as inputs. say I want to implement a "overlay with image" node, my first image is a batch of 10 images, and my second image is just ONE logo, it would make sense to pad the shorter list (the logo in this case) to 10. does this have to do anything with this?
The relevant code can be found in the method map_node_over_list in execution.py.
if this is relevant to the explanation, please also insert the code, otherwise this should be a footnote.
However, as Comfy wraps node outputs into a list of length one, if the tuple returned by a custom node contains a list, that list will be wrapped, and treated as a single piece of data.
I'm lost.
INPUT_IS_LIST
The article seems to be cut off here
https://docs.comfy.org/custom-nodes/backend/server_overview#input-is-list%2C-output-is-list
INPUT_IS_LIST, OUTPUT_IS_LIST
These are used to control sequential processing of data, and are described later.
the link is broken
my tests:
class NoList:
@classmethod
def INPUT_TYPES(cls):
return {
'required': {
"in_image0" : ("IMAGE",),
"in_image1" : ("IMAGE",),
"in_int" : ("INT",),
},
}
RETURN_TYPES = ("IMAGE", "INT")
RETURN_NAMES = ("out_image", "out_int")
FUNCTION = 'execute'
CATEGORY = 'test'
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False, False)
def execute(self, in_image0, in_image1, in_int):
print(f'in_image0={type(in_image0)}[{len(in_image0)}] in_image1={type(in_image1)}[{len(in_image1)}]')
for i, img in enumerate(in_image0):
print(f'NoList {i}')
time.sleep(0.5)
return (in_image0, in_int)
NODE_DISPLAY_NAME_MAPPINGS ["NoList"] = "NoList"
NODE_CLASS_MAPPINGS ["NoList"] = NoList
class InputList:
@classmethod
def INPUT_TYPES(cls):
return {
'required': {
"in_image0" : ("IMAGE",),
"in_image1" : ("IMAGE",),
"in_int" : ("INT",),
},
}
RETURN_TYPES = ("IMAGE", "INT")
RETURN_NAMES = ("out_image", "out_int")
FUNCTION = 'execute'
CATEGORY = 'test'
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (False, False)
def execute(self, in_image0, in_image1, in_int):
print(f'in_image0={type(in_image0)}[{len(in_image0)}] in_image1={type(in_image1)}[{len(in_image1)}] in_int={type(in_int)}[{len(in_int)}]')
for i, img in enumerate(in_image0):
print(f'InputList {i}')
time.sleep(0.5)
return (in_image0[0], in_int)
NODE_DISPLAY_NAME_MAPPINGS ["InputList"] = "InputList"
NODE_CLASS_MAPPINGS ["InputList"] = InputList