import { test } from 'node:test'; import assert from 'node:assert/strict'; import { countObjects } from '../src/count-objects.js'; /** * Baut ein ImageData-artiges Objekt aus einem Raster von Graustufenwerten * (0-255 je Zelle) - derselbe minimale Ersatz wie in ocr-preprocess.test.js, * hier zweidimensional, weil countObjects() auf zusammenhaengende Flaechen * angewiesen ist. * @param {number[][]} rows gleich lange Zeilen von Graustufenwerten */ function image(rows) { const height = rows.length; const width = height > 0 ? rows[0].length : 0; const data = new Uint8ClampedArray(width * height * 4); let i = 0; for (const row of rows) { for (const value of row) { data[i] = value; data[i + 1] = value; data[i + 2] = value; data[i + 3] = 255; i += 4; } } return { width, height, data }; } /** Erzeugt ein width x height grosses Raster, komplett mit `background` gefuellt. */ function grid(width, height, background) { return Array.from({ length: height }, () => new Array(width).fill(background)); } /** Malt ein width x height grosses Quadrat/Rechteck an Position (x, y) mit `value`. */ function paint(rows, x, y, width, height, value) { for (let row = y; row < y + height; row += 1) { for (let col = x; col < x + width; col += 1) { rows[row][col] = value; } } } test('leeres Bild (kein Objekt, gleichmaessiger Untergrund) ergibt null', () => { const rows = grid(20, 20, 60); const result = countObjects(image(rows)); assert.equal(result.count, 0); }); test('eine einzelne Flaeche ergibt eins', () => { const rows = grid(40, 40, 30); paint(rows, 15, 15, 10, 10, 220); const result = countObjects(image(rows)); assert.equal(result.count, 1); }); test('mehrere getrennte, gleich grosse Flaechen ergeben ihre Anzahl', () => { const rows = grid(80, 80, 30); paint(rows, 5, 5, 10, 10, 220); paint(rows, 30, 5, 10, 10, 220); paint(rows, 5, 30, 10, 10, 220); paint(rows, 30, 30, 10, 10, 220); const result = countObjects(image(rows)); assert.equal(result.count, 4); }); test('winzige Stoerflaechen (Staub, Reflexe) werden nicht mitgezaehlt', () => { const rows = grid(80, 80, 30); paint(rows, 5, 5, 10, 10, 220); paint(rows, 30, 5, 10, 10, 220); paint(rows, 5, 30, 10, 10, 220); paint(rows, 30, 30, 10, 10, 220); // Einzelpixel- bis Doppelpixel-Stoerflaechen, weit entfernt von den // echten Flaechen und voneinander - deutlich unter einem Viertel der // typischen Flaechengroesse (100 Bildpunkte). rows[60][60] = 220; rows[65][65] = 220; rows[65][66] = 220; rows[70][10] = 220; const result = countObjects(image(rows)); assert.equal(result.count, 4, 'Stoerflaechen duerfen die Anzahl nicht erhoehen'); }); test('eine Flaeche von etwa vierfacher Einzelgroesse wird als vier gezaehlt', () => { const rows = grid(80, 80, 30); paint(rows, 5, 5, 10, 10, 220); // 100 Bildpunkte, typische Groesse paint(rows, 30, 5, 10, 10, 220); // 100 paint(rows, 5, 30, 10, 10, 220); // 100 // Verschmolzene Flaeche mehrerer beruehrender Teile: 20x20 = 400 = 4x100. paint(rows, 30, 30, 20, 20, 220); const result = countObjects(image(rows)); assert.equal(result.count, 7, '3 Einzelflaechen (je 1) + hochgerechnete Flaeche (4)'); }); test('ein einzelnes, nur leicht groesseres Teil wird nicht als zwei gezaehlt', () => { const rows = grid(80, 80, 30); paint(rows, 5, 5, 10, 10, 220); // 100 paint(rows, 30, 5, 10, 10, 220); // 100 paint(rows, 5, 30, 10, 10, 220); // 100 // Rund 30% groesser als die anderen (11x12=132 statt 100) - haeufiger // Fall, wenn ein Teil im Bild etwas naeher an der Kamera liegt. paint(rows, 30, 30, 11, 12, 220); const result = countObjects(image(rows)); assert.equal(result.count, 4, 'leicht groessere Einzelflaeche zaehlt weiterhin als eins'); }); test('dunkle Objekte auf hellem Grund werden genauso gezaehlt wie helle auf dunklem', () => { const darkOnLight = grid(80, 80, 220); paint(darkOnLight, 5, 5, 10, 10, 30); paint(darkOnLight, 30, 5, 10, 10, 30); paint(darkOnLight, 5, 30, 10, 10, 30); paint(darkOnLight, 30, 30, 10, 10, 30); const lightOnDark = grid(80, 80, 30); paint(lightOnDark, 5, 5, 10, 10, 220); paint(lightOnDark, 30, 5, 10, 10, 220); paint(lightOnDark, 5, 30, 10, 10, 220); paint(lightOnDark, 30, 30, 10, 10, 220); const darkResult = countObjects(image(darkOnLight)); const lightResult = countObjects(image(lightOnDark)); assert.equal(darkResult.count, 4); assert.equal(lightResult.count, 4); }); test('ein Bild ohne Bildpunkte stuerzt nicht ab', () => { assert.doesNotThrow(() => { const result = countObjects({ width: 0, height: 0, data: new Uint8ClampedArray(0) }); assert.equal(result.count, 0); }); }); test('zwei aneinanderliegende, aber nur diagonal beruehrende Flaechen zaehlen getrennt (4er-Nachbarschaft)', () => { const rows = grid(40, 40, 30); // Zwei 5x5-Quadrate, die sich nur in einer Ecke (ein Bildpunkt) beruehren. paint(rows, 10, 10, 5, 5, 220); paint(rows, 15, 15, 5, 5, 220); const result = countObjects(image(rows)); assert.equal(result.count, 2, 'nur diagonal beruehrende Flaechen bleiben getrennt'); });