Log density visualization

This commit is contained in:
2024-12-01 21:57:10 -05:00
parent 79b66337e8
commit 2e8a6d1ce7
20 changed files with 206 additions and 194 deletions

View File

@ -1,30 +1,27 @@
import {VictoryChart, VictoryLine, VictoryScatter, VictoryTheme} from "victory";
import {useContext, useEffect, useState} from "react";
import React, {useContext, useEffect} from "react";
import * as params from "../src/params";
import {PainterContext} from "../src/Canvas";
import {chaosGameHistogram} from "./chaosGameHistogram";
import {PlotData, plotHistogram} from "./plotHistogram";
import {chaosGameHistogram} from "@site/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameHistogram";
function* plotChaosGame(width: number, height: number, setPdf: (data: PlotData) => void, setCdf: (data: PlotData) => void) {
const emptyImage = new ImageData(width, height);
for (let histogram of chaosGameHistogram(width, height)) {
const plotData = plotHistogram(histogram);
setPdf(plotData);
yield emptyImage;
}
type Props = {
quality?: number;
paintFn: (width: number, histogram: Uint32Array) => ImageData;
children?: React.ReactElement;
}
export default function FlameHistogram() {
export default function FlameHistogram({quality, paintFn, children}: Props) {
const {width, height, setPainter} = useContext(PainterContext);
const [pdfData, setPdfData] = useState<{ x: number, y: number }[]>(null);
useEffect(() => setPainter(plotChaosGame(width, height, setPdfData, null)), []);
useEffect(() => {
const gameParams = {
width,
height,
transforms: params.xforms,
final: params.xformFinal,
quality,
painter: paintFn
}
setPainter(chaosGameHistogram(gameParams));
}, []);
return (
<VictoryChart theme={VictoryTheme.clean}>
<VictoryLine
data={pdfData}
interpolation='natural'
/>
</VictoryChart>
)
return children;
}

View File

@ -1,34 +1,34 @@
import {plot} from "./plotHistogram";
// hidden-start
import {randomBiUnit} from "../src/randomBiUnit";
import {randomChoice} from "../src/randomChoice";
import {buildTransform} from "../2-transforms/buildTransform";
import {transformPost} from "../2-transforms/post";
import {transforms} from "../2-transforms/FlameFinal";
import * as params from "../src/params";
import {ChaosGameFinalProps} from "../2-transforms/chaosGameFinal";
import {camera, histIndex} from "../src/camera";
// hidden-end
export type ChaosGameHistogramProps = ChaosGameFinalProps & {
painter: (width: number, histogram: Uint32Array) => ImageData;
}
export function* chaosGameHistogram({width, height, transforms, final, quality, step, painter}: ChaosGameHistogramProps) {
let iterations = (quality ?? 10) * width * height;
step = step ?? 100_000;
const finalTransform = buildTransform(params.xformFinalCoefs, params.xformFinalVariations);
const finalTransformPost = transformPost(finalTransform, params.xformFinalCoefsPost);
const step = 1000;
const quality = 1;
export function* chaosGameHistogram(width: number, height: number) {
let iterations = quality * width * height;
let histogram = new Uint32Array(width * height);
const histogram = new Uint32Array(width * height);
let [x, y] = [randomBiUnit(), randomBiUnit()];
for (let i = 0; i < iterations; i++) {
const [_, transform] = randomChoice(transforms);
[x, y] = transform(x, y);
[x, y] = finalTransformPost(x, y);
[x, y] = final(x, y);
if (i > 20)
plot(x, y, width, histogram);
if (i > 20) {
const [pixelX, pixelY] = camera(x, y, width);
const pixelIndex = histIndex(pixelX, pixelY, width, 1);
histogram[pixelIndex] += 1;
}
if (i % step === 0)
yield histogram;
yield painter(width, histogram);
}
yield histogram;
yield painter(width, histogram);
}

View File

@ -19,17 +19,23 @@ Can we do something more intelligent with that information?
## Image histograms
To start with, it's worth demonstrating how much work is actually "wasted."
We'll render the reference image again, but this time, counting the times
we tried to turn on a pixel.
We'll render the reference image again, but this time, set each pixel's transparency
based on how many times we encounter it in the chaos game:
import CodeBlock from "@theme/CodeBlock";
import plotHistogramSource from "!!raw-loader!./plotHistogram";
<CodeBlock language="typescript">{plotHistogramSource}</CodeBlock>
import paintLinearSource from "!!raw-loader!./paintLinear"
<CodeBlock language="typescript">{paintLinearSource}</CodeBlock>
import Canvas from "../src/Canvas";
import FlameHistogram from "./FlameHistogram";
import {paintLinear} from "./paintLinear";
<Canvas width={400} height={400} hidden={true}>
<FlameHistogram/>
</Canvas>
<Canvas><FlameHistogram quality={5} paintFn={paintLinear}/></Canvas>
## Log display
import {paintLogarithmic} from './paintLogarithmic'
<Canvas><FlameHistogram quality={10} paintFn={paintLogarithmic}/></Canvas>

View File

@ -0,0 +1,18 @@
export function paintLinear(width: number, histogram: Uint32Array): ImageData {
const image = new ImageData(width, histogram.length / width);
let countMax = 0;
for (let value of histogram) {
countMax = Math.max(countMax, value);
}
for (let i = 0; i < histogram.length; i++) {
const pixelIndex = i * 4;
image.data[pixelIndex] = 0; // red
image.data[pixelIndex + 1] = 0; // green
image.data[pixelIndex + 2] = 0; // blue
image.data[pixelIndex + 3] = Number(histogram[i]) / countMax * 0xff;
}
return image;
}

View File

@ -0,0 +1,21 @@
export function paintLogarithmic(width: number, histogram: Uint32Array): ImageData {
const image = new ImageData(width, histogram.length / width);
const histogramLog = new Array<number>();
histogram.forEach(value => histogramLog.push(Math.log(value)));
let histogramLogMax = -Infinity;
for (let value of histogramLog) {
histogramLogMax = Math.max(histogramLogMax, value);
}
for (let i = 0; i < histogram.length; i++) {
const pixelIndex = i * 4;
image.data[pixelIndex] = 0; // red
image.data[pixelIndex + 1] = 0; // green
image.data[pixelIndex + 2] = 0; // blue
image.data[pixelIndex + 3] = histogramLog[i] / histogramLogMax * 0xff;
}
return image;
}

View File

@ -1,23 +0,0 @@
// hidden-start
import {camera, histIndex} from "../src/camera";
// hidden-end
export function plot(x: number, y: number, width: number, hitCount: Uint32Array) {
const [pixelX, pixelY] = camera(x, y, width);
const pixelIndex = histIndex(pixelX, pixelY, width, 1);
hitCount[pixelIndex] += 1;
}
export type PlotData = {x: number, y: number}[];
export function plotHistogram(hitCount: Uint32Array) {
const data = new Map<number, number>();
hitCount.forEach(value => {
const bucket = 32 - Math.clz32(value);
const currentCount = data.get(bucket) ?? 0;
data.set(bucket, currentCount + 1);
})
const output: PlotData = [];
data.forEach((value, bucket) =>
output.push({x: Math.pow(2, bucket), y: value}));
return output;
}