This project processes an image containing neon green dots on a black background, typically formatted in a sine-wave pattern, to extract embedded Morse code and convert it into a plain-text string.
It's especially useful for decoding steganographic messages or visually encoded signals from stylized images.
How It Works Image Preprocessing The script starts by converting the input image into an RGB array. It isolates bright green pixels using color thresholding — looking for high green values and low red/blue components.
Signal Detection It then collapses the 2D green signal vertically, yielding a 1D binary array representing the presence (1) or absence (0) of signal at each horizontal pixel.
Run-Length Analysis Using groupby, it measures the lengths of continuous signals (1s) and spaces (0s) to distinguish between:
# Convert image to RGB array
rgb_array = np.array(rgb_image)
# Create a binary mask for neon green (bright green dots on black)
# We'll consider a pixel "on" if its green value is high and red/blue are low
green_mask = (
(rgb_array[:, :, 1] > 200) & # green is high
(rgb_array[:, :, 0] < 100) & # red is low
(rgb_array[:, :, 2] < 100) # blue is low
)
# Collapse vertically to get a 1D signal
green_signal_1d = green_mask.any(axis=0).astype(int)
# Detect runs
runs = [(val, sum(1 for _ in group)) for val, group in groupby(green_signal_1d)]
# Estimate thresholds for dot/dash and space types
signal_lengths = [length for val, length in runs if val == 1]
space_lengths = [length for val, length in runs if val == 0]
min_dot = min(signal_lengths)
dot_dash_threshold = min_dot * 2
min_space = min(space_lengths)
letter_space_threshold = min_space * 3
word_space_threshold = min_space * 6
# Convert runs to Morse code
morse_code = ""
for val, length in runs:
if val == 1:
if length < dot_dash_threshold:
morse_code += "."
else:
morse_code += "-"
else:
if length >= word_space_threshold:
morse_code += " "
elif length >= letter_space_threshold:
morse_code += " "
morse_code
This project generates a stylized SVG visualization of a Morse code message, arranged along a sinewave and rendered with an oscilloscope-inspired aesthetic.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.patches as patches
# Morse code string
morse_code = "..-. .-.. .- --. ---... -.-- --- ..- - ..- -... . .-.-.- -.-. --- -- -..-. .-- .- - -.-. .... ..--.. ...- -.. --.- .-- ....- .-- ----. .-- --. -..- -.-. --.-"
# Split into characters and spaces
elements = []
for char in morse_code:
if char in ['.', '-']:
elements.append(char)
elif char == ' ':
elements.append(' ') # Space between letters
# Generate sinewave x positions with enough spacing
spacing = 0.6
x_positions = []
current_x = 0
for element in elements:
x_positions.append(current_x)
if element == ' ':
current_x += spacing * 2 # More space for spaces
else:
current_x += spacing
# Sine wave y values
x_vals = np.array(x_positions)
y_vals = np.sin(x_vals / 3) * 0.8 # Smoothed sine wave with amplitude adjustment
# Plot
fig, ax = plt.subplots(figsize=(24, 6), facecolor='white')
ax.set_facecolor('white')
# Plot symbols with oscilloscope-style neon green
green = '#00FF41'
for i, element in enumerate(elements):
x, y = x_vals[i], y_vals[i]
if element == '.':
circle = patches.Circle((x, y), radius=0.1, color=green)
ax.add_patch(circle)
elif element == '-':
rect = patches.FancyBboxPatch((x - 0.15, y - 0.05), 0.3, 0.1,
boxstyle="round,pad=0.02", color=green)
ax.add_patch(rect)
# Optionally, add a glowing sinewave path for effect
ax.plot(x_vals, y_vals, color=green, linewidth=0.5, alpha=0.15)
# Enhancements for oscilloscope vibe
ax.set_aspect('equal')
ax.axis('off')
ax.set_xlim(x_vals.min() - 1, x_vals.max() + 1)
ax.set_ylim(y_vals.min() - 1, y_vals.max() + 1)
# Save as SVG
svg_path = "./morse_oscilloscope_style.svg"
fig.savefig(svg_path, format='svg', bbox_inches='tight', transparent=True)
plt.close(fig)
svg_path