ShadowGlyph & Coder
Coder Coder
Hey, did you ever look into how medieval scribes hid messages in illuminated manuscripts using null encryption? There's something pretty fascinating about the way they used patterns to conceal text.
ShadowGlyph ShadowGlyph
I’ve skimmed a few of those illuminated codices. The way the gold leaf is layered, the way the marginalia loops just around a hidden line of text, it’s a quiet code hidden in plain sight. Patterns that look like decoration actually encode a word. I’ve been tracking the repetition rate in a few volumes, and it’s surprisingly consistent. It’s a neat little puzzle to solve.
Coder Coder
That’s a pretty neat puzzle – maybe you could map the loop density to a substitution cipher, or write a quick script to quantify the pattern frequencies automatically. Has anyone tried automating the analysis yet?
ShadowGlyph ShadowGlyph
A few folks have slapped a bit of Python on it, using OpenCV to scan the parchment and count the strokes. They map loop density to a frequency table and feed it into a substitution solver. It’s still a bit rough, but the code can flag the odd‑ball patterns that look like hidden text. Not a huge library out there, just a handful of scripts shared on GitHub. If you want to dig deeper, I can pull the code snippets.
Coder Coder
Sounds cool – if you drop a snippet, I can take a quick look and see if I spot any edge‑cases or ways to make the loop detection more robust. Just share what you’ve got.
ShadowGlyph ShadowGlyph
Here’s a rough outline I’ve used in a quick prototype. It’s not perfect, but it gives you a starting point for loop counting and frequency mapping. ```python import cv2 import numpy as np from collections import Counter # Load a high‑resolution scan of the manuscript img = cv2.imread('manuscript.png', cv2.IMREAD_GRAYSCALE) # Binarize – tweak threshold for the paper texture _, binary = cv2.threshold(img, 200, 255, cv2.THRESH_BINARY_INV) # Find contours – these often correspond to ink strokes contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # Count loops: a loop is a contour that contains a child contour loop_counts = [] for cnt in contours: # Approximate shape to reduce noise approx = cv2.approxPolyDP(cnt, 0.01*cv2.arcLength(cnt, True), True) area = cv2.contourArea(approx) if area < 50: # ignore tiny specks continue # Rough heuristic: if the contour has a hole, it’s a loop mask = np.zeros_like(binary) cv2.drawContours(mask, [cnt], -1, 255, -1) holes = cv2.findContours(cv2.bitwise_not(mask), cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)[0] if len(holes) > 1: loop_counts.append(len(holes)-1) # Now you have a list of loop counts per stroke; map to letters freq = Counter(loop_counts) print(freq.most_common()) ``` Adjust the threshold, area cutoff, and hole detection logic for your specific manuscripts. Let me know what you think.