🔍 CVFlow
A linter for computer-vision datasets.
Point it at a folder of images and labels. It tells you what's broken,
duplicated, mislabeled, or suspicious, before it wastes a training run.
Quickstart
Three commands. No config, no account, no setup.
pip install cvflow # 1. install
cvflow inspect # 2. no dataset? it fetches coco128 and checks that
cvflow inspect ./dataset --serve # 3. or point it at yours and open the dashboard
Running cvflow inspect with no path downloads Ultralytics' coco128 sample
(about 7 MB) into a cache directory and inspects it, so you can see exactly what
the tool does before arranging any data. It is fetched once and reused; set
CVFLOW_CACHE to choose where it lands.
That's the whole tool. ./dataset is any folder holding a YOLO or COCO dataset;
CVFlow works out which one it is, and whether it holds boxes, polygons or
oriented boxes.
Never used a terminal tool like this?
Step-by-step, from nothing
-
Check you have Python 3.9 or newer. In a terminal (Command Prompt on Windows, Terminal on macOS/Linux):
python --versionNo Python? Install it from python.org/downloads.
-
Install CVFlow:
pip install cvflow
-
Try it. With no dataset of your own, just run it: CVFlow downloads and unzips the
coco128sample for you.cvflow inspect --serve
Already have a dataset? Point at its folder instead:
cvflow inspect ./my-dataset --serve -
Your browser opens
http://localhost:8000with the dashboard. PressCtrl+Cin the terminal when you're finished.
cvflow: command not found? Use python -m cvflow instead of cvflow.
Same tool, works even when your PATH isn't set up:
python -m cvflow inspect ./dataset --serve
What you get
The dashboard: --serve
cvflow inspect ./dataset --serve
# coco128: 128 images, 929 annotations, 71 classes (YOLO object detection, no splits).
# 98 findings: 0 errors, 49 warnings, 49 info.
# Open http://localhost:8000 for the detail: every finding, its image, and the fix.
One page, four tabs, everything hover- and keyboard-readable, light and dark:
| Tab | What it answers |
|---|---|
| Overview | How healthy is this dataset? What should I fix first, and how much accuracy is it worth? |
| Classes | Which classes dominate? How long is the tail? |
| Geometry | How big are the boxes, what shape, how many per image, and where in the frame do they sit? |
| Findings | Every finding, filterable by severity, check, or free text. |
No extra install. The charts are Chart.js and the type is Archivo, the Ultralytics brand face, both vendored inside the Python package and inlined into the page. No Node, no npm, no CDN:
pip install cvflowis the whole setup and the page works offline.
Handy bits: hover any card for a plain-English explanation of what it shows, use ⤢ to blow it up full screen or ↓ to save it as PNG or JSON, collapse the sidebar with the rail button, click any check or image to filter the findings to it, and use the Accuracy headroom panel to estimate what fixing each problem is worth. Tick items off as you go and the number updates.
Findings render as cards: severity, the headline, why it was flagged, a thumbnail of the image, and the suggested next step. Filter by severity, check, split or free text, and sort by any of them.
Fix boxes without leaving the page
Click any finding that points at an image (or any bar in Images with the most findings) and the photo opens with its boxes drawn on top. The box the finding is about is drawn in red; every other class keeps its own colour. Down the right side: what was flagged, why, the suggested next step, and one-click fixes (Clamp into frame, Delete this box, Reassign class, Add a box). You can also:
- drag a box to move it, drag its corner to resize,
- drag on empty canvas to draw a new one,
- change the class of the selected box, or delete it,
- Save labels to write the corrections straight back to the label file.
Editing needs --serve (the static --html file has no backend) and writes
YOLO detection labels only: one small text file per image, so a change is
contained and easy to review in git. COCO, segmentation and OBB datasets open
read-only, because rewriting a polygon as a box would silently throw the mask
away, and the panel says so. Everything is confined to your dataset folder: the
server serves that one page plus images from inside the dataset root, nothing else.
Prefer a file you can archive or attach to a PR? --html writes the same
self-contained page to disk, no server involved:
cvflow inspect ./dataset --html report.html
The terminal report: the default
cvflow inspect ./dataset
CVFlow Dataset Health
────────────────────────────────────────────────────
Format YOLO
Images 12,482
Annotations 12,103
Classes 8
Splits train, val
Health Summary
────────────────────────────────────────────────────
ERROR 17
WARNING 184
INFO 6
Most Important Problems
────────────────────────────────────────────────────
1. [ERROR] 17 images are unreadable or corrupt.
2. [ERROR] 21 bounding boxes extend outside the image boundaries.
3. [WARNING] 143 unusually small bounding boxes detected.
4. [WARNING] 127 highly similar image pairs between 'train' and 'val' (possible leakage).
5. [WARNING] Class 'helmet' represents only 0.4% of annotations.
The exit code is 0 when nothing's wrong and non-zero when there are errors, so
cvflow inspect drops straight into CI. Add --strict to fail on warnings too.
What it checks
Point CVFlow at a dataset and it answers the questions you'd otherwise check by hand, one script at a time:
CVFlow reads object detection, instance segmentation and oriented box (OBB) datasets. Polygons and oriented boxes are audited through their axis-aligned extent, and the task is named in the sidebar and the terminal, so you always know how your labels were read.
- Is anything broken? Corrupt/unreadable images, missing or invalid annotation files, broken paths, bad image dimensions, duplicate filenames.
- Are the annotations sane? Boxes outside the image, negative or zero-area boxes, absurdly tiny or full-frame boxes, duplicate overlapping boxes, class IDs that don't exist.
- Is the distribution weird? Class balance, objects per image, box sizes, aspect ratios, and images that are statistical outliers.
- Do I have duplicates? Exact copies (by hash) and near-duplicates (by perceptual hash, with a similarity score).
- Are my splits leaking? The same (or nearly the same) image in more than one split. This one bites hardest on datasets cut from video.
Every finding carries a severity, a plain-English reason, where it is, the evidence behind it, and a suggested next step. CVFlow won't tell you your dataset is wrong; it shows you what looks off and lets you make the call.
How your dataset should be laid out
CVFlow reads the two most common detection formats. The closer your folder is to one of the layouts below, the more it can audit. In particular it needs to find the actual image files on disk to check for corrupt images, duplicates, and split leakage.
YOLO
An Ultralytics-style data.yaml next to mirrored images/ and labels/ folders:
dataset/
├── data.yaml
├── images/
│ ├── train/
│ │ ├── frame_0001.jpg
│ │ └── frame_0002.jpg
│ └── val/
│ └── frame_9001.jpg
└── labels/
├── train/
│ ├── frame_0001.txt # matches images/train/frame_0001.jpg
│ └── frame_0002.txt
└── val/
└── frame_9001.txt
data.yaml names the classes and points at each split:
path: . # optional; base for the paths below (relative to this file)
train: images/train
val: images/val
# test: images/test # optional
names: # a list works too: [person, helmet]
0: person
1: helmet
A label file holds one box per line in normalized YOLO format: class id, then box center and size, each as a fraction of the image (0–1):
# class_id cx cy w h
0 0.512 0.437 0.104 0.216
1 0.300 0.300 0.050 0.080
A few things worth knowing:
- CVFlow finds a label by taking the image path and swapping
images/→labels/and the extension →.txt. Keep that mirroring intact. - An image with no label file, or an empty one, is treated as a background image (no objects); that's a WARNING to sanity-check, not an error.
- Supported image extensions:
.jpg .jpeg .png .bmp .webp .tif .tiff.
No data.yaml? CVFlow falls back to a plain images/ + labels/ pair. It
picks up train/, val/, test/ subfolders if present, otherwise treats
everything as one split. Class names come from classes.txt (one name per line)
if it exists, and are inferred from the label files otherwise.
COCO
Annotation JSON(s) under annotations/, images under images/:
dataset/
├── annotations/
│ ├── instances_train.json
│ └── instances_val.json
└── images/
├── train/
│ └── 000000000001.jpg
└── val/
└── 000000009001.jpg
Each JSON is standard COCO: images, annotations, and categories:
{
"images": [{ "id": 1, "file_name": "000000000001.jpg", "width": 640, "height": 480 }],
"annotations":[{ "id": 1, "image_id": 1, "category_id": 1, "bbox": [100, 120, 80, 160] }],
"categories": [{ "id": 1, "name": "person" }]
}
Notes:
- COCO
bboxis absolute pixels[x, y, width, height]with(x, y)at the top-left. CVFlow normalizes it using each image'swidth/height, so a YOLO box and a COCO box end up meaning the same thing. - The split is inferred from the JSON filename: anything containing
train,val, ortest. A single JSON with no such hint loads as one unnamed split. - To run the image-level checks (corrupt / duplicate / leakage), CVFlow needs the
pixels. It looks for each
file_nameunder the dataset root, thenimages/, thenimages/<split>/and<split>/. If it can't find them it still audits structure, annotations, and statistics, and tells you image checks were skipped rather than inventing false positives.
Rule of thumb: structure and annotation checks run from the labels alone; corrupt-image, duplicate, and leakage checks need the image files reachable from the dataset root. Point CVFlow at the folder that contains both.
Command reference
cvflow inspect [path] [options]
path Dataset root. Omitted, CVFlow downloads and
inspects the coco128 sample.
-f, --format {yolo,coco} Force a format instead of auto-detecting.
--no-images Skip checks that read image bytes (much faster;
also skips corrupt/duplicate/leakage detection).
--no-stats Hide the dataset-statistics section.
--strict Exit non-zero on warnings, not just errors.
dashboard:
--serve Open the findings in a browser dashboard instead
of printing a text report.
--port N Port for --serve (default: first free from 8000).
--host HOST Interface for --serve (default: 127.0.0.1).
--no-browser With --serve, print the URL instead of opening it.
--html FILE Write the dashboard to a self-contained HTML file.
cvflow --version Print the version.
cvflow inspect --help Show this list in your terminal.
Exit codes: 0 clean · 1 problems found (errors, or warnings under --strict)
· 2 bad usage · 3 path not found · 4 the dataset couldn't be loaded.
Common recipes
# Kick the tyres with no dataset of your own
cvflow inspect --serve
# Fast structural check on a huge dataset (skips reading image bytes)
cvflow inspect ./dataset --no-images
# Gate a CI job on data quality
cvflow inspect ./dataset --strict
# Force the format when auto-detection can't tell
cvflow inspect ./dataset --format coco
# Share the dashboard with a teammate on your network
cvflow inspect ./dataset --serve --host 0.0.0.0 --port 9000
Design philosophy
Don't tell developers their dataset is wrong. Show them what looks suspicious, explain why, and let them decide.
That principle is baked into the tool. Severity is used honestly: ERROR means
something is objectively broken, WARNING means "worth a look", and INFO is an
observation. Statistical oddities and duplicates are never hard errors; they're
candidates for review, phrased that way on purpose. The dashboard's accuracy
estimate is labeled an estimate, with its formula on screen.
Roadmap
- ✅ Project foundation: CLI, packaging, model, tests, CI
- ✅ Dataset loaders: YOLO & COCO → one normalized model
- ✅ Integrity analysis: corrupt images, missing/invalid annotations
- ✅ Annotation analysis: bounding-box validation & anomalies
- ✅ Dataset statistics: distributions & outlier detection
- ✅ Duplicate detection: exact + perceptual hashing
- ✅ Split-leakage detection: cross-split similarity
- ✅ Dashboard: one browser page for the whole report
- ✅ Visualization: eyeball the flagged samples
How it fits together
Dataset ─▶ Loaders ─▶ Normalized model ─▶ Analysis engine ─▶ Issues ─┬─▶ Report (text)
(cvflow.model) ├─ integrity │
├─ annotations └─▶ Design (dashboard)
├─ statistics
├─ duplicates
└─ leakage
Everything downstream speaks one normalized model and one Issue type, so a new
format, rule, or output slots in without touching the rest. Details in
docs/architecture.md.
Development
git clone https://github.com/RizwanMunawar/cvflow
cd cvflow
pip install -e ".[dev]"
ruff check . # lint
ruff format . # format
mypy # type-check (strict)
pytest # tests
Contributions are welcome. See CONTRIBUTING.md.
License
CVFlow is released under the MIT License.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cvflow-0.0.3.tar.gz.
File metadata
- Download URL: cvflow-0.0.3.tar.gz
- Upload date:
- Size: 311.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d78fc44aee6aa53f6422a6924dd8c738b66d5f2203f3d5ce52630fe90526ece
|
|
| MD5 |
c6177dc595dfd3e8b63c860c32b95c3f
|
|
| BLAKE2b-256 |
94e5c9856f52c75c7a040d2d0440f1dcde06d38939c908842eabd3ab1a2f9e30
|
Provenance
The following attestation bundles were made for cvflow-0.0.3.tar.gz:
Publisher:
publish.yml on RizwanMunawar/cvflow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cvflow-0.0.3.tar.gz -
Subject digest:
8d78fc44aee6aa53f6422a6924dd8c738b66d5f2203f3d5ce52630fe90526ece - Sigstore transparency entry: 2434953689
- Sigstore integration time:
-
Permalink:
RizwanMunawar/cvflow@c5bd4fe82a35aa040b187b65e30892c313c55ff6 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/RizwanMunawar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c5bd4fe82a35aa040b187b65e30892c313c55ff6 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file cvflow-0.0.3-py3-none-any.whl.
File metadata
- Download URL: cvflow-0.0.3-py3-none-any.whl
- Upload date:
- Size: 300.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
40b0c1b0a19bd09bce081fff3a6a0b228bf2b63c179d7c6fee9beb831b27dc94
|
|
| MD5 |
3561152932fc77f23b40ce5824fe3200
|
|
| BLAKE2b-256 |
16bd903762b7b414bb593596184f4158c1e6f7aa18b2cd8ba46565a31c41de1d
|
Provenance
The following attestation bundles were made for cvflow-0.0.3-py3-none-any.whl:
Publisher:
publish.yml on RizwanMunawar/cvflow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cvflow-0.0.3-py3-none-any.whl -
Subject digest:
40b0c1b0a19bd09bce081fff3a6a0b228bf2b63c179d7c6fee9beb831b27dc94 - Sigstore transparency entry: 2434953759
- Sigstore integration time:
-
Permalink:
RizwanMunawar/cvflow@c5bd4fe82a35aa040b187b65e30892c313c55ff6 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/RizwanMunawar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c5bd4fe82a35aa040b187b65e30892c313c55ff6 -
Trigger Event:
workflow_dispatch
-
Statement type: