| Title: | Detect and Audit Train/Test Leakage from Near-Duplicate Images |
| Version: | 0.1.0 |
| Description: | Detects near-duplicate images across dataset splits using perceptual hashing, reports the resulting train/validation/test contamination, and produces a corrected, leak-free split assignment. Intended for machine learning researchers who need to verify that image classification splits do not share near-duplicate samples across partitions before reporting model metrics. |
| License: | MIT + file LICENSE |
| Encoding: | UTF-8 |
| Imports: | magick |
| Suggests: | testthat (≥ 3.0.0), knitr, rmarkdown |
| Config/testthat/edition: | 3 |
| URL: | https://github.com/anakincodex/leakaudit |
| BugReports: | https://github.com/anakincodex/leakaudit/issues |
| Depends: | R (≥ 4.1) |
| VignetteBuilder: | knitr |
| Config/roxygen2/version: | 8.0.0 |
| NeedsCompilation: | no |
| Packaged: | 2026-07-20 07:00:58 UTC; Kartik |
| Author: | Kartik Patel [aut, cre], Samruddhi Amol Shah [aut], BitandByte [cph] |
| Maintainer: | Kartik Patel <kartikpatel.id@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-07-29 16:40:13 UTC |
Produce a leak-free split assignment
Description
Resolves duplicate groups that span multiple splits by assigning every
member of a leaked group to a single split, chosen by priority. This
mirrors the common convention of keeping duplicates in train (so no
information is discarded, only potential evaluation leakage) but any
priority order can be supplied.
Usage
clean_splits(grouped_df, priority = c("train", "val", "test"))
Arguments
grouped_df |
A data frame with columns |
priority |
Character vector giving split precedence, highest
priority first. Defaults to |
Value
grouped_df with split replaced by the corrected,
leak-free assignment, plus a logical column reassigned flagging
which rows were changed.
Examples
groups <- data.frame(
path = c("a.jpg", "b.jpg", "c.jpg"),
split = c("train", "test", "val"),
group_id = c(1, 1, 2)
)
clean_splits(groups)
Compute perceptual hashes for a set of images
Description
Computes a 64-bit difference hash (dHash) for each image path. dHash is robust to resizing, mild compression, and small crops, which makes it well suited to catching near-duplicate images that differ only in resolution, format, or minor edits – the kind of duplication that commonly leaks across train/validation/test splits.
Usage
compute_hashes(paths, split = NULL)
Arguments
paths |
Character vector of file paths to image files. Any format
readable by |
split |
A character vector the same length as |
Value
A data frame with columns path, split, and hash (a 16
character hex string encoding the 64-bit dHash). Any path that fails
to load produces NA in hash with a warning, rather than stopping
the whole run.
Examples
## Not run:
hashes <- compute_hashes(
paths = list.files("images/", full.names = TRUE),
split = rep(c("train", "test"), length.out = 10)
)
## End(Not run)
Audit train/val/test leakage from duplicate groups
Description
Given duplicate groups (from find_duplicate_groups()) and each
image's split assignment, reports how many groups span more than one
split – i.e. how many near-duplicate images "leak" across the
train/validation/test boundary – and what fraction of each split is
affected.
Usage
dhash_audit(grouped_df)
Arguments
grouped_df |
A data frame with columns |
Value
An object of class leakage_report (a list) with:
- leaked_groups
data frame of group_ids that span >1 split, with the splits involved and member count
- n_leaked_images
total images sitting in a leaked group
- pct_leaked_by_split
named numeric vector, % of each split's images that belong to a leaked group
- total_images
total images audited
Examples
groups <- data.frame(
path = c("a.jpg", "b.jpg", "c.jpg", "d.jpg"),
split = c("train", "test", "train", "val"),
group_id = c(1, 1, 2, 3)
)
dhash_audit(groups)
Group near-duplicate images by hash distance
Description
Given a table of image hashes (as produced by compute_hashes()),
clusters images into duplicate groups: any two images within
threshold Hamming distance of each other are placed in the same
group, and group membership propagates transitively (A~B~C all end
up in one group even if A and C aren't directly close).
Usage
find_duplicate_groups(hash_df, threshold = 5)
Arguments
hash_df |
A data frame with at least columns |
threshold |
Maximum Hamming distance (in bits, out of 64) for two
images to be considered near-duplicates. Defaults to |
Value
hash_df with an added integer column group_id. Images with
no near-duplicates get their own unique group_id. Row order is
preserved.
Examples
hashes <- data.frame(
path = c("a.jpg", "b.jpg", "c.jpg", "d.jpg"),
hash = c("0000000000000000", "0000000000000001",
"ffffffffffffffff", "1234567890abcdef")
)
find_duplicate_groups(hashes, threshold = 5)
Hamming distance between two hex-encoded perceptual hashes
Description
Computes the number of differing bits between two perceptual hashes
encoded as hexadecimal strings (as produced by compute_hashes()).
Usage
hamming_distance(hash1, hash2)
Arguments
hash1 |
A character scalar or vector of hex-encoded hashes. |
hash2 |
A character scalar or vector of hex-encoded hashes, the
same length as |
Value
An integer vector of Hamming distances (number of differing bits).
Examples
hamming_distance("ff00", "ff01")
hamming_distance(c("ffff", "0000"), "0000")