Image To Graph¶
pleat.image_to_graph digitises a raster image of a line drawing (e.g. a scanned or photographed hand-drawn crease pattern) into a planar half-edge graph.
The pipeline is:
- downsample the image,
- threshold to a binary foreground mask,
- close gaps with a morphological closing,
- skeletonise and prune the result,
- detect branch points and the edge segments between them,
- merge branch points that are closer than a chosen cutoff,
- emit an
EuclideanPositionHEG.
The function plots intermediate results so you can pick threshold and edge_length_cutoff interactively.
Requires the optional dependencies scikit-image and mahotas.
import matplotlib
matplotlib.rcParams['figure.figsize'] = (5, 5)
import pleat
from pleat.image_to_graph import image_to_graph
/home/runner/work/pleat/pleat/.venv/lib/python3.12/site-packages/mahotas/morph.py:315: SyntaxWarning: invalid escape sequence '\s' ''' /home/runner/work/pleat/pleat/.venv/lib/python3.12/site-packages/mahotas/features/texture.py:33: SyntaxWarning: invalid escape sequence '\|' ''' /home/runner/work/pleat/pleat/.venv/lib/python3.12/site-packages/mahotas/features/texture.py:158: SyntaxWarning: invalid escape sequence '\|' '''
Convert the image¶
We use the photograph test_graph_image.jpg shipped under misc_outputs/. The two parameters that almost always need tuning are:
threshold— grayscale cutoff separating ink from paper. After the initial run the function shows you the grayscale histogram so you can pick a value.edge_length_cutoff— distance below which neighbouring branch-point clusters are merged into a single vertex. The function shows you the distribution of edge lengths to help pick this.
The function prints/plots the original image, the grayscale conversion, the thresholded foreground, the morphological closing, and finally the labelled edges.
G = image_to_graph(
'images/test_graph_image.jpg',
threshold=75,
closing_iterations=3,
edge_length_cutoff=40,
)
Inspect the resulting graph¶
image_to_graph returns a fully-fledged EuclideanPositionHEG — the same data structure produced by the tile-growth pipeline. Lengths and angles have already been recomputed from the extracted vertex positions, so it is ready for downstream processing (Conway operators, reciprocal figures, folding, etc.).
print(f'{len(G.vertices)} vertices, {len(G.halfedges_representing_edges())} edges, {len(G.faces)} faces')
G.show()
40 vertices, 98 edges, 59 faces
Saving and loading¶
Digitising a drawing is comparatively expensive, so you typically want to persist the result. Pleat's native YAML format .heg is round-trippable via pleat.io.save_graph and pleat.io.load_graph:
# pleat.io.save_graph('graphs/test_graph_image.heg', G)
# G_loaded = pleat.io.load_graph('graphs/test_graph_image.heg')
# G_loaded.show()