diff --git a/index.html b/index.html index 0032e7e..d575719 100644 --- a/index.html +++ b/index.html @@ -134,7 +134,6 @@

H-Space similarity explorer

worker.jobs = {}; worker.job_counter = 0; worker.last_calc_promise = new Promise((resolve, reject) => {resolve()}); -worker.last_fetch_repr_promise = new Promise((resolve, reject) => {resolve()}); worker.onmessage = (e) => { const { id, data } = e.data; const job = worker.jobs[id]; @@ -144,6 +143,9 @@

H-Space similarity explorer

} else { console.warn('Received message for unknown job:', e.data); } + if (data.status === 'error') { + console.error('Error in worker:', data.msg); + } }; worker.onerror = (e) => { console.error('Error in worker:', e); @@ -170,13 +172,9 @@

H-Space similarity explorer

}); return worker.last_calc_promise; } else if (task === 'fetch_repr') { - worker.last_fetch_repr_promise = worker.last_fetch_repr_promise.then(() => { - console.log('Fetching repr:', id, task, data.url); - worker.jobs[id] = job; - worker.postMessage({ id, task, data }); - return promise; - }); - return worker.last_fetch_repr_promise; + worker.jobs[id] = job; + worker.postMessage({ id, task, data }); + return promise; } else { console.error('Unknown task:', task); } @@ -417,7 +415,7 @@

H-Space similarity explorer

try { img_ctx.drawImage(concept.img, 0, 0, img_ctx.canvas.width, img_ctx.canvas.height); } catch (error) { - console.warn('Error while drawing image'); + console.warn('Error while drawing image: ', error.toString()); } drawGrid(img_ctx); if (concept === base_concept) { @@ -448,7 +446,7 @@

H-Space similarity explorer

}) .catch((error) => { // console.error('Error while calculating similarities:', error); - drawError(concept, 'Error calculating similarities...\nTry to change settings.'); + drawError(concept, 'Error calculating similarities...\nMaybe try to change settings.'); updateCanvasesSoon(base_concept, col, row); // schedule update to check if representations are loaded and animate loading text }); } diff --git a/worker/pkg/worker.js b/worker/pkg/worker.js index d7d1b86..6ee050c 100644 --- a/worker/pkg/worker.js +++ b/worker/pkg/worker.js @@ -513,7 +513,7 @@ function __wbg_get_imports() { return addHeapObject(ret); }; imports.wbg.__wbindgen_closure_wrapper274 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 46, __wbg_adapter_20); + const ret = makeMutClosure(arg0, arg1, 45, __wbg_adapter_20); return addHeapObject(ret); }; diff --git a/worker/pkg/worker_bg.wasm b/worker/pkg/worker_bg.wasm index e9ae225..c764076 100644 Binary files a/worker/pkg/worker_bg.wasm and b/worker/pkg/worker_bg.wasm differ diff --git a/worker/src/lib.rs b/worker/src/lib.rs index e456d50..46f6c76 100644 --- a/worker/src/lib.rs +++ b/worker/src/lib.rs @@ -11,6 +11,10 @@ use ndarray::{s, Array2, Array3, ArrayView1, Axis, Zip}; use std::sync::Arc; +macro_rules! jserr {() => {|e| JsValue::from_str(&format!("WASM: {:#?}", e))};} +macro_rules! jsnone {() => {JsValue::from_str(&format!("WASM: unexpected None in {}:{}:{}", file!(), line!(), column!()))};} + + // cache to store representations and means thread_local! { static GLOBAL_MAP: RefCell,Array2,Array2)>>> = RefCell::new(HashMap::new()); @@ -38,8 +42,8 @@ pub fn calc_similarities( // get representations from cache GLOBAL_MAP.with(|map| { let reprs = map.borrow(); - let arc_data1 = reprs.get(&repr1_str).ok_or_else(|| JsValue::from_str(&format!("Failed to get representation, url: {}", repr1_str)))?; - let arc_data2 = reprs.get(&repr2_str).ok_or_else(|| JsValue::from_str(&format!("Failed to get representation, url: {}", repr2_str)))?; + let arc_data1 = reprs.get(&repr1_str).ok_or_else(|| JsValue::from_str("loading"))?; + let arc_data2 = reprs.get(&repr2_str).ok_or_else(|| JsValue::from_str("loading"))?; let (repr1, repr2, means1_full, means2_full, norms1, norms2) = (&arc_data1.0, &arc_data2.0, &arc_data1.1, &arc_data2.1, &arc_data1.2, &arc_data2.2); let n = (repr1.shape()[1] as f32).sqrt() as usize; @@ -91,7 +95,7 @@ pub fn calc_similarities( // normalize distances if func == "euclidean" || func == "manhattan" || func == "chebyshev" { - let max_distance = *similarities.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap(); + let max_distance = *similarities.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).ok_or(jsnone!())?; for distance in similarities.iter_mut() { *distance = 1.0 - (*distance / max_distance); } @@ -127,36 +131,34 @@ pub async fn fetch_repr(url: String, steps: usize, n: usize, m: usize) -> Result // fetch representation let global = js_sys::global().unchecked_into::(); - let resp_value = match JsFuture::from(global.fetch_with_request(&request)).await { - Ok(value) => value, - Err(e) => { - console::warn_1(&JsValue::from_str(&format!("WASM: Fetch error: {:?}", e))); - return Err(JsValue::from_str(&format!("Failed to fetch representation: {}", url))) - } - }; - let resp: Response = match resp_value.dyn_into() { - Ok(resp) => resp, - Err(_) => { - console::warn_1(&JsValue::from_str("WASM: Failed to get Response: resp_value.dyn_into() failed")); - return Err(JsValue::from_str("Failed to get Response: resp_value.dyn_into() failed")); - } + let resp: Response = match JsFuture::from(global.fetch_with_request(&request)).await { + Ok(value) => value.dyn_into().map_err(jserr!())?, + Err(e) => return Err(JsValue::from_str(&format!("Failed to fetch representation ({}): {:#?}", url, e))) }; // convert response to float16 vector - let buffer_value = JsFuture::from(resp.array_buffer()?).await?; - let buffer: ArrayBuffer = match buffer_value.dyn_into() { - Ok(buffer) => buffer, - Err(_) => return Err(JsValue::from_str("Failed to get ArrayBuffer: buffer_value.dyn_into() failed")), - }; + let buffer: ArrayBuffer = JsFuture::from(resp.array_buffer()?).await?.dyn_into().map_err(jserr!())?; if buffer.byte_length() % 2 != 0 { - return Err(JsValue::from_str("Buffer length is not a multiple of 2 (for float16)")); + return Err(JsValue::from_str(&format!("Buffer length is not a multiple of 2 (for float16): {}", url))); } let bytes = Uint8Array::new(&buffer).to_vec(); - let float16_data: Vec = bytes.chunks(2).map(|chunk| f16::from_le_bytes([chunk[0], chunk[1]])).collect(); + let float16_data: Vec = bytes.chunks_exact(2).map(|chunk| f16::from_le_bytes([chunk[0], chunk[1]])).collect(); + + // convert float16 vector to Array4 + let representations = match Array3::from_shape_vec((steps, n*n, m), float16_data.iter().map(|&x| f32::from(x)).collect()) { + Ok(repr) => repr, + Err(e) => { + let new_steps = float16_data.len() / (n*n*m); + if new_steps * n*n*m != float16_data.len() { + return Err(JsValue::from_str(format!("Failed to convert float16 vector (len {}, {}) to Array3 with shape ({}, {}, {}): {:#?}", float16_data.len(), url, steps, n*n, m, e).as_str())) + } + console::warn_1(&JsValue::from_str(format!("Failed to convert float16 vector (len {}, {}) to Array3 with shape ({}, {}, {}), using shape ({}, {}, {}) instead", float16_data.len(), url, steps, n*n, m, new_steps, n*n, m).as_str())); + Array3::from_shape_vec((new_steps, n*n, m), float16_data.iter().map(|&x| f32::from(x)).collect()).map_err(jserr!())? + } + }; - // convert float16 vector to Array4 and store it in cache - let representations = Array3::from_shape_vec((steps, n*n, m), float16_data.iter().map(|&x| f32::from(x)).collect()).unwrap(); - let means = representations.mean_axis(Axis(1)).unwrap(); + // store representations and means and norms in cache + let means = representations.mean_axis(Axis(1)).ok_or(jsnone!())?; let norms = representations.mapv(|x| x.powi(2)).sum_axis(Axis(2)).mapv(f32::sqrt); GLOBAL_MAP.with(|map| { map.borrow_mut().insert(url.to_string(), Arc::new((representations, means, norms))); diff --git a/worker/webworker.js b/worker/webworker.js index bf964b6..153db60 100644 --- a/worker/webworker.js +++ b/worker/webworker.js @@ -10,16 +10,18 @@ onmessage = function(e) { wasm.fetch_repr(url, steps, n, m) .then(() => postMessage({id, data: {status: 'success'}})) .catch((e) => postMessage({id, data: {status: 'error', msg: e}})) - .finally(() => console.log('finished fetching repr')); } else if (e.data.task === 'calc_similarities') { const { func, repr1_str, repr2_str, step1, step2, row, col } = e.data.data; try { const similarities = wasm.calc_similarities(func, repr1_str, repr2_str, step1, step2, row, col); postMessage({ id, data: similarities }); } catch (e) { - // assume that the error is due to the loading of the representations - console.log('error in calc_similarities, assuming representaitons are loading\n', e); - postMessage({ id, data: 'loading' }); + if (e === 'loading') { + postMessage({ id, data: 'loading' }); + } else { + console.error('error in calc_similarities\n', e); + postMessage({ id, data: 'error' }); + } } } })