CV Tuple
How you feed a (width, height) to a node that only takes two numbers
- a
- b
- c
- d
- tuple
- array
- literal
Here's a problem only a pack of ~470 auto-generated cv2.* wrappers has: half of OpenCV takes composite values. dsize is a Size. ksize is a Size. patternSize is a Size. center is a Point. winSize is a Size. These aren't two numbers in OpenCV - they're one object - and a node with two loose widgets and no way to prove both were set is a bug generator.
CV Tuple authors that composite value once and emits it on one wire.
What it does
You set count and dtype, fill in a and b (and c, d if you asked for more), and get three views of the same thing:
- tuple - the whole composite as a
CV_TUPLEvalue. Wire this into any cv2 node's composite input:dsize,ksize,center,patternSize,winSize, whatever that wrapper exposes. Both components travel together, which makes a half-connected size structurally impossible. - array - the same numbers as a 1-D NPARRAY vector. For sockets that want an array rather than a composite:
cv2_inRangebounds, arithmetic operands. - literal - the
(a, b)string. Some params are still free-text literals and that's what they take; it's also what a STRING boundary on a subgraph can carry.
count is 2 for a Size or 2-D point, 3 for a 3-D point or an iteration-stop tuple, 4 for a Rect or Scalar. dtype is int for pixel sizes, kernel sizes and pixel coordinates - cv2 rejects a float Size - and float for subpixel coordinates and world units. A float array that should be an int Size is the mistake this widget exists to prevent; it's a runtime error somewhere deep in a wrapper otherwise.
The reason this belongs in the plumbing layer every graph is 70% made of: one source, many consumers. Set the size once, wire it to five nodes, change it in one place. The same argument as a PrimitiveInt, applied to a type ComfyUI's core primitives don't cover. Related: the sockets on a and b accept both INT and FLOAT links, so an image width from a cv2 wrapper and a subpixel coordinate both wire straight in.
The bit nobody expects
CV Tuple is also the way to set a composite input on a subgraph instance. When an input is promoted to a subgraph boundary it becomes a bare socket with no widget behind it - there's nothing to type a (w, h) into. Feed the boundary from a CV Tuple and the subgraph gets its size. That single fact is why the node exists at all rather than being an invisible implementation detail.
There's a reverse node, CV Split Tuple, for pulling a composite back apart into INT or FLOAT outputs - and note it takes the value as a socket, not a widget, because a widget there would just be CV Tuple again.
Install
Manager → ComfyUI CV, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Restart. Python ≥ 3.12, a recent ComfyUI built on the V3 node API (which is what makes typed sockets like CV_TUPLE possible in the first place), and opencv-contrib-python-headless~=5.0.0.93 - the contrib wheel, since stack order in one shared cv2 decides whether half this pack exists. No models. GPL-3.0, forked from opencv-comfyui.
Where people get burned
Getting dtype wrong. A float Size is rejected by cv2, and the error you get is somewhere several nodes downstream. If a resample, blur or pattern-detection node is complaining about a type, check the Tuple that fed it.
Expecting the order to be (height, width). It's (width, height) - a is described as "width / x", b as "height / y". The classic numpy/OpenCV shape confusion, except here it's a labelled widget, so read it.
Wiring literal where tuple belongs. They look similar and go to different sockets. If a param accepts the pipe-joined or paren-text form, literal is right; if it's a real composite socket, tuple is.
Assuming one Tuple can serve everything. It can serve any number of nodes that want the same value, which is the point. Two different sizes means two nodes.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| count | INT | 22–4 | How many components to emit: 2 for a Size / 2-D point, 3 for a 3-D point or an iteration-stop tuple, 4 for a Rect / Scalar. |
| dtype | COMBO | int | Component type. 'int' for pixel sizes, kernel sizes and pixel coordinates (cv2 rejects a float Size); 'float' for subpixel coordinates and world units. |
| a | FLOAT,INT | 0-1e+38–1e+38 | First component - width / x. The socket takes an INT or a FLOAT link, so an image width and a subpixel coordinate both wire in directly. |
| b | FLOAT,INT | 0-1e+38–1e+38 | Second component - height / y. Takes an INT or a FLOAT link. |
| copt | FLOAT,INT | 0-1e+38–1e+38 | Third component (used when count >= 3) - z / width. |
| dopt | FLOAT,INT | 0-1e+38–1e+38 | Fourth component (used when count = 4) - height. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| tuple | CV_TUPLE | The whole composite as one value - wire it into a cv2 node's composite input. |
| array | NPARRAY | The same components as a 1-D NPARRAY vector, for the sockets that take an array (cv2.inRange bounds, arithmetic operands). |
| literal | STRING | The '(a, b)' literal string, for the composite params that are still free-text (Scalar, RotatedRect, Moments) and for STRING subgraph boundaries. |