Pictures and plots

A node can pass a picture or a list of numbers to another node, and a card can show them. Two port types do this: Image and Series. Both ports link only to ports of their own kind, so a picture cannot be wired into a number input, and the editor refuses the link and says why.

A picture

An Image is a grey or colour picture. channels is 1 for grey and 3 for red, green and blue, and pixels holds width * height * channels bytes, row by row from the top. The largest picture is 256 by 256.

import element_51


@element_51.node
def ramp(side: int = 28) -> element_51.Image:
    row = bytes(column * 255 // max(side - 1, 1) for column in range(side))
    return element_51.Image(
        width=side, height=side, channels=1, pixels=row * side
    )

After a run the card of ramp shows the picture below its ports, with crisp pixels, and its output box says 28 x 28 grey. A node that takes a picture (def invert(source: element_51.Image) -> element_51.Image) has no value box for that input, because a picture cannot be typed.

A list of numbers

A Series is a list of numbers, such as a loss for each training step. A NaN in it is a gap, not an error: the line stops there and starts again at the next number. A series holds at most 100,000 numbers. A node with several series outputs returns a pydantic model, as for any node with several outputs:

class Curves(element_51.Model):
    loss: element_51.Series
    accuracy: element_51.Series


@element_51.node
def training(steps: int = 60) -> Curves: ...

The output box of a series shows the last number it holds, such as 0.0842; hover over the box to read what it is (last of 282 points). A linked input shows the same number, and the name of the output it is linked to after it, such as 0.0842 [n4.loss].

Plot

element_51.PlotNode draws the series linked to it as lines on its card. Register the class itself:

app = element_51.App([ramp, training, element_51.PlotNode])

The card starts with one input, series_1. Every series you link adds a row, and a new empty row waits below for the next one, so a plot of three lines has series_1 to series_3 and an empty series_4. Removing a link removes its row and the card shrinks. A row keeps its name, so series_1 and series_3 can stay when series_2 goes.

Each line has the colour of its port type and is named after the output port it comes from, with its last value at its end. Two lines with the same name get their node id in front (n4 loss), and from the sixth line on, the lines are dashed, so colour is never the only way to tell them apart. A long series is thinned to about twice the width of the chart in pixels, keeping the lowest and highest number of each part, so a spike is never lost.

Resizing a card

A card that shows a picture or a chart has a grip in its bottom right corner (three short diagonal lines). Drag it to make the card wider and the picture or chart taller; the boxes on the card widen with it. A card never gets smaller than its default size. The size is part of the graph: Python sees it in app.graph, app.save writes it, a reload and a loaded file bring it back, and Ctrl+Z undoes a resize. A saved file with a card size is format version 3; version 1 and 2 files still load (version 4 adds series colours; all earlier versions still load).

Save as CSV

The card menu of a Plot card also has Save as CSV, which downloads the numbers of the chart as n3-chart.csv: an index column, then one column per line, named as the chart’s legend names it (loss, or n3 loss when two lines share a name), and one row per point. A gap, a number that is not finite, and the end of a shorter line are empty cells. Numbers are written with all their digits. Before a run there is nothing to save, and the page says so with a message you can dismiss.

Line colours

Beside each series_N name of a Plot card is a small colour box in the colour of that line. Click it and pick a colour: the line and its legend entry change. A small x then appears beside the box, and pressing it gives the line the theme’s colour again. A colour that is hard to see on the chart (below 3:1 against its background) is drawn as you chose it, and the box gets a red outline with the ratio as its hover text. The colour belongs to the series input: Python holds it (app.graph, SetColour), Ctrl+Z undoes it, and a saved file keeps it (format version 4). examples/d_editor/d7_series_colours.py sets two colours from the script.

Save as PNG

The card menu (the three bars on a card, or Shift+F10 with the node focused) has Save as PNG on a card that shows a picture or a chart. The page asks Python for the file, so the editor must be connected, and the browser downloads n1-out.png for a picture and n5-chart.png for a chart. The first chart takes a second longer, while matplotlib loads.

A chart is drawn by matplotlib: 2100 by 1400 pixels (7 by 4 2/3 inches at 300 dpi) on a white page, a light grid, the lines in the colours of the card (a colour you picked is used exactly; a theme colour that is too pale for white is darkened, keeping its hue, to reach 3 to 1), a legend when there is more than one line, and the line’s name as the y label when there is only one. A picture is enlarged by a whole number with crisp pixels (a 28 by 28 digit becomes 1400 by 1400). The file says it is 300 dpi, so Word and LaTeX place it at its real size. Before a run there is nothing to save, and a chart with no numbers cannot be drawn; the page says so with a message you can dismiss.

Save as CSV

The card menu of a Plot card also has Save as CSV, which downloads the numbers of the chart as n3-chart.csv: an index column, then one column per line, named as the chart’s legend names it (loss, or n3 loss when two lines share a name), and one row per point. A gap, a number that is not finite, and the end of a shorter line are empty cells. Numbers are written with all their digits. Before a run there is nothing to save, and the page says so with a message you can dismiss.

Line colours

Beside each series_N name of a Plot card is a small colour box in the colour of that line. Click it and pick a colour: the line and its legend entry change. A small x then appears beside the box, and pressing it gives the line the theme’s colour again. A colour that is hard to see on the chart (below 3:1 against its background) is drawn as you chose it, and the box gets a red outline with the ratio as its hover text. The colour belongs to the series input: Python holds it (app.graph, SetColour), Ctrl+Z undoes it, and a saved file keeps it (format version 4). examples/d_editor/d7_series_colours.py sets two colours from the script.

Save as PNG

The card menu (the three bars on a card, or Shift+F10 with the node focused) has Save as PNG on a card that shows a picture or a chart. The browser downloads n1-out.png for a picture, and n5-chart.png for a chart. Both are made to be put in a paper, whatever theme the editor has: a chart is 2100 by 1400 pixels (7 by 4 2/3 inches at 300 dpi) on a white page, with a thin black frame, serif numbers and labels, a faint grid, blue and red lines with different markers so they differ in greyscale, and a boxed legend when there is more than one line. A picture is enlarged by a whole number, with crisp pixels and a thin frame (a 28 by 28 digit becomes 1400 by 1400). The file says it is 300 dpi, so Word and LaTeX place it at its real size. Before a run there is nothing to save, and the page says so with a message you can dismiss.

Keyboard and screen readers

Pictures and plots are on a canvas, so the editor says what they show in the hidden list of nodes that a screen reader reads: n1, ramp. 1 input, 1 output. Shows a 28 by 28 picture. and n5, Plot. 3 inputs, 0 outputs. Shows a plot of 2 lines, loss last 0.0523 and accuracy last 0.906. The rows of a Plot are listed as ports, with the empty one last. The picture and chart states, the card menu and the source panel were scanned with axe-core in all three built-in themes, with no violation.

Try it

examples/g_experiments/g1_picture_and_plot.py has a picture, its negative, a pixel count and a plot, and its notes say what to try:

$ uv run python examples/g_experiments/g1_picture_and_plot.py