Skip to content

What is in it ​

Covers10,000 photographs from Wikimedia Commons
Stego pairs344,357
Arms35 stego, plus 4 clean
FormatWebDataset tar shards
LicenceCC BY 4.0 for the collection; each file's own terms in its record

The arms ​

FamilyToolsPayloadsDomain
Adaptive, spatialHUGO, WOW, S-UNIWARD, HILL, MiPOD0.05, 0.1, 0.2, 0.4 bppPixels
Adaptive, DCTJ-UNIWARD, UERD0.05, 0.1, 0.2, 0.4 bpnzacJPEG coefficients
End-user toolssteghide, outguess5%, 20%, 50% of capacityJPEG
ControlAppended data after the end markern/aJPEG

The four digits in an arm name are not always the same thing ​

An arm is named for its tool and a four digit number, and that number is always the payload rate times a thousand. The unit of the rate is set by the family, so the same digits mean three different quantities.

Arm nameReads asWhich is
hugo-00500.05 bits per pixelAn absolute rate: bits hidden per pixel of the image
juniward-00500.05 bits per non-zero AC coefficientAn absolute rate, over JPEG coefficients rather than pixels
steghide-00505% of capacityA relative rate: a share of what that tool said that picture could hold
append_after_eoi-0000n/aA fixed payload, the same on every image

hugo-0400 and steghide-0500 are not "more payload" versions of each other, and the relative rates are not comparable between pictures either, because steghide's capacity depends on the content of the photograph.

Every JPEG arm is quality 95 ​

The JPEG arms and their clean halves are written at quality 95: one setting, everywhere, both halves, with identical quantisation tables. Steganalysis performance on JPEG moves sharply with the quality factor, so a number measured here is a number at quality 95 and should be reported as one.

The appended-data arm ​

This is the sanity check. Anything claiming to detect steganography should find it trivially; a tool that misses it is not reading the file. No detector result is published here, so this is a statement about what the arm is for rather than a measurement: if you want a number, measure it and say how.

The payload is a fixed 4,122 byte literal, identical in all 10,000 images, sat after the end-of-image marker. That makes it a clean control and a useless training target: a classifier trained on this arm learns one byte string and reports a perfect score that means nothing.

The tool arms carry no passphrase ​

steghide and outguess were run without one, so all 54,357 samples in those six arms can be extracted by anybody who has them. That's deliberate, because a corpus whose payloads nobody can recover cannot be checked, but it does mean these arms are readable rather than merely detectable.

The clean arms ​

Every pair needs both halves, and the clean half is not always the cover.

ArmWhat it isPairs with
clean-greyThe cover in greyscaleThe spatial arms
clean-jpegWritten back through the DCT library, coefficients untouchedThe DCT arms
clean-jpeg-toolsThe cover as a JPEG, as the tools were handed itsteghide, appended data
clean-outguessThrough outguess's own writer, carrying the least it acceptsoutguess

They are separate because they are different encodings of the same photograph. A tool that rewrites the whole file leaves its encoder's signature on everything it writes, so pairing it against anything else measures the encoder rather than the hiding.

The format ​

A sample's parts share a basename, which is the WebDataset convention:

pentimento-core-wow-0200-00000.tar
  000000.png     the image
  000000.json    its record, licence included

Shards stream without unpacking and every major dataset loader reads them.

What a record holds ​

FieldWhat it is
source_pngThe cover this descends from. Group by it to split
cover_licenceThe cover's licence, artist, credit line and source URL
sha256The image's digest, checked when it was packed
arm, toolWhich arm this came from, and what made it
rateThe nominal payload, null on the clean arms
rate_unitWhat the rate counts, including unstated on the clean arms
pairingcontainer-verified, or no-clean-half on the clean arms
licence_joinHow this file's licence was traced back to its cover

Every one of the 39 arms carries those nine, and domain below is a tenth that is always present but not always filled in. Nothing else is universal, and a loader that assumes otherwise falls over on the first arm it has not seen before. Reach for anything in the next table with record.get(...) rather than record[...].

FieldArms that have itArms that don't
clean, clean_sha256, stego, stego_sha256The 35 stego armsThe 4 clean arms, which are one half rather than a pair
file, roleThe 4 clean armsThe 35 stego arms
codingThe 28 adaptive armsThe 6 tool arms, the appended-data arm, the 4 clean arms
domainAll 39, though it is null on clean-jpeg-tools and clean-outguessNone, but test for null rather than for absence
samples_changed, change_rateThe 20 spatial adaptive armsEverything else, the DCT arms included
coefficients_changedThe 8 DCT adaptive armsEverything else
jpeg_quality, payload_bytes, detailThe 6 tool arms and the appended-data armEverything else. The DCT arms are quality 95 too, they just don't record it
capacity_bytesThe 6 tool arms, whose rates are relative to itEverything else
source_sha256The 35 stego arms, proving which cover bytes they came fromThe 4 clean arms
source_pngAll 39 arm shardsThe cover shards, which call the same value file
source_jpegThe 8 DCT adaptive arms and the 4 clean arms, 12 in totalThe 20 spatial adaptive arms, the 6 tool arms, the appended-data arm

The corpus is published under CC BY 4.0. Each file also carries its own licence.