UNPKG

@thi.ng/pixel-analysis

Version:

Image color & feature analysis utilities

111 lines (110 loc) 3.35 kB
import { ensureArray } from "@thi.ng/arrays/ensure-array"; import { compareByKey } from "@thi.ng/compare"; import { TAU } from "@thi.ng/math/api"; import { roundTo } from "@thi.ng/math/prec"; import { smoothStep } from "@thi.ng/math/step"; import { circularRange } from "@thi.ng/metrics/metrics"; import { normFrequenciesAuto, repeat, transduce } from "@thi.ng/transducers"; import { map } from "@thi.ng/transducers/map"; import { mapcat } from "@thi.ng/transducers/mapcat"; import { mean } from "@thi.ng/transducers/mean"; import { circularMean } from "@thi.ng/vectors/circular"; function* selectHueRange(colors, minHue, maxHue, minSat) { const pred = __hueSelector(minHue, maxHue); for (let col of colors) { if (col[1] >= minSat && pred(col[0])) yield col; } } function* selectHueRangeIDs(colors, minHue, maxHue, minSat) { const pred = __hueSelector(minHue, maxHue); let id = 0; for (let col of colors) { if (col[1] >= minSat && pred(col[0])) yield id; id++; } } const countHueRange = (colors, minHue, maxHue, minSat) => { const pred = __hueSelector(minHue, maxHue); let count = 0; for (let col of colors) { if (col[1] >= minSat && pred(col[0])) count++; } return count; }; const __hueSelector = (min, max) => min <= max ? (h) => h >= min && h < max : (h) => h >= min || h < max; const hueRangeArea = (colors, hueRanges, minSat = 0.2) => { const $img = ensureArray(colors); const selected = new Set( mapcat((range) => selectHueRangeIDs($img, ...range, minSat), hueRanges) ); return selected.size / $img.length; }; const hueRangeAreaIntensity = (colors, hueRanges, minSat = 0.2) => { const $colors = ensureArray(colors); const selected = new Set( mapcat( (range) => selectHueRangeIDs($colors, ...range, minSat), hueRanges ) ); const area = selected.size / $colors.length; const intensity = transduce( map((id) => { const color = $colors[id]; return color[1] * color[2]; }), mean(), selected ); return intensity * area; }; const meanIntensity = (colors) => transduce( map((x) => x[1] * x[2]), mean(), colors ); const hueRange = circularRange; const temperature = (colors, minSat = 0.2, coeffs) => { const $colors = ensureArray(colors); const filtered = $colors.filter((x) => x[1] >= minSat); const area = filtered.length / $colors.length; const hues = [ ...transduce( map((x) => roundTo(x[0], 1 / 12) % 1), normFrequenciesAuto(), filtered ) ].sort(compareByKey(0)); const angles = [ ...mapcat(([hue, num]) => { num *= 50; return num >= 1 ? repeat(hue * TAU, num) : null; }, hues) ]; if (!angles.length) { return { hues, meanHue: 0, temp: 0, areaTemp: 0, area: 0 }; } const meanHue = circularMean(angles) / TAU; const temp = hueTemperature(meanHue, coeffs); const areaTemp = temp * area; return { hues, meanHue, temp, areaTemp, area }; }; const DEFAULT_TEMPERATURE_COEFFS = [ 0.1, 0.6, 0.72, 0.92 ]; const hueTemperature = (hue, [a, b, c, d] = DEFAULT_TEMPERATURE_COEFFS) => 2 * (hue < 2 / 3 ? smoothStep(b, a, hue) : smoothStep(c, d, hue)) - 1; export { DEFAULT_TEMPERATURE_COEFFS, countHueRange, hueRange, hueRangeArea, hueRangeAreaIntensity, hueTemperature, meanIntensity, selectHueRange, selectHueRangeIDs, temperature };