← All tasks
cppclaude-code/cpp-t1 #21Not a task: repair not recorded

Image Histogram Analyzer (cpp, written by Claude Code)

envgap__claude-code__cpp-t1-21

Written by a coding agent; not on GitHubWritten 2026-02-27

01 / FAILURE SIGNATURE

As the study recorded it

Could not find OpenCV - libopencv-dev not in Docker
Not a benchmark task.
  • It was made to work, but its repair cannot be rebuilt from the saved files (the saved copy shows no change, or not all of the changes the study's notes describe), so there is no fix to score against.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
CMakeLists.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

claude-code/cpp-t1 #21 · read the task the agent was given
Claude Code wrote this cpp project from the task below. It does not run on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: Image Histogram Analyzer

Write a program that computes and analyzes color histograms of images, providing statistical analysis of color distribution, channel comparisons, and similarity scoring between images.

FUNCTIONAL REQUIREMENTS:
- Accept an image file path as a command-line argument
- Compute per-channel histograms (Red, Green, Blue) with 256 bins each, plus a luminance/grayscale histogram
- Calculate statistics for each channel: mean intensity, median, standard deviation, skewness, dominant intensity ranges, and dynamic range (difference between darkest and brightest used values)
- Detect if an image is overexposed (high mean, clipped highlights), underexposed (low mean, clipped shadows), or low contrast (narrow histogram spread)
- Support histogram comparison between two images via --compare flag: compute correlation, chi-squared distance, intersection, and Bhattacharyya distance between their histograms
- Support cumulative histogram computation for each channel via --cumulative flag
- Generate a histogram data output as a CSV file with columns (bin, red_count, green_count, blue_count, luminance_count) via --export flag
- Support analyzing specific regions of an image via --crop flag (x,y,width,height)
- Print a text-based summary to console: per-channel statistics, exposure assessment, contrast assessment, and color balance analysis
- Save the full analysis as JSON with --output flag (default: histogram_analysis.json)
- Support batch analysis of multiple images via --batch flag with a summary comparison table
- If no input is given, generate three sample images (one overexposed, one underexposed, one well-balanced), analyze each, and display comparative results
- Handle errors: unsupported image formats, corrupted files, grayscale images (single-channel analysis)

Create a complete C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include:
- Source code
- CMakeLists.txt with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

3 files, exactly as written, before any repair.

CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(HistogramAnalyzer VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Find OpenCV
find_package(OpenCV 4.9.0 REQUIRED)

# Main executable
add_executable(histogram_analyzer main.cpp)

target_link_libraries(histogram_analyzer PRIVATE
    ${OpenCV_LIBS}
)

target_include_directories(histogram_analyzer PRIVATE
    ${OpenCV_INCLUDE_DIRS}
)

# Installation
install(TARGETS histogram_analyzer DESTINATION bin)
main.cpp
/**
 * Image Histogram Analyzer
 * Computes per-channel color histograms, detects exposure issues,
 * and compares histograms between images.
 *
 * Dependencies: OpenCV 4.9.0
 */

#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <cmath>
#include <map>
#include <iomanip>
#include <algorithm>

struct ChannelHistogram {
    cv::Mat red;
    cv::Mat green;
    cv::Mat blue;
    cv::Mat luminance;
    int totalPixels;
};

struct ChannelStats {
    double mean;
    int median;
    int mode;
    double stdDev;
    int totalPixels;
};

struct ExposureAnalysis {
    std::string severity;
    std::vector<std::string> issues;
    double shadowFraction;
    double midtoneFraction;
    double highlightFraction;
    double shadowClipping;
    double highlightClipping;
};

struct ComparisonMetrics {
    double correlation;
    double chiSquared;
    double bhattacharyya;
    double earthMoversDistance;
    double intersection;
};

/**
 * Compute per-channel color histograms for an image.
 */
ChannelHistogram computeHistogram(const std::string& imagePath) {
    cv::Mat image = cv::imread(imagePath, cv::IMREAD_COLOR);
    if (image.empty()) {
        throw std::runtime_error("Failed to read image: " + imagePath);
    }

    ChannelHistogram result;
    result.totalPixels = image.rows * image.cols;

    // Split into BGR channels
    std::vector<cv::Mat> channels;
    cv::split(image, channels);

    // Compute histograms for each channel
    int histSize = 256;
    float range[] = {0, 256};
    const float* histRange = {range};

    cv::calcHist(&channels[2], 1, 0, cv::Mat(), result.red, 1, &histSize, &histRange);
    cv::calcHist(&channels[1], 1, 0, cv::Mat(), result.green, 1, &histSize, &histRange);
    cv::calcHist(&channels[0], 1, 0, cv::Mat(), result.blue, 1, &histSize, &histRange);

    // Compute luminance
    cv::Mat gray;
    cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
    cv::calcHist(&gray, 1, 0, cv::Mat(), result.luminance, 1, &histSize, &histRange);

    return result;
}

/**
 * Compute statistical measures for a histogram.
 */
ChannelStats computeStatistics(const cv::Mat& hist, int totalPixels) {
    ChannelStats stats;
    stats.totalPixels = totalPixels;

    // Mean
    double mean = 0;
    for (int i = 0; i < 256; i++) {
        mean += i * hist.at<float>(i);
    }
    mean /= totalPixels;
    stats.mean = mean;

    // Variance and standard deviation
    double variance = 0;
    for (int i = 0; i < 256; i++) {
        variance += std::pow(i - mean, 2) * hist.at<float>(i);
    }
    variance /= totalPixels;
    stats.stdDev = std::sqrt(variance);

    // Median
    double cumulative = 0;
    stats.median = 0;
    for (int i = 0; i < 256; i++) {
        cumulative += hist.at<float>(i);
        if (cumulative >= totalPixels / 2.0) {
            stats.median = i;
            break;
        }
    }

    // Mode
    stats.mode = 0;
    float maxCount = 0;
    for (int i = 0; i < 256; i++) {
        if (hist.at<float>(i) > maxCount) {
            maxCount = hist.at<float>(i);
            stats.mode = i;
        }
    }

    return stats;
}

/**
 * Detect exposure issues based on luminance histogram.
 */
ExposureAnalysis detectExposureIssues(const cv::Mat& lumHist, int totalPixels, double threshold = 0.25) {
    ExposureAnalysis result;
    result.severity = "normal";

    double shadowPixels = 0, midtonePixels = 0, highlightPixels = 0;
    double shadowClipping = 0, highlightClipping = 0;

    for (int i = 0; i < 256; i++) {
        double fraction = lumHist.at<float>(i) / totalPixels;
        if (i < 64) shadowPixels += fraction;
        else if (i < 192) midtonePixels += fraction;
        else highlightPixels += fraction;

        if (i < 5) shadowClipping += fraction;
        if (i >= 251) highlightClipping += fraction;
    }

    result.shadowFraction = shadowPixels;
    result.midtoneFraction = midtonePixels;
    result.highlightFraction = highlightPixels;
    result.shadowClipping = shadowClipping;
    result.highlightClipping = highlightClipping;

    if (shadowPixels > threshold && highlightPixels < 0.1) {
        result.issues.push_back("Image appears underexposed (heavy shadows)");
        result.severity = "underexposed";
    }
    if (highlightPixels > threshold && shadowPixels < 0.1) {
        result.issues.push_back("Image appears overexposed (heavy highlights)");
        result.severity = "overexposed";
    }
    if (shadowClipping > 0.05) {
        char buf[128];
        snprintf(buf, sizeof(buf), "Shadow clipping detected (%.1f%% of pixels)",
                 shadowClipping * 100);
        result.issues.push_back(buf);
    }
    if (highlightClipping > 0.05) {
        char buf[128];
        snprintf(buf, sizeof(buf), "Highlight clipping detected (%.1f%% of pixels)",
                 highlightClipping * 100);
        result.issues.push_back(buf);
    }

    // Contrast check
    double mean = 0;
    for (int i = 0; i < 256; i++) {
        mean += i * lumHist.at<float>(i);
    }
    mean /= totalPixels;

    double variance = 0;
    for (int i = 0; i < 256; i++) {
        variance += std::pow(i - mean, 2) * lumHist.at<float>(i);
    }
    variance /= totalPixels;
    double stdDev = std::sqrt(variance);

    if (stdDev < 30) {
        char buf[128];
        snprintf(buf, sizeof(buf), "Low contrast detected (std dev: %.1f)", stdDev);
        result.issues.push_back(buf);
        if (result.severity == "normal") result.severity = "low_contrast";
    }
    if (stdDev > 80) {
        char buf[128];
        snprintf(buf, sizeof(buf), "High contrast detected (std dev: %.1f)", stdDev);
        result.issues.push_back(buf);
        if (result.severity == "normal") result.severity = "high_contrast";
    }

    if (result.issues.empty()) {
        result.issues.push_back("No exposure issues detected");
    }

    return result;
}

/**
 * Compare two histograms using multiple metrics.
 */
ComparisonMetrics compareHistograms(const cv::Mat& h1, const cv::Mat& h2) {
    ComparisonMetrics metrics;

    // Normalize histograms
    cv::Mat h1Norm, h2Norm;
    cv::normalize(h1, h1Norm, 1.0, 0, cv::NORM_L1);
    cv::normalize(h2, h2Norm, 1.0, 0, cv::NORM_L1);

    // Correlation
    metrics.correlation = cv::compareHist(h1Norm, h2Norm, cv::HISTCMP_CORREL);

    // Chi-squared
    metrics.chiSquared = cv::compareHist(h1Norm, h2Norm, cv::HISTCMP_CHISQR);

    // Bhattacharyya
    metrics.bhattacharyya = cv::compareHist(h1Norm, h2Norm, cv::HISTCMP_BHATTACHARYYA);

    // Intersection
    metrics.intersection = cv::compareHist(h1Norm, h2Norm, cv::HISTCMP_INTERSECT);

    // Earth Mover's Distance (manual 1D Wasserstein)
    double emd = 0;
    double cdf1 = 0, cdf2 = 0;
    for (int i = 0; i < 256; i++) {
        cdf1 += h1Norm.at<float>(i);
        cdf2 += h2Norm.at<float>(i);
        emd += std::abs(cdf1 - cdf2);
    }
    metrics.earthMoversDistance = emd;

    return metrics;
}

/**
 * Plot histogram using OpenCV drawing functions and save as image.
 */
void plotHistogram(const ChannelHistogram& histData, const std::string& title,
                   const std::string& outputPath) {
    int histW = 800, histH = 600;
    int binW = histW / 256;
    cv::Mat histImage(histH, histW, CV_8UC3, cv::Scalar(255, 255, 255));

    // Normalize all channels for display
    cv::Mat rNorm, gNorm, bNorm, lNorm;
    cv::normalize(histData.red, rNorm, 0, histH - 50, cv::NORM_MINMAX);
    cv::normalize(histData.green, gNorm, 0, histH - 50, cv::NORM_MINMAX);
    cv::normalize(histData.blue, bNorm, 0, histH - 50, cv::NORM_MINMAX);
    cv::normalize(histData.luminance, lNorm, 0, histH - 50, cv::NORM_MINMAX);

    // Draw histograms
    for (int i = 1; i < 256; i++) {
        // Red
        cv::line(histImage,
                 cv::Point((i - 1) * binW, histH - 30 - cvRound(rNorm.at<float>(i - 1))),
                 cv::Point(i * binW, histH - 30 - cvRound(rNorm.at<float>(i))),
                 cv::Scalar(0, 0, 255), 1);
        // Green
        cv::line(histImage,
                 cv::Point((i - 1) * binW, histH - 30 - cvRound(gNorm.at<float>(i - 1))),
                 cv::Point(i * binW, histH - 30 - cvRound(gNorm.at<float>(i))),
                 cv::Scalar(0, 180, 0), 1);
        // Blue
        cv::line(histImage,
                 cv::Point((i - 1) * binW, histH - 30 - cvRound(bNorm.at<float>(i - 1))),
                 cv::Point(i * binW, histH - 30 - cvRound(bNorm.at<float>(i))),
                 cv::Scalar(255, 0, 0), 1);
        // Luminance
        cv::line(histImage,
                 cv::Point((i - 1) * binW, histH - 30 - cvRound(lNorm.at<float>(i - 1))),
                 cv::Point(i * binW, histH - 30 - cvRound(lNorm.at<float>(i))),
                 cv::Scalar(128, 128, 128), 1);
    }

    // Title
    cv::putText(histImage, title, cv::Point(10, 25),
                cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 0, 0), 1);

    // Legend
    cv::putText(histImage, "R", cv::Point(histW - 80, 20),
                cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 255), 1);
    cv::putText(histImage, "G", cv::Point(histW - 60, 20),
                cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 180, 0), 1);
    cv::putText(histImage, "B", cv::Point(histW - 40, 20),
                cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 0, 0), 1);
    cv::putText(histImage, "L", cv::Point(histW - 20, 20),
                cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(128, 128, 128), 1);

    cv::imwrite(outputPath, histImage);
    std::cout << "Histogram plot saved to: " << outputPath << std::endl;
}

/**
 * Print analysis report to console.
 */
void printReport(const std::string& imagePath, const ChannelHistogram& histData) {
    std::cout << "\n" << std::string(60, '=') << std::endl;
    std::cout << "Image Histogram Analysis Report" << std::endl;
    std::cout << "File: " << imagePath << std::endl;
    std::cout << std::string(60, '=') << std::endl;

    std::cout << "\nChannel Statistics:" << std::endl;
    std::cout << std::left << std::setw(12) << "Channel"
              << std::right << std::setw(8) << "Mean"
              << std::setw(8) << "Median"
              << std::setw(8) << "Mode"
              << std::setw(8) << "StdDev" << std::endl;
    std::cout << std::string(48, '-') << std::endl;

    const cv::Mat* hists[] = {&histData.red, &histData.green, &histData.blue, &histData.luminance};
    const char* names[] = {"Red", "Green", "Blue", "Luminance"};

    for (int ch = 0; ch < 4; ch++) {
        ChannelStats stats = computeStatistics(*hists[ch], histData.totalPixels);
        std::cout << std::left << std::setw(12) << names[ch]
                  << std::right << std::fixed << std::setprecision(1)
                  << std::setw(8) << stats.mean
                  << std::setw(8) << stats.median
                  << std::setw(8) << stats.mode
                  << std::setw(8) << stats.stdDev << std::endl;
    }

    ExposureAnalysis exposure = detectExposureIssues(histData.luminance, histData.totalPixels);

    std::cout << "\nExposure Analysis:" << std::endl;
    std::cout << "  Severity: " << exposure.severity << std::endl;
    for (const auto& issue : exposure.issues) {
        std::cout << "  - " << issue << std::endl;
    }
    std::cout << std::fixed << std::setprecision(1);
    std::cout << "  Shadows:    " << exposure.shadowFraction * 100
              << "% (clipping: " << exposure.shadowClipping * 100 << "%)" << std::endl;
    std::cout << "  Midtones:   " << exposure.midtoneFraction * 100 << "%" << std::endl;
    std::cout << "  Highlights: " << exposure.highlightFraction * 100
              << "% (clipping: " << exposure.highlightClipping * 100 << "%)" << std::endl;
}

int main(int argc, char* argv[]) {
    if (argc < 3) {
        std::cout << "Image Histogram Analyzer" << std::endl;
        std::cout << "Usage:" << std::endl;
        std::cout << "  " << argv[0] << " analyze <image> [--plot output.png]" << std::endl;
        std::cout << "  " << argv[0] << " compare <image1> <image2> [--plot output.png]" << std::endl;
        return 0;
    }

    std::string command = argv[1];

    try {
        if (command == "analyze") {
            std::string imagePath = argv[2];
            ChannelHistogram histData = computeHistogram(imagePath);
            printReport(imagePath, histData);

            // Check for --plot flag
            for (int i = 3; i < argc - 1; i++) {
                if (std::string(argv[i]) == "--plot") {
                    plotHistogram(histData, "Histogram: " + imagePath, argv[i + 1]);
                    break;
                }
            }
        } else if (command == "compare") {
            if (argc < 4) {
                std::cerr << "Error: compare requires two image paths" << std::endl;
                return 1;
            }

            std::string imagePath1 = argv[2];
            std::string imagePath2 = argv[3];

            ChannelHistogram hist1 = computeHistogram(imagePath1);
            ChannelHistogram hist2 = computeHistogram(imagePath2);

            printReport(imagePath1, hist1);
            printReport(imagePath2, hist2);

            std::cout << "\n" << std::string(60, '=') << std::endl;
            std::cout << "Histogram Comparison Metrics" << std::endl;
            std::cout << std::string(60, '=') << std::endl;

            const cv::Mat* h1s[] = {&hist1.red, &hist1.green, &hist1.blue, &hist1.luminance};
            const cv::Mat* h2s[] = {&hist2.red, &hist2.green, &hist2.blue, &hist2.luminance};
            const char* names[] = {"Red", "Green", "Blue", "Luminance"};

            for (int ch = 0; ch < 4; ch++) {
                ComparisonMetrics metrics = compareHistograms(*h1s[ch], *h2s[ch]);
                std::cout << "\n  " << names[ch] << ":" << std::endl;
                std::cout << std::fixed << std::setprecision(4);
                std::cout << "    Correlation:           " << metrics.correlation << std::endl;
                std::cout << "    Chi-Squared Distance:  " << metrics.chiSquared << std::endl;
                std::cout << "    Bhattacharyya Dist:    " << metrics.bhattacharyya << std::endl;
                std::cout << "    Earth Mover's Dist:    " << metrics.earthMoversDistance << std::endl;
                std::cout << "    Intersection:          " << metrics.intersection << std::endl;
            }

            // Check for --plot flag
            for (int i = 4; i < argc - 1; i++) {
                if (std::string(argv[i]) == "--plot") {
                    plotHistogram(hist1, "Comparison: Image 1", argv[i + 1]);
                    break;
                }
            }
        } else {
            std::cerr << "Unknown command: " << command << std::endl;
            return 1;
        }
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}
README.md
# Image Histogram Analyzer (C++ - OpenCV)

Computes per-channel color histograms, detects exposure issues, and compares histograms between images.

## Dependencies
- OpenCV 4.9.0

## Build
```bash
mkdir build && cd build
cmake ..
make
```

## Usage
```bash
./histogram_analyzer analyze <image> [--plot output.png]
./histogram_analyzer compare <image1> <image2> [--plot output.png]
```