ꘇ top.todat
Image pixels as a table you can point at
- image
- table
- text
ꘇ top.todat samples an IMAGE and gives you a table: one row per sampled pixel, with x, y, r, g, b, a as columns. Same bounded readback as ꘇ top.tochop, different output shape - a table you can address a single cell in, filter, transpose, or just read as CSV.
What it is
The DAT family in this pack is the "text and numbers you can inspect" side of the graph, and the author's own note is that these are explicit bridges between three worlds rather than one giant type system: images are IMAGE, signals are CHOP, tables and documents are DAT. This is the image→table bridge. Where ꘇ top.tochop is for driving things with pixels (channels that flow into a signal chain), this is for reading pixels: pulling one number out of a render, averaging a region, sanity-checking a mask, or wiring a specific pixel's brightness into a numeric widget through ꘇ dat.cell.
It's also the easiest conversion to see working, because the text output is plain CSV that a CodeMirror-style DAT view or a string consumer can show you immediately. If you've ever wanted to know what ComfyUI actually handed you at a given point in the graph, this is a legible answer.
How it works
The frame at batch_index is bilinearly resized to your requested sample size, given an alpha of 1 if it only had three channels, moved to the CPU, and walked row by row. For each pixel it writes a row: x, y, then the four channel values in 0..1. Because the loop is y-outer/x-inner, the table reads left to right, top to bottom - row 1 of the table (after the header) is pixel (0,0). Sample width and height are capped at 64 each, and the readback is rate-limited live with one read in flight, exactly like its CHOP sibling. On Queue it samples one explicit frame.
Two practical consequences of that shape. The first data row is below the header, so ꘇ dat.cell with row = 1 gets you pixel (0,0)'s x value, row = 2 gets pixel (1,0), and so on with column picking x/y/r/g/b/a by index (0..5). And the whole thing is regenerated per read: this is a snapshot table, not a live-updating grid. A 16×16 sample gives you 257 rows, which is fine to eyeball and fine to pass to ꘇ dat.expression, but it's not a substitute for a preview node.
The inputs and outputs that matter
- image - the IMAGE to sample.
- sample_width / sample_height - INT 1..64, default 16 each. Sample 1×1 if you only want one pixel: it makes the table three rows long and the indexing obvious.
- sample_rate - FLOAT 0.1..60, default
10. How often the live readback happens. - batch_index - INT 0..10000, default
0. Reading past the end of the batch raises "Batch index exceeds IMAGE batch". - table out -
GENERETI_DATwith headerx,y,r,g,b,aand one row per sampled pixel. - text out - the same table as CSV text. Wire it to any STRING input if you want to log it; it's the value a queued run can hand back to you.
Install
Every node on these pages ships in one pack. Manager → search genereti (title "ꘇ Genereti") → install → restart ComfyUI → hard-refresh the browser. Manual:
cd ComfyUI
git clone https://github.com/languel/genereti.git custom_nodes/genereti
./.venv/bin/python -m pip install -r custom_nodes/genereti/requirements.txt
Install with your ComfyUI's own Python. The requirements file is comfyui-frontend-package>=1.53.6 - one line, no models, no extra server, and nothing that installs over a dependency another pack depends on. ꘇ-OpenTouch-Operators-and-Lessons covers the conversion family; the lesson uses 8×8 samples at 2 Hz so conversions don't slow down the rest of the stage.
Common issues
"Batch index exceeds IMAGE batch." Your batch_index is larger than the batch that arrived. Default 0 is right unless you're deliberately probing a multi-frame source.
Row indices seem off by one. The header is row 0 and cells are zero-based. That convention matches ꘇ dat.cell, whose defaults (row 1, column 0) already assume a header.
You wanted a mask's area, and got a table. There's no aggregation here. ꘇ dat.expression evaluates per cell, so averaging or thresholding means writing that expression (or doing the maths behind ꘇ chop.select on the CHOP variant instead).
Alpha is flat 1.0. Three-channel images get an opaque alpha appended. If you need real transparency, it has to arrive as a four-channel image.
Sampling is deliberately small. 64×64 per axis maximum, one in-flight read, capped rate. This is a probe, not a video path - the docs are explicit that the conversions are boundaries, not lossless round trips.
Alpha pack. The README says it: super alpha, early classroom testing, expect bugs and changing interfaces. Back up workflows before updating.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| sample_width | INT | 161–64 | — |
| sample_height | INT | 161–64 | — |
| sample_rate | FLOAT | 10.000.1–60 | — |
| batch_index | INT | 00–10000 | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| table | GENERETI_DAT | — |
| text | STRING | — |