@thi.ng/pixel-analysis
Version:
Image color & feature analysis utilities
111 lines (110 loc) • 3.35 kB
JavaScript
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
};