Concepts: subproject, parts table, step, gate, delivery
✅ verified · the conventions live in AGENTS.md one directory up · the code behind every stage: appendix B
Three things, three places
Krita/
dtp/ the library (a uv project, its own git): atomic pure functions plus readers
and writers, `import dtp`; one main.py template per stage
work/<name>/ subprojects (not in git): one picture, one route, one subproject; one stage
per step; one main.py per step
diffusion-to-painting/ this handbook (mdBook, its own git)
The library knows no particular picture: no part names, no coordinates, no prompts. Everything a picture has to tell the tool lives in that subproject’s parts.py. The library also knows nothing about step directories, orchestrates no stages and has no command line: its functions take arrays, dicts, a parts table and the path of a single file, and return values. What wires them into a stage is the main.py inside each step.
A subproject, work/<name>/
work/pic/
input/origin.png the picture
parts.py this picture's parts table (copied from the template in the library)
README.md written by a person: what is wanted, and the verdict at each gate
overrides.json overruling the lines (optional, ch. 2 and 4)
phase1-layers/ one phase per milestone: every layer's whole shape and its lines
README.md what this phase produces, plus the verdict (written by a person)
step01-segment/ one step per stage, numbered globally; inside it a main.py (copied from
the template) plus the artifacts and README it writes
step02-lineart/ step03-segment/ step04-complete/ step05-lines/
phase2-draft/ the draft (a gate)
step06-draft/
phase3-paint/ the finished painting; the one-shot route's trace lives here too
step07-paint/ step08-gauge/
One picture, one route, one subproject: taking a picture down the one-shot route creates pic-basic/, down the layered route pic-pro/, and a second attempt is pic-basic-2/. Two subprojects share only the picture and parts.py (copied), and each keeps its own account in its steps.
Phases and steps
Phases are the milestones a person wants to look at, and there are three: phase1-layers/ for every layer’s whole shape and lines (segment, lineart, segment again with the lines as walls, complete, per-layer lineart; the one-shot route has only segment), phase2-draft/ for the draft, which is the gate, and phase3-paint/ for the finished painting (paint and gauge; the one-shot route’s trace lives here too). A phase holds several stages, and a redone step stays in its own phase. A phase’s README.md records what it produces when it is first opened; the verdict is written by a person.
One stage is one step, one step has one main.py, the living chain is consecutively numbered, and a failure consumes no number. Opening a step means making its directory, copying that stage’s main.py template into it, editing the two blocks of constants at the top, and running it (appendix A). When a step is judged a failure it is mvd to stepNN-<stage>-legacy-wrong-1 and kept in its phase as a record (an experiment becomes -legacy-experiment-1), and the rerun uses the same number; grep renames it in the READMEs and main.py files that refer to it. A second pass that is not a failure (segmenting again once the lineart exists) keeps both numbers. Inside each step directory:
| file | written by | contents |
|---|---|---|
main.py | a person (copied from the template and edited) | Two blocks of constants at the top: # ---- upstream (which steps it reads, as paths relative to the subproject root) and # ---- knobs; below them the body wires the library’s pure functions together and writes the artifacts and the README. It is the complete record of this step: what it read, what the parameters were, how it was computed |
README.md | main.py writes the numbers, a person adds ## Verdict | It opens with the table of numbers this step wrote (creation time, inputs, knobs, the table); it ends with ## Verdict, a person’s “pass or fail, and why” after looking, which a rerun preserves |
| the job description | main.py (the three steps that paint) | the Krita job’s arguments, which is also what a person replays the job from on a real desktop (appendix A) |
| artifacts | main.py | the plans (in 1x coordinates), the layer tree, what painting produced, the review images, and what ComfyUI produced – which is not asked for again once it is there. The names are fixed; they are all in appendix F |
Steps pass files to each other through step directories and nothing else. The upstream is named at the top of main.py as the path of the step to read, and changing that one line is what changes the upstream. The knobs are constants at the top too, not environment variables – the only things the library takes from the environment are machine-level settings, where this machine keeps ComfyUI and the layering model; the multiplier and the seed are constants in each main.py.
The parts table, parts.py
An ordinary Python module of uppercase constants, in three tiers:
| tier | what a person writes there | used by |
|---|---|---|
| sub-parts | which raw regions make up each named sub-part, bottom to top by occlusion | segment; the one-shot route divides its parts by this tier alone |
| classes | the words to ask the segmentation model for, and which sub-parts each word’s class covers | the majority vote in segment |
| groups | which sub-parts make up each layer, which layers share one flat colour, which layer is cut out of a mask, which lineart layer a layer’s lines go to, which layers grow strands or get hatching, which of the model’s layers a group may be completed from, which method paints it, and where the 1:1 close-ups are centred | every step of the layered route |
Each tier is explained in the chapter of the stage that governs it; appendix G is the field-by-field reference – the exact names, the format of each one, and which step to rerun after changing it. A missing field is an error rather than a guess.
The intermediate form is a tree
The final form “painting” takes is a layer tree, written out as one file: a description of an editable .kra, where every layer has a kind (fill, gradient, strokes, mask, filter, group), a blending mode, an opacity and a shape. The painting job – there is exactly one, shared by all three routes – reads it, builds the tree and paints, and decides nothing else.
What used to pass between the two was a list of strokes. That model forced everything to become strokes: filling an area took hundreds of scanning strokes, a shadow could not be written as “a multiply layer at 65%” but only as the composited RGB filled flat, and “draw the whole highlight band and then cut it” could not be expressed at all. With a tree, it can be read, diffed and edited without opening Krita, and a 1x preview of it draws in twenty seconds; and when the artist opens it, the shadow really is a layer they can adjust.
The criterion changed with it: what is counted is how many numbers this tree takes to say what the picture is (ch. 7).
Gates
What numbers cannot measure is put in front of a person: whether a layer is whole, why a line is there, whether what was invented looks right. The gates are at these steps:
| step | what to look at | what to change |
|---|---|---|
segment | the three-column review image – the hand-written table, the majority vote, and what the vote changed – and the map of numbered regions | which regions make up which sub-part, and the words; run it once more with the lineart as walls |
lineart | the review image on top of the source: blue for a kept AI line, brown for a kept contour, red dashes for dropped | overrides.json |
complete | the completion review image – per group: the hidden area, its completed layer, the layer alone, the outline – and the restack difference | which of the model’s layers that group is completed from |
lines | one review image per layer, in the same three colours; and the combined one, where kept lines are coloured by layer and the pale ones are invented | overrides.json (its boxes are 1x full-image coordinates) |
draft | the review images painting writes: one per layer, one per group, the 1x composite and the 1:1 close-ups; hide the upper layers to see the lines underneath | any of the four above |
paint / trace | the same, plus the layer tree’s description count | which method paints that group |
The file names are fixed and are listed in appendix F; the field to change in each case is in appendix G.
The images are to be looked at, not merely produced: every time the baseline changes, look at the 1:1 close-ups of at least the face, the neck, a hand and a foot, whose centres the parts table fixes. The verdict goes at the end of the step’s README.md and into the gate record in the subproject’s README.md.
What is delivered
Each painting step leaves five things behind: the .kra (the whole layer tree, with clip / blend / opacity / label / vector layers surviving the round trip), the composite, the painting journal (one line per stroke and per pixel write – the only evidence that this picture was painted, and the input for replaying it), a manifest of every node in the tree (path, type, blend, clip, bounds), and the review images, plus one image per stage. The names are in appendix F; what the tree looks like is in ch. 5.
The two routes
| one-shot (ch. 9) | layered (ch. 10) | |
|---|---|---|
| step | segment -> trace | segment -> lineart -> segment -> complete -> lines -> draft -> a person looks -> paint |
| the question it answers | can a diffusion image become a 4K painting where every pixel has a provenance | can an artist take it over |
| Layers | each part cut into bands by luminance, one vector shape plus two paint layers per band; several hundred nodes, and no layer called “eye” | each group = shape / flat colour / shading / named bands / lines; dozens of nodes |
| lines | none | each layer’s own lines, one stroke at a time, each with a source and open to being overruled; a line in a hidden area is invented, and accounted for |
| what is hidden | empty | a layering model invents the whole shape, only the hidden part is taken, and exactly which pixels those are is written down |
| Gates | none | draft |
| needs ComfyUI | SAM only | all four of segment, lineart, complete and per-layer lineart need it |
| how closely it resembles the source | closer, and sharper inside | slightly softer; the difference is what layering costs |
How to choose: for a picture that resembles the source with no interest in the layers, take the one-shot route; for a file an artist can edit, take the layered one. The comparison only exists once both have been run – point the criteria step at the other subproject’s finished picture and it produces the crops from both at matching positions.
Experiments
Trying an idea without changing the library: open a step and write its main.py however you like (it need not come from a template), calling the library’s pure functions. The conventions for an experiment: copy the plans from a baseline step, change the plan for one group only, turn the Krita job off and look at the 1x simulation first (which takes seconds), and after painting point the criteria step at the baseline so the two are compared on the same rulers. When it works, it sinks into the library as a pure function with tests, and the main.py stays in the step as the record.