Skip to content

Commit 60b7552

Browse files
committed
Hist-match Correct onLoad for user-selectable source/target
1 parent df218eb commit 60b7552

3 files changed

Lines changed: 69 additions & 95 deletions

File tree

‎public/histogram-matching-js.html‎

Lines changed: 69 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
</head>
2727
<body>
2828
<div style="padding: 30px; ">
29-
<h3>Pure JS Histogram Matching - client-side</h3>
29+
<h3>Pure JS, client-side, Histogram Matching</h3>
3030
<p>
3131
Inspired by <a href="https://github.com/mapbox/rio-hist/issues/3">mapbox/rio-hist</a> (python), this demo showcases pure-JS Histogram Matching. <br/>Implementation based on scikit-image (<a href="https://scikit-image.org/docs/stable/auto_examples/color_exposure/plot_histogram_matching.html">doc</a> and <a href="https://github.com/scikit-image/scikit-image/blob/v0.25.2/skimage/exposure/histogram_matching.py#L33-L93">code</a>) which simply relies on numpy <code>bincount, cumsum, interp</code>. <br/>scikit-image and numpy use BSD3 License.
3232
</p>
@@ -48,10 +48,6 @@ <h3>Pure JS Histogram Matching - client-side</h3>
4848
// // See [doc](https://scikit-image.org/docs/stable/auto_examples/color_exposure/plot_histogram_matching.html) and [histogram_matching.py](https://github.com/scikit-image/scikit-image/blob/v0.25.2/skimage/exposure/histogram_matching.py#L33-L93
4949
// Relies on numpy bincount, cumsum, interp, reimplemented below with some assumptions
5050

51-
const img_objs = [];
52-
let source_filepath = './source1.jpg';
53-
let target_filepath = './reference1.jpg';
54-
5551

5652
// Compute histogram bins for an array, with optional min/max for faster compute, and optional normalization parameter
5753
export function bincount(arr, binCount, min, max, normalize = false) {
@@ -169,122 +165,100 @@ <h3>Pure JS Histogram Matching - client-side</h3>
169165

170166

171167

172-
function newImageWithOnload(filepath) {
173-
const img = new Image();
168+
// --- image handling ---
169+
const img_objs = {};
170+
// slot is source or target, indexing img_objs, mapped to input and img slot+"-img-div
171+
function loadImage(fileOrUrl, slot) {
172+
const img = new Image();
174173
img.onload = function() {
175174
const canvas = document.createElement("canvas");
176-
canvas.width = img.width;
177-
canvas.height = img.height;
175+
canvas.width = img.naturalWidth; // naturalWidth instead of width
176+
canvas.height = img.naturalHeight; // naturalHeight instead of height
178177
const ctx = canvas.getContext("2d");
179178
ctx.drawImage(img, 0, 0);
180-
img_objs.push({ filepath, ctx, img, canvas });
181-
182-
if (img_objs.length === 2) {
183-
const source = img_objs.find(i => i.filepath === source_filepath);
184-
const target = img_objs.find(i => i.filepath === target_filepath);
185-
console.log('source', source, target)
186-
187-
const source_arr = source.ctx.getImageData(0, 0, source.canvas.width, source.canvas.height);
188-
const target_arr = target.ctx.getImageData(0, 0, target.canvas.width, target.canvas.height);
189-
console.log('source_arr', source_arr, target_arr)
190-
179+
img_objs[slot] = {ctx, img, canvas};
180+
document.getElementById(slot+"-img-div").replaceChildren(img);
181+
182+
if (img_objs.source && img_objs.target) {
183+
// Get source and target arrays from dom imgs
184+
const source_canvas = img_objs.source.canvas
185+
const target_canvas = img_objs.target.canvas
186+
const sourceArr = img_objs.source.ctx.getImageData(0,0,source_canvas.width,source_canvas.height).data;
187+
const targetArr = img_objs.target.ctx.getImageData(0,0,target_canvas.width,target_canvas.height).data;
188+
189+
// Match Histograms Computation
190+
// Check getImageData always return 4 channels RGBA
191191
const start = Date.now();
192-
const returnType = 'MATCHED_DATA'
193-
const channels = 3 // should be computed from image, or getImageData is always same type?
194-
const matched = matchHistograms(source_arr.data, target_arr.data, channels, returnType);
195-
console.log('matched', matched, 'mappings slice')
196-
console.log(matched.mappings[0].slice(0, 30))
192+
const matched = matchHistograms(sourceArr, targetArr, 4, 'MATCHED_DATA');
197193
const elapsed_ms = Date.now() - start
198194

199-
// Render matched image
200-
const canvas_out = document.createElement("canvas");
201-
canvas_out.width = source.img.width;
202-
canvas_out.height = source.img.height;
203-
const ctx_out = canvas_out.getContext("2d");
204-
const imageData = ctx_out.createImageData(canvas_out.width, canvas_out.height);
205-
imageData.data.set(matched.matched);
206-
ctx_out.putImageData(imageData, 0, 0);
207-
208-
container = document.getElementById('matched-img-div');
209-
210-
const stats_div = document.createElement("div");
195+
// Write matched image to outCanvas
196+
const outCanvas = document.createElement("canvas");
197+
outCanvas.width = source_canvas.width;
198+
outCanvas.height = source_canvas.height;
199+
const ctxOut = outCanvas.getContext("2d");
200+
const outData = ctxOut.createImageData(outCanvas.width, outCanvas.height);
201+
outData.data.set(matched.matched);
202+
ctxOut.putImageData(outData, 0, 0);
203+
204+
// Display to user and log stats
205+
const container = document.getElementById('matched-img-div');
206+
console.log('matched', matched, 'mappings slice')
207+
console.log(matched.mappings[0].slice(0, 30))
208+
const stats_div = document.createElement("pre");
211209
const statsObj = {
212210
source: {
213-
width: source.img.width,
214-
height: source.img.height,
215-
Mpix: Math.floor(source.img.width * source.img.height / 1000000),
211+
width: img_objs.source.canvas.width,
212+
height: img_objs.source.canvas.height,
213+
Mpix: 0.01 * Math.floor(img_objs.source.canvas.width * img_objs.source.canvas.height / 1000000 * 100),
216214
},
217215
target: {
218-
width: target.img.width,
219-
height: target.img.height,
220-
Mpix: Math.floor(target.img.width * target.img.height / 1000000),
216+
width: img_objs.target.canvas.width,
217+
height: img_objs.target.canvas.height,
218+
Mpix: 0.01 * Math.floor(img_objs.target.canvas.width * img_objs.target.canvas.height / 1000000 * 100),
221219
},
222220
timings: {
223221
ms: elapsed_ms
224222
}
225223
}
226-
stats_div.innerHTML = JSON.stringify(statsObj, null, " ");
224+
const statsStr = JSON.stringify(statsObj, null, " ")
225+
console.log(statsStr)
226+
stats_div.textContent = `Histogram matching took ${statsObj.timings.ms}ms\n` + statsStr;
227+
stats_div.style = 'text-align: left; padding: 20px;'
227228

228-
// container.replaceChildren(canvas_out);
229-
canvas_out.toBlob(function(blob) {
229+
// container.replaceChildren(outCanvas);
230+
outCanvas.toBlob(function(blob) {
230231
const url = URL.createObjectURL(blob);
231232
const img = document.createElement("img");
232233
img.src = url;
233234
container.replaceChildren(img);
234235

235-
container.addChildren(stats_div)
236+
container.appendChild(stats_div)
236237
}, "image/png");
238+
237239
}
238-
}
239-
img.src = filepath;
240-
let container
241-
if (filepath === source_filepath)
242-
container = document.getElementById('source-img-div');
243-
else
244-
container = document.getElementById('target-img-div');
245-
container.replaceChildren(img);
240+
};
241+
img.src = fileOrUrl;
246242
}
247243

248-
let img_source = newImageWithOnload(source_filepath)
249-
let img_target = newImageWithOnload(target_filepath)
250-
console.log('done loading image, will start processing callbacks to compute Histogram Matching')
251-
252-
253-
let obj
254-
function onChangeInput (event) {
255-
console.log(event)
256-
const target = event.target
257-
const srcId = target.id
258-
console.log(srcId)
259-
if (srcId == 'source-input') {
260-
obj = img_objs.find(i => i.filepath === source_filepath);
261-
source_filepath = target.value
262-
console.log('source-input')
263-
} else if (srcId == 'target-input') {
264-
obj = img_objs.find(i => i.filepath === target_filepath);
265-
target_filepath = target.value
266-
console.log('target-input')
267-
}
268-
console.log(obj, source_filepath, target_filepath)
269-
var file = target.files[0];
270-
console.log(file)
271-
var reader = new FileReader();
272-
reader.onloadend = function () {
273-
console.log('loadend')
274-
obj.img.src = reader.result;
275-
console.log('end of loadend')
276-
}
277-
if (file) {
278-
console.log('readAsDataURL', file);
279-
reader.readAsDataURL(file);
280-
console.log('readAsDataURL done');
281-
} else {
282-
preview.src = "";
283-
}
284-
};
285-
window.onChangeInput = onChangeInput
286-
document.getElementById('source-input').onchange = onChangeInput
287-
document.getElementById('target-input').onchange = onChangeInput
244+
function handleInputChange(e) {
245+
const file = e.target.files[0];
246+
if (!file) return;
247+
const reader = new FileReader();
248+
// reader.onload = ev => loadImage(ev.target.result, e.target.id.split("-")[0]);
249+
reader.onload = ev => loadImage(ev.target.result, e.target.id.includes('source') ? 'source' : 'target');
250+
reader.readAsDataURL(file);
251+
}
252+
253+
document.getElementById("source-input").addEventListener("change", handleInputChange);
254+
document.getElementById("target-input").addEventListener("change", handleInputChange);
255+
256+
// preload defaults
257+
let source_filepath = './source1.jpg';
258+
let target_filepath = './reference1.jpg';
259+
260+
loadImage(source_filepath, "source");
261+
loadImage(target_filepath, "target");
288262

289263
</script>
290264

‎public/reference1.jpg‎

28.8 KB
Loading

‎public/source1.jpg‎

221 KB
Loading

0 commit comments

Comments
 (0)