Skip to content

Repository files navigation

Banner

Display Quality Validation Tool

Automated display defect detection using Python, OpenCV & Pytest

Python OpenCV Pytest License Tests Screenshot 2026-03-24 140319 Screenshot 2026-03-24 140333 Screenshot 2026-03-24 140345 Screenshot 2026-03-24 140420

Detects dead pixels, color anomalies, and brightness variation in display screenshots.
Generates per-image PASS/FAIL reports with a quality score (0-100).

Dec 2025 - Feb 2026


Table of Contents


Overview

This tool automatically validates the visual quality of display screenshots by running three independent defect detectors and producing a unified report:

Category What it detects Pass condition
Dead Pixels Stuck near-black pixels via morphological analysis <= 5 dead pixel clusters
Color Anomaly R/G/B channel imbalance and saturation clipping No channel deviates > 20% from mean
Brightness Variation Luminance non-uniformity across a 3x3 zone grid Std-dev < 40, variation < 30%

Each image receives an overall PASS / FAIL verdict and a quality score from 0 to 100.


Project Structure

Folder Structure

display-quality-validator/
|-- display_validator.py      # Core engine: detectors + ValidationReport
|-- report_generator.py       # HTML QA report builder
|-- conftest.py               # Shared pytest fixtures (image factory)
|-- test_display_quality.py   # 25 test cases across 3 defect categories
|-- requirements.txt          # Pinned dependencies
|-- README.md                 # This file
|-- images/                   # README diagram assets
    |-- banner.png
    |-- architecture.png
    |-- defect_categories.png
    |-- pipeline.png
    |-- test_results.png
    |-- sample_output.png
    |-- folder_structure.png

System Requirements

Requirement Minimum Recommended
Python 3.9 3.11+
OS Windows 10 / Ubuntu 20.04 / macOS 11 Ubuntu 22.04 / macOS 13+
RAM 512 MB 2 GB
Disk 200 MB 500 MB
CPU Any x64 Multi-core for batch mode

Installation

Step 1 - Clone or download the project

# Option A: clone (if using git)
git clone https://github.com/yourname/display-quality-validator.git
cd display-quality-validator

# Option B: unzip (if using the ZIP package)
unzip display-quality-validator.zip
cd display-quality-validator

Step 2 - Create a virtual environment (recommended)

# Create venv
python -m venv venv

# Activate on Linux / macOS
source venv/bin/activate

# Activate on Windows (Command Prompt)
venv\Scripts\activate.bat

# Activate on Windows (PowerShell)
venv\Scripts\Activate.ps1

Step 3 - Install dependencies

pip install -r requirements.txt

What gets installed:

opencv-python >= 4.8.0     # image loading, thresholding, contour detection
numpy         >= 1.24.0    # array operations, luminance calculations
pytest        >= 7.4.0     # test runner
pytest-cov    >= 4.1.0     # code coverage reports
pytest-html   >= 4.0.0     # HTML test result reports
Pillow        >= 10.0.0    # optional: faster batch image I/O

Step 4 - Verify installation

python -c "import cv2, numpy; print('OpenCV:', cv2.__version__, '| NumPy:', numpy.__version__)"
pytest --version

Expected output:

OpenCV: 4.13.0 | NumPy: 2.4.2
pytest 7.4.x

Quick Start

Validate a single screenshot

python display_validator.py path/to/screenshot.png

Validate a full folder of screenshots

python display_validator.py path/to/screenshots/

Generate an HTML QA report

python report_generator.py path/to/screenshots/ qa_report.html

Then open qa_report.html in any browser.

Run the full test suite

pytest test_display_quality.py -v

Run with HTML test report

pytest test_display_quality.py -v --html=test_report.html --self-contained-html

Architecture

Architecture

The tool is built around four main classes:

DeadPixelDetector

Converts the image to grayscale, applies a binary threshold (pixels <= 15 intensity = dead), removes noise with morphological opening, then uses cv2.findContours to identify and count stuck-pixel clusters. Returns a list of DeadPixelDefect objects, each with bounding box location and severity rating.

ColorAnomalyDetector

Splits the BGR image into its three channels and computes the mean value of each. Any channel whose mean deviates more than 20% from the overall image mean is flagged as a ColorDefect. Also separately detects channel saturation clipping (more than 5% of pixels pegged at 255).

BrightnessVariationDetector

Converts the image to a float32 grayscale luminance map. Computes global standard deviation and then divides the image into a 3x3 zone grid, measuring the mean luminance of each zone. Calculates the percentage variation between the brightest and darkest zones. Fails if std > 40 or zone variation > 30%.

DisplayValidator

The orchestrator. Loads each image via cv2.imread, validates its dimensions, runs all three detectors, aggregates results into a ValidationReport dataclass, and computes a quality score. Supports single-image and batch modes, and can produce a JSON summary.


Defect Categories

Defect Categories

Dead Pixel Detection

A dead pixel is a display cell stuck permanently at very low luminance (near-black). The detector uses this OpenCV pipeline:

gray     = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, mask  = cv2.threshold(gray, 15, 255, cv2.THRESH_BINARY_INV)
kernel   = np.ones((1, 1), np.uint8)
clean    = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
contours, _ = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

A CRITICAL defect is raised when the cluster area is greater than 4 pixels. A WARNING is raised for isolated single pixels.

Color Anomaly Detection

Channel imbalance is computed as:

deviation_pct = |channel_mean - overall_mean| / overall_mean x 100

If deviation_pct > 20% for any channel, a ColorDefect is raised. If deviation_pct > 40%, severity is CRITICAL; otherwise WARNING.

Brightness Variation Detection

The zone grid approach:

for r in range(3):
    for c in range(3):
        zone_mean = np.mean(luminance[r*H//3:(r+1)*H//3, c*W//3:(c+1)*W//3])
        zone_means.append(zone_mean)

variation_pct = (max(zone_means) - min(zone_means)) / max(zone_means) * 100

Fails if std > 40 or variation_pct > 30%.


Validation Pipeline

Validation Pipeline

Scoring Formula

score  = 100.0
score -= min(dead_pixel_count    * 5,  40)   # max penalty: -40
score -= min(critical_color      * 10, 30)   # max penalty: -30
score -= min(critical_brightness * 10, 30)   # max penalty: -30
score  = max(0.0, score)
Score range Interpretation
90 - 100 Excellent: display is fully functional
70 - 89 Good: minor anomalies detected
50 - 69 Poor: significant defects present
0 - 49 Critical: display requires attention

Test Suite

Test Results

The test suite contains 25 test cases across 4 test classes, all using synthetically generated images from conftest.py.
No external image files are required.

conftest.py - Image Factory

The session-scoped factory generates 14 types of synthetic test images:

Kind Description
clean_grey Uniform 128-grey 200x200 image
clean_white Uniform 240-white image
clean_dark Uniform 30-dark image
few_dead Grey image with 5 planted dead pixels
many_dead Grey image with 300+ dead pixels
single_dead Single dead pixel at centre
red_cast Dominant red channel (220, 30, 30)
blue_cast Dominant blue channel
green_cast Dominant green channel
balanced_rgb Balanced 150-grey RGB image
h_gradient Left-to-right brightness gradient 0 to 255
v_gradient Top-to-bottom brightness gradient
checkerboard 25px black-and-white checkerboard
vignette Bright centre, dark edges (radial fade)

Test Case Reference

Category 1 - Dead Pixels (TC-DP-01 to TC-DP-06)

ID Test description Expected
TC-DP-01 Clean uniform image reports 0 dead pixels count == 0
TC-DP-02 Image with 10 planted dead pixels detects >= 1 count >= 1
TC-DP-03 Dead pixels within threshold - passes() = True True
TC-DP-04 50+ dead pixels exceeds threshold - passes() = False False
TC-DP-05 Every detected defect has location data (x, y) location != None
TC-DP-06 ValidationReport sets dead_pixel_result = FAIL "FAIL"

Category 2 - Color Anomaly (TC-CA-01 to TC-CA-06)

ID Test description Expected
TC-CA-01 Balanced grey image passes color validation passes() = True
TC-CA-02 Red-biased image flagged as color anomaly R channel detected
TC-CA-03 Blue-biased image flagged with B channel error B channel detected
TC-CA-04 Channel means returned per R / G / B stats["R"]["mean"] > stats["G"]["mean"]
TC-CA-05 Deviation percentage is always >= 0 deviation >= 0.0
TC-CA-06 ValidationReport sets color_result = FAIL "FAIL"

Category 3 - Brightness Variation (TC-BV-01 to TC-BV-06)

ID Test description Expected
TC-BV-01 Uniform grey image passes brightness check passes() = True
TC-BV-02 Hard gradient image fails brightness uniformity passes() = False
TC-BV-03 Luminance stats include mean, std, min, max All 4 keys present
TC-BV-04 Gradient image has luminance std > 50 std > 50
TC-BV-05 Uniform image has luminance std < 5 std < 5
TC-BV-06 ValidationReport sets brightness_result = FAIL "FAIL"

Integration Tests (TC-INT-01 to TC-INT-07)

ID Test description Expected
TC-INT-01 Clean image PASS on all 3 categories, score >= 90 overall = "PASS"
TC-INT-02 Report contains all required fields All 6 fields present
TC-INT-03 Quality score always in range [0, 100] 0 <= score <= 100
TC-INT-04 to_json() produces parseable JSON json.loads() succeeds
TC-INT-05 Missing image raises FileNotFoundError Exception raised
TC-INT-06 Batch validation returns one report per image len(reports) == 2
TC-INT-07 Summary totals equal number of validated images passed + failed == 3

Sample Output

Sample Output

{
  "image_path": "screenshots/screen_02_dead_pixels.png",
  "timestamp": "2026-02-14T10:32:44.812301",
  "width": 400,
  "height": 300,
  "overall_result": "FAIL",
  "dead_pixel_result": "FAIL",
  "color_result": "PASS",
  "brightness_result": "PASS",
  "dead_pixel_count": 20,
  "dead_pixels": [
    {
      "category": "DEAD_PIXEL",
      "location": { "x": 5, "y": 5, "width": 1, "height": 1 },
      "pixel_value": [0, 0, 0],
      "severity": "WARNING"
    }
  ],
  "color_defects": [],
  "brightness_defects": [],
  "score": 0.0
}

Batch summary example:

{
  "total_images": 10,
  "passed": 4,
  "failed": 6,
  "pass_rate_pct": 40.0,
  "avg_score": 83.5,
  "dead_pixel_failures": 2,
  "color_failures": 2,
  "brightness_failures": 2
}

Python API Reference

Single image validation

from display_validator import DisplayValidator

validator = DisplayValidator()
report    = validator.validate("screenshot.png")

print(report.overall_result)      # "PASS" or "FAIL"
print(report.score)               # 0.0 to 100.0
print(report.dead_pixel_count)    # integer
print(report.color_defects)       # list of dicts
print(report.brightness_defects)  # list of dicts
print(report.to_json())           # full JSON string

Batch validation

from display_validator import DisplayValidator

validator = DisplayValidator()
paths     = ["s01.png", "s02.png", "s03.png"]
reports   = validator.validate_batch(paths)
summary   = validator.generate_summary(reports)

print(summary["pass_rate_pct"])   # e.g. 66.7
print(summary["avg_score"])       # e.g. 85.0

Using individual detectors

import cv2
from display_validator import DeadPixelDetector, ColorAnomalyDetector, BrightnessVariationDetector

image = cv2.imread("screenshot.png")

# Dead pixel detector
dp      = DeadPixelDetector(threshold=15)
defects = dp.detect(image)
count   = dp.count(image)
passed  = dp.passes(image)               # True / False

# Color anomaly detector
ca    = ColorAnomalyDetector(deviation_threshold=20.0)
defs  = ca.detect(image)
stats = ca.get_channel_means(image)      # {"R": {"mean":..}, "G":.., "B":..}

# Brightness variation detector
bv    = BrightnessVariationDetector(std_threshold=40.0, variation_pct=30.0, grid_size=(3,3))
defs  = bv.detect(image)
stats = bv.get_luminance_stats(image)    # {"mean":..,"std":..,"min":..,"max":..}

HTML Report Generator

from display_validator import DisplayValidator
from report_generator  import ReportGenerator
from pathlib import Path

paths     = [str(p) for p in Path("screenshots").glob("*.png")]
validator = DisplayValidator()
reports   = validator.validate_batch(paths)

ReportGenerator().save(reports, output_path="qa_report.html")

Or from the command line:

python report_generator.py ./screenshots/ qa_report.html

# Open in browser
open qa_report.html          # macOS
xdg-open qa_report.html      # Linux
start qa_report.html         # Windows

The HTML report includes:

  • Summary metric cards (total, passed, failed, avg score)
  • Per-category failure counts
  • Per-image results table with thumbnails, PASS/FAIL badges, score bars, defect chips
  • Fully self-contained: no internet or external files needed

Thresholds and Configuration

All thresholds are centralised in the Thresholds class inside display_validator.py:

class Thresholds:
    DEAD_PIXEL_INTENSITY      = 15     # grayscale <= this -> dead pixel
    DEAD_PIXEL_MAX_ALLOWED    = 5      # fail if more than N dead pixel clusters
    COLOR_CHANNEL_DEVIATION   = 20.0  # % per-channel imbalance -> flag
    BRIGHTNESS_STD_FAIL       = 40.0  # luminance std-dev -> hard fail
    BRIGHTNESS_STD_WARN       = 25.0  # luminance std-dev -> warning
    BRIGHTNESS_VARIATION_PCT  = 30.0  # % zone variation -> fail
    MIN_IMAGE_DIMENSION       = 10    # minimum valid image side in pixels
    SATURATION_THRESHOLD      = 240   # channel value treated as saturated

To adjust at runtime without editing the file:

from display_validator import DeadPixelDetector, BrightnessVariationDetector

# More strict: fail if any dead pixel at all
dp     = DeadPixelDetector(threshold=20)
result = dp.passes(image, max_allowed=0)

# More lenient brightness check for UI-heavy screenshots
bv = BrightnessVariationDetector(std_threshold=60.0, variation_pct=50.0)

Running with Coverage

# Terminal coverage summary
pytest test_display_quality.py --cov=display_validator --cov-report=term-missing

# HTML coverage report (open htmlcov/index.html in browser)
pytest test_display_quality.py --cov=display_validator --cov-report=html

# Full combined run: HTML test report + coverage together
pytest test_display_quality.py -v \
  --html=test_report.html --self-contained-html \
  --cov=display_validator --cov-report=html

Troubleshooting

ModuleNotFoundError: No module named cv2

pip install opencv-python
# For headless servers or CI environments:
pip install opencv-python-headless

ModuleNotFoundError: No module named pytest

pip install pytest

Image reads as None

import cv2
img = cv2.imread("path/to/image.png")
if img is None:
    print("Check: file exists, path is correct, format supported (PNG/JPG/BMP/TIFF)")

All images failing brightness check

The default std-dev threshold of 40 is tuned for uniform test patterns. Real screenshots with varied UI content will naturally have higher variation. Increase the threshold:

from display_validator import BrightnessVariationDetector
bv = BrightnessVariationDetector(std_threshold=70.0, variation_pct=60.0)

Pytest not finding test file

Run from the project root:

cd display-quality-validator
pytest test_display_quality.py -v

Windows PowerShell execution policy error

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Display Quality Validation Tool  ·  Python · OpenCV · Pytest  ·  Dec 2025 - Feb 2026

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages