Image Histogram Analyzer (java, written by Codex)
envgap__codex__java-t1-21
Written by a coding agent; not on GitHubWritten 2026-03-03
01 / FAILURE SIGNATURE
As the study recorded it
None
Not a benchmark task.
- The project already builds and runs before the fix, so there is nothing to repair.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
pom.xml- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/java-t1 #21 · read the task the agent was given
Codex wrote this java project from the task below. It installed and ran 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 Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include: - Source code - pom.xml 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.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.tmlr</groupId>
<artifactId>image-histogram-analyzer</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-bmp</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>org.sejda.imageio</groupId>
<artifactId>webp-imageio</artifactId>
<version>0.1.6</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifest>
<mainClass>ImageHistogramAnalyzer</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
README.md
# Image Histogram Analyzer (Java) Computes per-channel and luminance histograms, statistical summaries, exposure/contrast detection, histogram comparison metrics, CSV export, JSON report output, and batch analysis. ## Requirements - Ubuntu 22.04 - JDK 17+ - Maven 3.8+ ## Dependencies (Pinned) - `com.twelvemonkeys.imageio:imageio-core:3.11.0` - `com.twelvemonkeys.imageio:imageio-jpeg:3.11.0` - `com.twelvemonkeys.imageio:imageio-bmp:3.11.0` - `com.twelvemonkeys.imageio:imageio-tiff:3.11.0` - `org.sejda.imageio:webp-imageio:0.1.6` - `com.google.code.gson:gson:2.11.0` ## Build ```bash mvn clean package ``` ## Run ```bash java -cp target/image-histogram-analyzer-1.0.0.jar ImageHistogramAnalyzer input.jpg java -cp target/image-histogram-analyzer-1.0.0.jar ImageHistogramAnalyzer input.jpg --crop 50,50,400,300 --cumulative --export histogram.csv --output analysis.json java -cp target/image-histogram-analyzer-1.0.0.jar ImageHistogramAnalyzer input.jpg --compare other.jpg java -cp target/image-histogram-analyzer-1.0.0.jar ImageHistogramAnalyzer ./images --batch --output batch_histogram_analysis.json java -cp target/image-histogram-analyzer-1.0.0.jar ImageHistogramAnalyzer ``` ## Output - Console summary with per-channel stats, exposure assessment, contrast assessment, and color balance analysis. - JSON report defaults to `histogram_analysis.json`. - CSV schema: `bin,red_count,green_count,blue_count,luminance_count`. - No-input mode generates overexposed, underexposed, and balanced samples and prints comparative output.
src/main/java/ImageHistogramAnalyzer.java
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
public final class ImageHistogramAnalyzer {
private static final Set<String> SUPPORTED = Set.of("png", "jpg", "jpeg", "bmp", "tif", "tiff", "webp");
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private record ParsedArgs(Map<String, String> options, List<String> positional) {}
private record CropRect(int x, int y, int width, int height) {}
private static final class DominantRange {
String range;
long count;
}
private static final class ChannelStats {
double mean;
double median;
double stddev;
double skewness;
List<DominantRange> dominantRanges;
int dynamicRange;
}
private static final class Assessment {
String exposure;
boolean lowContrast;
double clippedHighlightsRatio;
double clippedShadowsRatio;
}
private static final class MeanDifferences {
double rg;
double rb;
double gb;
}
private static final class ColorBalance {
boolean balanced;
String dominantChannel;
MeanDifferences meanDifferences;
}
private static final class Histograms {
int[] red;
int[] green;
int[] blue;
int[] luminance;
}
private static final class ImageAnalysis {
String image;
int width;
int height;
CropRect crop;
boolean grayscale;
Map<String, ChannelStats> stats;
Assessment assessment;
ColorBalance colorBalance;
Histograms histograms;
Map<String, int[]> cumulativeHistograms;
}
private static final class ComparisonMetrics {
double correlation;
double chiSquared;
double intersection;
double bhattacharyya;
}
private static final class ComparisonResult {
String targetImage;
ComparisonMetrics red;
ComparisonMetrics green;
ComparisonMetrics blue;
ComparisonMetrics luminance;
}
private ImageHistogramAnalyzer() {}
public static void main(String[] args) {
try {
ParsedArgs parsed = parseArgs(args);
String outputFile = parsed.options.getOrDefault("output", "histogram_analysis.json");
Path outputPath = Paths.get(outputFile).toAbsolutePath();
if (parsed.positional.isEmpty() && !parsed.options.containsKey("batch")) {
runDemo(parsed.options, outputPath);
return;
}
if (parsed.positional.isEmpty()) {
throw new IllegalArgumentException("Input path required.");
}
Path input = Paths.get(parsed.positional.get(0)).toAbsolutePath();
if (parsed.options.containsKey("batch")) {
if (!Files.isDirectory(input)) throw new IllegalArgumentException("--batch requires a directory input.");
List<ImageAnalysis> analyses = new ArrayList<>();
for (Path p : listImages(input)) {
ImageAnalysis analysis = analyzeImage(p, parsed.options);
analyses.add(analysis);
printSummary(analysis);
}
printBatchTable(analyses);
Map<String, Object> payload = new HashMap<>();
payload.put("mode", "batch");
payload.put("analyses", analyses);
writeJson(outputPath, payload);
return;
}
if (!Files.isRegularFile(input)) throw new IllegalArgumentException("Input file not found: " + input);
ImageAnalysis analysis = analyzeImage(input, parsed.options);
printSummary(analysis);
ComparisonResult comparison = null;
if (parsed.options.containsKey("compare")) {
Path otherPath = Paths.get(parsed.options.get("compare")).toAbsolutePath();
ImageAnalysis other = analyzeImage(otherPath, parsed.options);
comparison = new ComparisonResult();
comparison.targetImage = other.image;
comparison.red = compareHistograms(analysis.histograms.red, other.histograms.red);
comparison.green = compareHistograms(analysis.histograms.green, other.histograms.green);
comparison.blue = compareHistograms(analysis.histograms.blue, other.histograms.blue);
comparison.luminance = compareHistograms(analysis.histograms.luminance, other.histograms.luminance);
System.out.println("Comparison metrics (luminance):");
System.out.println(GSON.toJson(comparison.luminance));
System.out.println();
}
if (parsed.options.containsKey("export")) {
exportCsv(Paths.get(parsed.options.get("export")).toAbsolutePath(), analysis);
}
Map<String, Object> payload = new HashMap<>();
payload.put("mode", "single");
payload.put("analysis", analysis);
payload.put("comparison", comparison);
writeJson(outputPath, payload);
} catch (Exception ex) {
System.err.println("Error: " + ex.getMessage());
System.exit(1);
}
}
private static ParsedArgs parseArgs(String[] args) {
Map<String, String> options = new HashMap<>();
List<String> positional = new ArrayList<>();
for (int i = 0; i < args.length; i++) {
String token = args[i];
if (token.startsWith("--")) {
String key = token.substring(2);
if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
options.put(key, args[++i]);
} else {
options.put(key, "true");
}
} else {
positional.add(token);
}
}
return new ParsedArgs(options, positional);
}
private static List<Path> listImages(Path folder) throws Exception {
List<Path> files = new ArrayList<>();
try (var stream = Files.list(folder)) {
stream.filter(Files::isRegularFile).forEach(p -> {
if (SUPPORTED.contains(ext(p))) files.add(p.toAbsolutePath());
});
}
return files;
}
private static String ext(Path p) {
String n = p.getFileName().toString();
int idx = n.lastIndexOf('.');
if (idx < 0) return "";
return n.substring(idx + 1).toLowerCase(Locale.ROOT);
}
private static CropRect parseCrop(String raw, int width, int height) {
if (raw == null || raw.isBlank()) return new CropRect(0, 0, width, height);
String[] parts = raw.split(",", 4);
if (parts.length != 4) throw new IllegalArgumentException("Invalid --crop. Use x,y,width,height.");
int x = Integer.parseInt(parts[0].trim());
int y = Integer.parseInt(parts[1].trim());
int w = Integer.parseInt(parts[2].trim());
int h = Integer.parseInt(parts[3].trim());
if (w <= 0 || h <= 0) throw new IllegalArgumentException("Crop width/height must be positive.");
x = Math.max(0, x);
y = Math.max(0, y);
w = Math.min(w, width - x);
h = Math.min(h, height - y);
if (w <= 0 || h <= 0) throw new IllegalArgumentException("Crop is outside image bounds.");
return new CropRect(x, y, w, h);
}
private static ImageAnalysis analyzeImage(Path imagePath, Map<String, String> options) throws Exception {
if (!SUPPORTED.contains(ext(imagePath))) throw new IllegalArgumentException("Unsupported image format: " + imagePath);
BufferedImage image = ImageIO.read(imagePath.toFile());
if (image == null) throw new IllegalArgumentException("Corrupted/invalid image: " + imagePath);
CropRect crop = parseCrop(options.get("crop"), image.getWidth(), image.getHeight());
int[] red = new int[256];
int[] green = new int[256];
int[] blue = new int[256];
int[] lum = new int[256];
boolean grayscale = true;
for (int y = crop.y; y < crop.y + crop.height; y++) {
for (int x = crop.x; x < crop.x + crop.width; x++) {
int rgb = image.getRGB(x, y);
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
if (grayscale && (r != g || g != b)) grayscale = false;
red[r]++;
green[g]++;
blue[b]++;
int yv = (int) Math.round(0.2126 * r + 0.7152 * g + 0.0722 * b);
lum[yv]++;
}
}
if (grayscale) {
System.arraycopy(red, 0, green, 0, 256);
System.arraycopy(red, 0, blue, 0, 256);
}
ChannelStats redStats = statsFromHist(red);
ChannelStats greenStats = statsFromHist(green);
ChannelStats blueStats = statsFromHist(blue);
ChannelStats lumStats = statsFromHist(lum);
Map<String, ChannelStats> stats = new HashMap<>();
stats.put("red", redStats);
stats.put("green", greenStats);
stats.put("blue", blueStats);
stats.put("luminance", lumStats);
Histograms h = new Histograms();
h.red = red;
h.green = green;
h.blue = blue;
h.luminance = lum;
ImageAnalysis analysis = new ImageAnalysis();
analysis.image = imagePath.toAbsolutePath().toString();
analysis.width = crop.width;
analysis.height = crop.height;
analysis.crop = crop;
analysis.grayscale = grayscale;
analysis.stats = stats;
analysis.assessment = assessExposure(lumStats, lum);
analysis.colorBalance = colorBalance(redStats, greenStats, blueStats);
analysis.histograms = h;
if (options.containsKey("cumulative")) {
Map<String, int[]> cumulative = new HashMap<>();
cumulative.put("red", cumulative(red));
cumulative.put("green", cumulative(green));
cumulative.put("blue", cumulative(blue));
cumulative.put("luminance", cumulative(lum));
analysis.cumulativeHistograms = cumulative;
}
return analysis;
}
private static ChannelStats statsFromHist(int[] hist) {
long total = 0;
for (int v : hist) total += v;
ChannelStats out = new ChannelStats();
if (total == 0) {
out.mean = 0;
out.median = 0;
out.stddev = 0;
out.skewness = 0;
out.dynamicRange = 0;
out.dominantRanges = List.of();
return out;
}
double mean = 0;
for (int i = 0; i < 256; i++) mean += i * (double) hist[i];
mean /= total;
double variance = 0;
for (int i = 0; i < 256; i++) variance += (i - mean) * (i - mean) * hist[i];
variance /= total;
double stddev = Math.sqrt(variance);
double skewness = 0;
if (stddev > 0) {
double m3 = 0;
for (int i = 0; i < 256; i++) m3 += Math.pow(i - mean, 3) * hist[i];
m3 /= total;
skewness = m3 / Math.pow(stddev, 3);
}
long half = total / 2;
long run = 0;
int median = 0;
for (int i = 0; i < 256; i++) {
run += hist[i];
if (run >= half) {
median = i;
break;
}
}
int min = 0;
int max = 255;
while (min < 256 && hist[min] == 0) min++;
while (max >= 0 && hist[max] == 0) max--;
int dynamic = Math.max(0, max - min);
List<DominantRange> ranges = new ArrayList<>();
for (int start = 0; start < 256; start += 16) {
long count = 0;
for (int i = start; i < start + 16; i++) count += hist[i];
DominantRange dr = new DominantRange();
dr.range = start + "-" + (start + 15);
dr.count = count;
ranges.add(dr);
}
ranges.sort((a, b) -> Long.compare(b.count, a.count));
out.mean = mean;
out.median = median;
out.stddev = stddev;
out.skewness = skewness;
out.dynamicRange = dynamic;
out.dominantRanges = ranges.subList(0, 3);
return out;
}
private static int[] cumulative(int[] hist) {
int[] out = new int[hist.length];
int run = 0;
for (int i = 0; i < hist.length; i++) {
run += hist[i];
out[i] = run;
}
return out;
}
private static Assessment assessExposure(ChannelStats luminanceStats, int[] lum) {
long total = 0;
for (int v : lum) total += v;
if (total == 0) total = 1;
double high = lum[255] / (double) total;
double low = lum[0] / (double) total;
String exposure = "normal";
if (luminanceStats.mean > 190 && high > 0.015) exposure = "overexposed";
else if (luminanceStats.mean < 65 && low > 0.015) exposure = "underexposed";
Assessment out = new Assessment();
out.exposure = exposure;
out.lowContrast = luminanceStats.dynamicRange < 80 || luminanceStats.stddev < 35;
out.clippedHighlightsRatio = high;
out.clippedShadowsRatio = low;
return out;
}
private static ColorBalance colorBalance(ChannelStats red, ChannelStats green, ChannelStats blue) {
double rg = Math.abs(red.mean - green.mean);
double rb = Math.abs(red.mean - blue.mean);
double gb = Math.abs(green.mean - blue.mean);
MeanDifferences diffs = new MeanDifferences();
diffs.rg = rg;
diffs.rb = rb;
diffs.gb = gb;
String dominant = "red";
if (green.mean > red.mean && green.mean >= blue.mean) dominant = "green";
else if (blue.mean > red.mean && blue.mean >= green.mean) dominant = "blue";
ColorBalance out = new ColorBalance();
out.balanced = rg < 10 && rb < 10 && gb < 10;
out.dominantChannel = dominant;
out.meanDifferences = diffs;
return out;
}
private static double[] normalize(int[] hist) {
double total = 0;
for (int v : hist) total += v;
double[] out = new double[hist.length];
if (total == 0) return out;
for (int i = 0; i < hist.length; i++) out[i] = hist[i] / total;
return out;
}
private static ComparisonMetrics compareHistograms(int[] histA, int[] histB) {
double[] a = normalize(histA);
double[] b = normalize(histB);
double meanA = 0;
double meanB = 0;
for (int i = 0; i < 256; i++) {
meanA += a[i];
meanB += b[i];
}
meanA /= 256.0;
meanB /= 256.0;
double cov = 0, varA = 0, varB = 0, chi2 = 0, intersection = 0, bc = 0;
for (int i = 0; i < 256; i++) {
double da = a[i] - meanA;
double db = b[i] - meanB;
cov += da * db;
varA += da * da;
varB += db * db;
chi2 += ((a[i] - b[i]) * (a[i] - b[i])) / (a[i] + b[i] + 1e-12);
intersection += Math.min(a[i], b[i]);
bc += Math.sqrt(a[i] * b[i]);
}
ComparisonMetrics out = new ComparisonMetrics();
out.correlation = (varA > 0 && varB > 0) ? cov / Math.sqrt(varA * varB) : 0;
out.chiSquared = chi2;
out.intersection = intersection;
out.bhattacharyya = Math.sqrt(Math.max(0, 1 - bc));
return out;
}
private static void exportCsv(Path csvPath, ImageAnalysis analysis) throws Exception {
Files.createDirectories(csvPath.toAbsolutePath().getParent());
List<String> lines = new ArrayList<>();
lines.add("bin,red_count,green_count,blue_count,luminance_count");
for (int i = 0; i < 256; i++) {
lines.add(i + "," + analysis.histograms.red[i] + "," + analysis.histograms.green[i] + "," +
analysis.histograms.blue[i] + "," + analysis.histograms.luminance[i]);
}
Files.write(csvPath, lines, StandardCharsets.UTF_8);
}
private static void writeJson(Path outputPath, Object payload) throws Exception {
Files.createDirectories(outputPath.toAbsolutePath().getParent());
Files.writeString(outputPath, GSON.toJson(payload), StandardCharsets.UTF_8);
}
private static void printSummary(ImageAnalysis analysis) {
System.out.println("Image: " + analysis.image);
System.out.println("Dimensions: " + analysis.width + "x" + analysis.height + (analysis.grayscale ? " | Grayscale" : ""));
for (String channel : List.of("red", "green", "blue", "luminance")) {
ChannelStats s = analysis.stats.get(channel);
System.out.printf(
"%s: mean=%.3f median=%.3f stddev=%.3f skew=%.3f dynamic=%d%n",
channel.toUpperCase(Locale.ROOT), s.mean, s.median, s.stddev, s.skewness, s.dynamicRange
);
}
System.out.println("Exposure assessment: " + analysis.assessment.exposure);
System.out.println("Contrast assessment: " + (analysis.assessment.lowContrast ? "low contrast" : "normal contrast"));
System.out.println("Color balance: " + (analysis.colorBalance.balanced ? "balanced" : "cast toward " + analysis.colorBalance.dominantChannel));
System.out.println();
}
private static void printBatchTable(List<ImageAnalysis> analyses) {
System.out.println("Batch summary:");
System.out.println("Image | Mean(L) | Std(L) | Exposure | Contrast");
System.out.println("----- | ------- | ------ | -------- | --------");
for (ImageAnalysis analysis : analyses) {
ChannelStats l = analysis.stats.get("luminance");
System.out.printf(
"%s | %.2f | %.2f | %s | %s%n",
Paths.get(analysis.image).getFileName(),
l.mean,
l.stddev,
analysis.assessment.exposure,
analysis.assessment.lowContrast ? "low" : "normal"
);
}
System.out.println();
}
private static void createSample(String kind, Path out) throws Exception {
int width = 640;
int height = 420;
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int base;
if ("overexposed".equals(kind)) base = 210 + ((x + y) % 45);
else if ("underexposed".equals(kind)) base = 15 + ((x + y) % 40);
else base = 65 + ((x * 3 + y * 2) % 130);
int noise = ((x * 17 + y * 31) % 23) - 11;
int v = Math.max(0, Math.min(255, base + noise));
int r = v;
int g = Math.max(0, Math.min(255, v + ("balanced".equals(kind) ? 3 : 0)));
int b = Math.max(0, Math.min(255, v - ("balanced".equals(kind) ? 3 : 0)));
img.setRGB(x, y, new Color(r, g, b).getRGB());
}
}
Files.createDirectories(out.toAbsolutePath().getParent());
ImageIO.write(img, "png", out.toFile());
}
private static void runDemo(Map<String, String> options, Path outputPath) throws Exception {
Path dir = Paths.get("sample_histograms").toAbsolutePath();
Path over = dir.resolve("overexposed.png");
Path under = dir.resolve("underexposed.png");
Path balanced = dir.resolve("balanced.png");
createSample("overexposed", over);
createSample("underexposed", under);
createSample("balanced", balanced);
List<ImageAnalysis> analyses = new ArrayList<>();
analyses.add(analyzeImage(over, options));
analyses.add(analyzeImage(under, options));
analyses.add(analyzeImage(balanced, options));
for (ImageAnalysis analysis : analyses) printSummary(analysis);
printBatchTable(analyses);
Map<String, Object> payload = new HashMap<>();
payload.put("mode", "demo");
payload.put("analyses", analyses);
writeJson(outputPath, payload);
}
}