Nodes/ComfyUI Schematic/Schematic Topographic
ComfyUI Node

Schematic Topographic

Turn a photo into a topographic map sheet (the contours have nothing to do with your image)

By jeremieLouvaert·Created 2 months ago·Updated a day ago· 0
Schematic Topographic
  • image
  • pixelate_mask
  • texture
  • height_mask
  • image
  • overlay_only
  • overlay_alpha
height_sourceprocedural
terrain_scale420
terrain_octaves3
terrain_roughness0.45
terrain_stretch1.6
terrain_angle30
smoothing10.0
contour_resolution2
elevation_min0
elevation_max1000
contour_interval40
contour_weight1.0
index_every5
index_weight2.2
contour_labelstrue
label_size9
label_spacing320
label_unit
contour_text
contour_text_onindex
contour_text_size11
contour_text_gap60
hachuresdepressions
hachure_spacing10
hachure_length6
slope_arrowsfalse
arrow_spacing90
arrow_length22
spot_heightstrue
spot_count12
spot_min_distance120
symbol_size8
benchmarkstrue
benchmark_count4
gridticks
grid_spacing200
grid_weight1.0
grid_labelstrue
map_scale1.00
band_fill0.00
north_arrowtrue
scale_bartrue
interval_notetrue
marginalia_scale1.0
annotations
pixel_size16
zone_size100
pixel_stroketrue
pixelate_zones
seed42
overlay_opacity1.00
image_opacity0.85
texture_opacity0.50
frame_text_size12
frame_text_tlDesign & Strategy
frame_text_trAKURATE
frame_text_bl
frame_text_br
palettewhiteOnDark
bg_color#0a0a0a
stroke_color#d8d8d8
size_presetmatch input

Schematic Topographic draws a full cartographic sheet over an image - contour lines, heavier index contours with elevation numbers rotated along the line, depression hachures, spot heights, benchmarks, slope arrows, a coordinate grid, a north arrow, a scale bar. The map-poster look, in milliseconds, on the CPU, with no model and no diffusion pass.

That's why it deserves a slot. Almost every "make it look designed" move in ComfyUI is a generative re-pass: you pay a sampler and gamble on the model not repainting what you liked. This is the other layer - deterministic vector work, wrapped as a node (post-processing.md).

Read this before you turn any dial

By default, the terrain has nothing to do with your photo. height_source defaults to procedural: a seeded fractal landform built from seed, terrain_scale, terrain_octaves, terrain_roughness, terrain_stretch and terrain_angle. The sheet lies over the picture like tracing paper. If you wanted contours that trace a face or a mountain, that's brightness, darkness or detail - or wire a mask into the optional height_mask, which replaces the terrain entirely (white = high ground).

Nudge terrain_scale for ten minutes and the photo still won't change. It was never read.

How it works

The source becomes a height field normalised to 0–1, then Gaussian-blurred by smoothing (sigma in px - your "how calm is the terrain" dial; 0 leaves raw noisy pixels). It's resampled onto a coarser grid at one sample per contour_resolution pixels, and a marching-squares tracer walks that grid at every level z = k × contour_interval between elevation_min and elevation_max, chaining segments into polylines. Every index_every-th level - counted in absolute metres, so 200/400/600 m at 40 m × 5 - is drawn heavier and labelled. Drawing is Pillow at 2× supersampling, which is why strokes come out clean rather than jaggy, and each element has its own seeded random stream, so switching benchmarks off doesn't re-roll your spot heights.

The settings you'll actually touch

Terrain shape first. terrain_scale (420) is the size of the largest landforms in px; terrain_stretch and terrain_angle are what sell it, stretching blobs into ridges and valleys flowing one direction, while terrain_octaves and terrain_roughness add finer wrinkles.

Then density. contour_interval is metres between lines, and the tooltip's arithmetic is the clearest spec you'll get: 0–1000 m at 40 m is 24 lines, at 20 m is 49. If the sheet reads as grey mush, raise the interval first.

Two more worth knowing. contour_text runs a string along the contours glyph by glyph, following the line - the calendar-poster device, a name spelled across the hillsides - with contour_text_on choosing index contours only or all of them. And image_opacity defaults to 0.85, not 1.0 - the photo is deliberately faded under the sheet, so push it to 1.0 if you want the picture to fight the map. map_scale (metres per canvas px) wants a physical value as well; at the default of 1, grid labels are pixel numbers wearing a metre hat.

Outputs

image is the finished composite - background, photo, hypsometric band tone, vector stack, grain - and goes to a SaveImage node. overlay_only is that vector layer minus the photo. overlay_alpha is a 0–1 mask of everywhere ink was drawn, and it's the one people forget: feed it to a downstream mask input to grade or blur only where the map sits. Batches get a sheet per frame from the same terrain, so animation contours stay locked; height_mask and texture use frame 0 for the whole batch.

Install

Nothing to download. ComfyUI Manager → search "ComfyUI Schematic" → install, or:

cd ComfyUI/custom_nodes
git clone https://github.com/jeremieLouvaert/ComfyUI-Schematic

Restart ComfyUI; the node shows up under AKURATE/Schematic. The pack's requirements.txt is Pillow>=9.0, which you already have. No scipy, no models, fonts bundled in the repo - nothing to pin, nothing to break (comfyui-ecosystem.md).

Where people get burned

A flat height field renders contour-free. Push a uniform image through brightness and the node logs a notice and skips every contour, hachure, arrow, spot and benchmark. No levels to trace, so nothing to draw.

Levels cap at 400. A tiny interval across a wide elevation range trips the guard and prints "level cap 400 reached, raise contour_interval" to the console.

Labels need room. At label_spacing 320 px, index contours shorter than that get no elevation label, so small features end up bare. And index_every at 0 means no index contours - hence no labels, and nothing for contour_text to ride on in index mode. Related: if elevation_maxelevation_min the node swaps them and logs it, so you get a sheet, just not the one you asked for.

Cost scales with canvas. contour_resolution 2 is indistinguishable from 1 at default smoothing and twice as fast - stay there unless you're printing. On mixed-resolution batches, size_preset cover-fits every frame to one canvas.

CategoryAKURATE/Schematic

Inputs (66)

NameTypeDefaultDescription
imageIMAGE
height_sourceCOMBOproceduralWhere the terrain comes from. Procedural (default) = a seeded landform that has nothing to do with the photo - the sheet is an overlay. Brightness / darkness / detail derive it from the photo instead.
terrain_scaleINT42040–3000Procedural terrain: size (px) of the largest landforms.
terrain_octavesINT31–6Procedural terrain: detail layers. 1 = broad swells only; more adds finer wrinkles to the contours.
terrain_roughnessFLOAT0.450.1–0.9Procedural terrain: how much each finer layer contributes (gain).
terrain_stretchFLOAT1.61–5Procedural terrain: elongates landforms along terrain_angle so contours flow as ridges and valleys. 1 = isotropic blobs.
terrain_angleFLOAT300–180Procedural terrain: direction (degrees) of the ridge/valley flow.
smoothingFLOAT10.00–40Gaussian blur sigma (px) on the height field before contouring. Higher = broader, calmer contours; 0 = raw pixels (noisy).
contour_resolutionINT21–8Pixels per contour-tracing cell. 1 = finest (slowest); 2 is indistinguishable at default smoothing and twice as fast.
elevation_minFLOAT0-10000–10000Elevation (m) of the lowest point of the field. Labels, spot heights and benchmarks read in this range.
elevation_maxFLOAT1000-10000–10000Elevation (m) of the highest point of the field.
contour_intervalFLOAT401–500Vertical distance (m) between contour lines - the contour DENSITY. 0-1000 m at 40 m = 24 lines; 20 m = 49.
contour_weightFLOAT1.00.1–8Stroke width (px) of regular contours; also the base width of hachures, arrows, symbols, grid ticks and marginalia.
index_everyINT50–20Every Nth contour (counted in absolute metres, so 200, 400... at 40 m x 5) is an INDEX contour: heavier and labelled. 0 = none.
index_weightFLOAT2.21–5Index contour width as a multiple of contour_weight.
contour_labelsBOOLEANtrueInline elevation labels on index contours, rotated along the line, with the line broken under each label.
label_sizeINT94–48Font size (px) of contour labels, spot heights, benchmarks and grid coordinates.
label_spacingINT32060–2000Distance (px) along an index contour between labels; contours shorter than this get no label.
label_unitSTRINGSuffix after every contour label, e.g. ' m'. Empty = bare numbers.
contour_textSTRINGText repeated along the contours, glyph by glyph, following the line (the calendar-poster device). Empty = off. Replaces the numeric labels on the lines it runs on.
contour_text_onCOMBOindexRun contour_text on the index contours only, or on every contour.
contour_text_sizeINT114–48Font size (px) of contour_text.
contour_text_gapINT600–1000Gap (px) between repetitions of contour_text along a line.
hachuresCOMBOdepressionsShort downhill ticks on contours. Depressions = only on closed contours enclosing LOWER ground (the map-sheet convention). All = every contour.
hachure_spacingINT102–100Distance (px) between hachure ticks along a contour - tick DENSITY.
hachure_lengthINT61–60Length (px) of each hachure tick.
slope_arrowsBOOLEANfalseRegular grid of arrows pointing downhill, length proportional to slope (terrain-analysis aspect arrows).
arrow_spacingINT9020–400Grid pitch (px) of the slope arrows - arrow DENSITY.
arrow_lengthINT224–120Length (px) of the steepest arrow; flatter sites draw shorter, near-flat sites draw none.
spot_heightsBOOLEANtrueMark local peaks (triangle) and pits (circle) with their elevation.
spot_countINT120–100Maximum spot heights, most prominent first.
spot_min_distanceINT12010–800Minimum spacing (px) between spot heights and benchmarks.
symbol_sizeINT82–40Size (px) of the peak triangle, pit circle and benchmark cross.
benchmarksBOOLEANtrueSurvey benchmarks: an x cross with 'BM' + elevation, at seeded positions kept clear of the spot heights.
benchmark_countINT40–40Number of benchmarks (the only element that uses the seed).
gridCOMBOticksMap grid: full lines, edge ticks only, edge ticks + interior crosses (the UTM-sheet look), or a dot at every intersection.
grid_spacingINT20020–1000Grid pitch (px) - grid DENSITY.
grid_weightFLOAT1.00.1–8Stroke width (px) of grid lines, ticks and crosses.
grid_labelsBOOLEANtrueCoordinate labels (m) along the bottom and right edges, from map_scale; skipped near the corners to keep the frame text clear.
map_scaleFLOAT1.000.01–1000Metres per canvas pixel. Drives the grid coordinates and the scale bar.
band_fillFLOAT0.000–0.5Hypsometric plate tone: stepped stroke-colour fill between contours, stronger with elevation. 0 disables.
north_arrowBOOLEANtrueNorth arrow with 'N' at the top-right, under the frame text.
scale_barBOOLEANtrueGraphic scale bar at the bottom-left (a round 1-2-5 length from map_scale), above the frame text.
interval_noteBOOLEANtrue'CONTOUR INTERVAL n M' note under the scale bar.
marginalia_scaleFLOAT1.00.3–4Size multiplier for the north arrow, scale bar, interval note offsets and annotation leaders.
annotationsSTRINGCustom call-outs as 'x,y:TEXT;x,y:TEXT' in canvas pixels: a dot at the point, a 45-degree leader with a horizontal tail, text at the end.
pixel_sizeINT161–80Mosaic block size of the pixelate zones - larger = blockier; 1 disables. Identical to Schematic Overlay's.
zone_sizeINT10010–500Half-width of each square pixelation zone
pixel_strokeBOOLEANtrueDraw an outline around each pixelation zone
pixelate_zonesSTRINGPixelation zone centers as 'x,y;x,y;...' in canvas pixels. Union with pixelate_mask centroids. Alters the underlying photo only.
seedINT420–4294967295Seed for the procedural terrain, benchmark placement and the grain texture.
overlay_opacityFLOAT1.000–1Master opacity for every drawn element.
image_opacityFLOAT0.850–1Fade the background photo under the map sheet.
texture_opacityFLOAT0.500–1Opacity of the noise overlay (screen blend)
frame_text_sizeINT126–72Font size of the 4 corner labels
frame_text_tlSTRINGDesign & StrategyTop-left corner frame text
frame_text_trSTRINGAKURATETop-right corner frame text
frame_text_blSTRINGBottom-left corner frame text
frame_text_brSTRINGBottom-right corner frame text
paletteCOMBOwhiteOnDarkColor palette for background and stroke color
bg_colorSTRING#0a0a0aCustom palette background color (hex). Used when palette = custom
stroke_colorSTRING#d8d8d8Custom palette stroke color (hex). Used when palette = custom
size_presetCOMBOmatch inputCanvas size. 'match input' keeps the source image dimensions; presets cover-fit (crop-to-fill, centered) the source before resizing
pixelate_maskoptMASKOptional mask; each 8-connected white region's centroid becomes a pixelation zone center (as Schematic Overlay)
textureoptIMAGEOptional texture/noise image, screen-blended over the canvas. If empty and texture_opacity > 0, a procedural grain fallback is used
height_maskoptMASKOptional mask used AS the terrain height instead of height_source (white = high). Smoothing still applies. Cover-fitted like the photo when sizes differ; frame 0 used for batches.

Outputs (3)

NameTypeDescription
imageIMAGE
overlay_onlyIMAGE
overlay_alphaMASK