Views
No views yet

prism-steganography hides a recoverable 64-bit message inside a cover image imperceptibly — not a fixed watermark, but a full trained encoder/decoder pair. Built as two jointly-trained networks: a U-Net encoder that embeds the message as bounded, near-invisible pixel perturbations, and a CNN decoder that recovers it. A differentiable noise layer sits between them at train time (blur, sensor noise, JPEG-like compression, pixel dropout), so the decoder learns to recover the message even after the image is distorted — not just from a pristine copy.
| Architecture | U-Net encoder (message tiled + concatenated with image) + CNN decoder |
| Message capacity | 64 bits (8 bytes / 8 ASCII characters, UTF-8, null-padded) |
| Input | RGB image, 128x128 |
| Training data | PD12M, pxhere, cc0-textures, ambientcg (Apache/CC0-licensed) |
| Training | Mixed precision, differentiable noise layer (blur/noise/JPEG-approx/dropout), early stopping on validation bit-accuracy |
1from huggingface_hub import hf_hub_download
2import torch, importlib.util, json
3
4model_file = hf_hub_download(repo_id="olaverse/prism-steganography", filename="model.py")
5ckpt_file = hf_hub_download(repo_id="olaverse/prism-steganography", filename="pytorch_model.pt")
6config_file = hf_hub_download(repo_id="olaverse/prism-steganography", filename="config.json")
7
8spec = importlib.util.spec_from_file_location("model", model_file)
9model_module = importlib.util.module_from_spec(spec)
10spec.loader.exec_module(model_module)
11
12config = json.load(open(config_file))
13checkpoint = torch.load(ckpt_file, map_location="cpu")
14
15encoder = model_module.StegEncoder(**config)
16encoder.load_state_dict(checkpoint["encoder"])
17encoder.eval()
18
19decoder = model_module.StegDecoder(**config)
20decoder.load_state_dict(checkpoint["decoder"])
21decoder.eval()1# encoder(cover_image_tensor, message_bits_tensor) -> stego_image_tensor
2# decoder(stego_image_tensor) -> recovered_bit_logits (threshold at 0)1from PIL import Image
2import torchvision.transforms.functional as TF
3
4MSG_BITS = config["msg_bits"] # 64
5
6def text_to_bits(text: str, num_bits: int = MSG_BITS) -> torch.Tensor:
7 num_bytes = num_bits // 8
8 raw = text.encode("utf-8")[:num_bytes]
9 raw = raw + b"\x00" * (num_bytes - len(raw))
10 bits = []
11 for byte in raw:
12 bits.extend([(byte >> i) & 1 for i in range(7, -1, -1)])
13 return torch.tensor(bits, dtype=torch.float32)
14
15def bits_to_text(bits: torch.Tensor) -> str:
16 bits = bits.round().long().tolist()
17 byte_vals = []
18 for i in range(0, len(bits), 8):
19 val = 0
20 for b in bits[i:i + 8]:
21 val = (val << 1) | b
22 byte_vals.append(val)
23 raw = bytes(byte_vals).rstrip(b"\x00")
24 return raw.decode("utf-8", errors="replace")
25
26# load a cover image and resize to the model's trained resolution
27img = Image.open("cover.jpg").convert("RGB").resize((128, 128))
28cover = TF.to_tensor(img).unsqueeze(0)
29
30# encode a message (truncated/padded to 8 bytes -- this model's fixed capacity)
31message = "hi there"[: MSG_BITS // 8]
32msg_bits = text_to_bits(message).unsqueeze(0)
33
34with torch.no_grad():
35 stego = encoder(cover, msg_bits) # near-identical image with the message hidden
36 recovered_logits = decoder(stego) # decode straight from the stego image
37 recovered_bits = (recovered_logits > 0).float()
38
39recovered_text = bits_to_text(recovered_bits[0])
40
41print("Original message: ", message)
42print("Recovered message:", recovered_text)
43
44TF.to_pil_image(stego[0]).save("stego.jpg") # save the image with the hidden messagemsg_bits // 8 ASCII characters (8, for this model) — longer input is silently truncated by text_to_bits. To test robustness against real-world distortion (compression, re-uploading, etc.), pass stego through your own noise/degradation of choice before decoding — recovery accuracy will drop somewhat under distortion (see Benchmarks above), so consider adding error-correction coding on top of the raw bits for applications that need near-perfect reliability.Spawning/PD12M, CDLA-Permissive-2.0), pxhere (nyuuzyou/pxhere, CC0), cc0-textures (nyuuzyou/cc0-textures, CC0), and ambientcg (nyuuzyou/ambientcg, CC0). Released under Apache-2.0.@misc{prism-steganography,
title = {prism-steganography},
author = {Olaverse},
year = {2026},
url = {https://huggingface.co/olaverse/prism-steganography}
}