diff --git a/packages/component/src/lib/embedding_view/EmbeddingViewImpl.svelte b/packages/component/src/lib/embedding_view/EmbeddingViewImpl.svelte index 697ad98..6ce64f4 100644 --- a/packages/component/src/lib/embedding_view/EmbeddingViewImpl.svelte +++ b/packages/component/src/lib/embedding_view/EmbeddingViewImpl.svelte @@ -36,7 +36,7 @@ interface Cluster { x: number; y: number; - sum_density: number; + sumDensity: number; rects: Rectangle[]; bandwidth: number; label?: string | null; @@ -548,12 +548,12 @@ viewport: ViewportState, ): Promise { let map = await renderer.densityMap(1000, 1000, bandwidth, viewport); - let cs = await findClusters(map.data, map.width, map.height, { union_threshold: bandwidth }); - let collectedClusters = []; + let cs = await findClusters(map.data, map.width, map.height); + let collectedClusters: Cluster[] = []; for (let idx = 0; idx < cs.length; idx++) { let c = cs[idx]; - let coord = map.coordinateAtPixel(c.mean_x, c.mean_y); - let rects: Rectangle[] = c.boundary_rect_approximation!.map(([x1, y1, x2, y2]) => { + let coord = map.coordinateAtPixel(c.meanX, c.meanY); + let rects: Rectangle[] = c.boundaryRectApproximation!.map(([x1, y1, x2, y2]) => { let p1 = map.coordinateAtPixel(x1, y1); let p2 = map.coordinateAtPixel(x2, y2); return { @@ -566,14 +566,14 @@ collectedClusters.push({ x: coord.x, y: coord.y, - sum_density: c.sum_density, + sumDensity: c.sumDensity, rects: rects, bandwidth: bandwidth, }); } - let maxDensity = collectedClusters.reduce((a, b) => Math.max(a, b.sum_density), 0); + let maxDensity = collectedClusters.reduce((a, b) => Math.max(a, b.sumDensity), 0); let threshold = maxDensity * 0.005; - return collectedClusters.filter((x) => x.sum_density > threshold); + return collectedClusters.filter((x) => x.sumDensity > threshold); } async function generateLabels(viewport: ViewportState): Promise { @@ -610,7 +610,7 @@ text: x.label!, x: x.x, y: x.y, - priority: x.sum_density, + priority: x.sumDensity, level: x.bandwidth == 10 ? 0 : 1, })); diff --git a/packages/density-clustering/density_clustering_wasm/js/index.d.ts b/packages/density-clustering/density_clustering_wasm/js/index.d.ts index 12b436f..1a0c825 100644 --- a/packages/density-clustering/density_clustering_wasm/js/index.d.ts +++ b/packages/density-clustering/density_clustering_wasm/js/index.d.ts @@ -3,27 +3,27 @@ export interface Cluster { /** Cluster identifier */ identifier: number; /** The total density */ - sum_density: number; + sumDensity: number; /** The mean x location (weighted by density) */ - mean_x: number; + meanX: number; /** The mean y location (weighted by density) */ - mean_y: number; + meanY: number; /** The maximum density */ - max_density: number; + maxDensity: number; /** The location with the maximum density */ - max_density_location: [number, number]; + maxDensityLocation: [number, number]; /** The number of pixels in the cluster */ - pixel_count: number; + pixelCount: number; /** The cluster's boundary represented as a list of polygons */ boundary?: [number, number][][]; - /** The cluster's boundary approximated with a list of rectangles */ - boundary_rect_approximation?: [number, number, number, number][]; + /** The cluster's boundary approximated with a list of rectangles, each rectangle is given as an array [x1, y1, x2, y2] */ + boundaryRectApproximation?: [number, number, number, number][]; } /** Options of the find clusters function */ export interface FindClustersOptions { /** The threshold for unioning two clusters */ - union_threshold: number; + unionThreshold: number; } /** @@ -34,9 +34,9 @@ export interface FindClustersOptions { * @param options algorithm options * @returns */ -export async function findClusters( - density_map: Float32Array, +export function findClusters( + densityMap: Float32Array, width: number, height: number, - options: Partial = {}, + options?: Partial, ): Promise; diff --git a/packages/density-clustering/density_clustering_wasm/js/index.js b/packages/density-clustering/density_clustering_wasm/js/index.js index a90fa59..6e598b5 100644 --- a/packages/density-clustering/density_clustering_wasm/js/index.js +++ b/packages/density-clustering/density_clustering_wasm/js/index.js @@ -4,23 +4,23 @@ import * as cluster from "../pkg/density_clustering_wasm.js"; /** * Find clusters from a density map - * @param density_map the density map, a `Float32Array` with `width * height` elements + * @param densityMap the density map, a `Float32Array` with `width * height` elements * @param width the width of the density map * @param height the height of the density map * @param options algorithm options * @returns */ -export async function findClusters(density_map, width, height, options = {}) { +export async function findClusters(densityMap, width, height, options = {}) { await cluster.default(); // console.debug(`find clusters start, size: ${width}x${height}`); let t0 = new Date().getTime(); - let input = new cluster.DensityMap(width, height, density_map); + let input = new cluster.DensityMap(width, height, densityMap); let result = cluster.find_clusters(input, { clustering_options: { use_disjoint_set: true, truncate_to_max_density: true, perform_neighbor_map_grouping: false, - union_threshold: 10.0, + union_threshold: options.unionThreshold ?? 10, density_upperbound_scaler: 0.2, density_lowerbound_scaler: 0.2, ...options, @@ -33,14 +33,14 @@ export async function findClusters(density_map, width, height, options = {}) { for (let [id, summary] of result.summaries) { clusters.push({ identifier: id, - sum_density: summary.sum_density, - mean_x: summary.sum_x_density / summary.sum_density, - mean_y: summary.sum_y_density / summary.sum_density, - max_density: summary.max_density, - max_density_location: summary.max_density_location, - pixel_count: summary.num_pixels, + sumDensity: summary.sum_density, + meanX: summary.sum_x_density / summary.sum_density, + meanY: summary.sum_y_density / summary.sum_density, + maxDensity: summary.max_density, + maxDensityLocation: summary.max_density_location, + pixelCount: summary.num_pixels, boundary: result.boundaries.get(id), - boundary_rect_approximation: result.boundary_rects.get(id), + boundaryRectApproximation: result.boundary_rects.get(id), }); } clusters = clusters.filter((x) => x.boundary != null); diff --git a/packages/docs/algorithms.md b/packages/docs/algorithms.md index a732da6..a9fbe75 100644 --- a/packages/docs/algorithms.md +++ b/packages/docs/algorithms.md @@ -16,11 +16,11 @@ To initialize the UMAP algorithm, use `createUMAP`: import { createUMAP } from "embedding-atlas"; let count = 2000; -let input_dim = 100; -let output_dim = 2; +let inputDim = 100; +let outputDim = 2; -// The data must be a Float32Array with count * input_dim elements. -let data = new Float32Array(count * input_dim); +// The data must be a Float32Array with count * inputDim elements. +let data = new Float32Array(count * inputDim); // ... fill in the data let options = { @@ -28,7 +28,7 @@ let options = { }; // Use `createUMAP` to initialize the algorithm. -let umap = await createUMAP(count, input_dim, output_dim, data, options); +let umap = await createUMAP(count, inputDim, outputDim, data, options); ``` After initialization, use the `run` method to update the embedding coordinates: @@ -48,7 +48,7 @@ for (let i = 0; i < 100; i++) { At any time, you can get the current embedding by calling the `embedding` method. ```js -// The result is a Float32Array with count * output_dim elements. +// The result is a Float32Array with count * outputDim elements. let embedding = umap.embedding(); ``` @@ -66,10 +66,10 @@ In addition, you can also use the `createKNN` function to perform approximate ne import { createKNN } from "embedding-atlas"; let count = 2000; -let input_dim = 100; +let inputDim = 100; -// The data must be a Float32Array with count * input_dim elements. -let data = new Float32Array(count * input_dim); +// The data must be a Float32Array with count * inputDim elements. +let data = new Float32Array(count * inputDim); // ... fill in the data let options = { @@ -77,11 +77,11 @@ let options = { }; // Create the KNN instance -let knn = await createKNN(count, input_dim, data, options); +let knn = await createKNN(count, inputDim, data, options); // Perform queries -let query = new Float32Array(input_dim); -knn.query_by_vector(query, k); +let query = new Float32Array(inputDim); +knn.queryByVector(query, k); // Destroy the instance knn.destroy(); @@ -96,7 +96,7 @@ To run the algorithm, use `findClusters`. import { findClusters } from "embedding-atlas"; // A density map of width * height floating point numbers. -let density_map: Float32Array; +let densityMap: Float32Array; -clusters = await findClusters(density_map, width, height); +clusters = await findClusters(densityMap, width, height); ``` diff --git a/packages/examples/src/svelte/FindClustersExample.svelte b/packages/examples/src/svelte/FindClustersExample.svelte index 0bb9e90..779690c 100644 --- a/packages/examples/src/svelte/FindClustersExample.svelte +++ b/packages/examples/src/svelte/FindClustersExample.svelte @@ -24,7 +24,7 @@ amplitude: randomUniform(0.1, 1, rng), }); } - let density_map = new Float32Array(width * height); + let densityMap = new Float32Array(width * height); for (let y = 0; y < width; y++) { for (let x = 0; x < width; x++) { let d = 0; @@ -37,20 +37,20 @@ ry /= g.s2; d += Math.exp(-(rx ** 2 + ry ** 2)) * g.amplitude; } - density_map[y * width + x] = d * 80; + densityMap[y * width + x] = d * 80; } } - return density_map; + return densityMap; } async function run() { - const density_map = generateDensity(width, height); - clusters = await findClusters(density_map, width, height); + const densityMap = generateDensity(width, height); + clusters = await findClusters(densityMap, width, height); let ctx = canvas.getContext("2d")!; let data = ctx.getImageData(0, 0, width, height); for (let i = 0; i < width * height; i++) { - let value = density_map[i]; + let value = densityMap[i]; data.data[i * 4 + 0] = value; data.data[i * 4 + 1] = value; data.data[i * 4 + 2] = value; @@ -67,7 +67,7 @@ {#each clusters as c} - {#each c.boundary_rect_approximation ?? [] as [x1, y1, x2, y2]} + {#each c.boundaryRectApproximation ?? [] as [x1, y1, x2, y2]} {/each} {#each c.boundary ?? [] as boundary} @@ -77,7 +77,7 @@ style:fill="rgba(255,127,14,0.1)" /> {/each} - + {/each} diff --git a/packages/umap-wasm/README.md b/packages/umap-wasm/README.md index ac15990..2b60375 100644 --- a/packages/umap-wasm/README.md +++ b/packages/umap-wasm/README.md @@ -16,11 +16,11 @@ To initialize the algorithm, use `createUMAP`: import { createUMAP } from "umap-wasm"; let count = 2000; -let input_dim = 100; -let output_dim = 2; +let inputDim = 100; +let outputDim = 2; -// The data must be a Float32Array with count * input_dim elements. -let data = new Float32Array(count * input_dim); +// The data must be a Float32Array with count * inputDim elements. +let data = new Float32Array(count * inputDim); // ... fill in the data let options = { @@ -28,7 +28,7 @@ let options = { }; // Use `createUMAP` to initialize the algorithm. -let umap = await createUMAP(count, input_dim, output_dim, data, options); +let umap = await createUMAP(count, inputDim, outputDim, data, options); ``` After initialization, use the `run` method to update the embedding coordinates: @@ -48,7 +48,7 @@ for (let i = 0; i < 100; i++) { At any time, you can get the current embedding by calling the `embedding` method. ```js -// The result is a Float32Array with count * output_dim elements. +// The result is a Float32Array with count * outputDim elements. let embedding = umap.embedding(); ``` diff --git a/packages/umap-wasm/index.d.ts b/packages/umap-wasm/index.d.ts index 9ea9113..e690a3d 100644 --- a/packages/umap-wasm/index.d.ts +++ b/packages/umap-wasm/index.d.ts @@ -4,23 +4,23 @@ export interface UMAPOptions { metric?: "euclidean" | "cosine"; /** The nearest neighbor method. By default we use HNSW with its default parameters. */ - knn_method?: "hnsw" | "nndescent" | "vptree"; + knnMethod?: "hnsw" | "nndescent" | "vptree"; /** The initialization method. By default we use spectral initialization. */ - initialize_method?: "spectral" | "random" | "none"; + initializeMethod?: "spectral" | "random" | "none"; - local_connectivity?: number; + localConnectivity?: number; bandwidth?: number; - mix_ratio?: number; + mixRatio?: number; spread?: number; - min_dist?: number; + minDist?: number; a?: number; b?: number; - repulsion_strength?: number; - n_epochs?: number; - learning_rate?: number; - negative_sample_rate?: number; - n_neighbors?: number; + repulsionStrength?: number; + nEpochs?: number; + learningRate?: number; + negativeSampleRate?: number; + nNeighbors?: number; /** The random seed. */ seed?: number; } @@ -30,10 +30,10 @@ export interface UMAP { get epoch(): number; /** The input dimension */ - get input_dim(): number; + get inputDim(): number; /** The output dimension */ - get output_dim(): number; + get outputDim(): number; /** * Get the current embedding. @@ -45,11 +45,11 @@ export interface UMAP { get embedding(): Float32Array; /** - * Run the UMAP algorithm until reaching `epoch_limit` epochs, - * or to completion if `epoch_limit` is not specified. - * @param epoch_limit the epoch number to run to + * Run the UMAP algorithm until reaching `epochLimit` epochs, + * or to completion if `epochLimit` is not specified. + * @param epochLimit the epoch number to run to */ - run(epoch_limit?: number): void; + run(epochLimit?: number): void; /** Destroy the instance and release resources */ destroy(): void; @@ -58,15 +58,15 @@ export interface UMAP { /** * Initialize a UMAP instance. * @param count the number of data points - * @param input_dim the input dimension - * @param output_dim the output dimension - * @param data the data array. Must be a Float32Array with count * input_dim elements. + * @param inputDim the input dimension + * @param outputDim the output dimension + * @param data the data array. Must be a Float32Array with count * inputDim elements. * @param options options */ export function createUMAP( count: number, - input_dim: number, - output_dim: number, + inputDim: number, + outputDim: number, data: Float32Array, options?: UMAPOptions, ): Promise; @@ -86,9 +86,9 @@ export interface KNNQueryResult { } export interface KNN { - query_by_index(index: number, k: number): KNNQueryResult; - query_by_vector(data: Float32Array, k: number): KNNQueryResult; + queryByIndex(index: number, k: number): KNNQueryResult; + queryByVector(data: Float32Array, k: number): KNNQueryResult; destroy(): void; } -export function createKNN(count: number, input_dim: number, data: Float32Array, options?: KNNOptions): Promise; +export function createKNN(count: number, inputDim: number, data: Float32Array, options?: KNNOptions): Promise; diff --git a/packages/umap-wasm/index.js b/packages/umap-wasm/index.js index 46dc760..0d9c510 100644 --- a/packages/umap-wasm/index.js +++ b/packages/umap-wasm/index.js @@ -29,7 +29,7 @@ function set_options(ctx, ptr, setters, options) { function set_str_buffer(buf, value) { const encoded = new TextEncoder().encode(value); if (encoded.length + 1 > buffer_length) { - throw new Error("invalid parameter " + name); + throw new Error("invalid parameter " + value); } const array = ctx.u8_array(buf, 64); array.fill(0); @@ -38,7 +38,10 @@ function set_options(ctx, ptr, setters, options) { } for (const name in options) { - set_str_buffer(str_buffer_1, name); + // Convert camel case to snake case (expected by the C++ code) + let name_snake_case = name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase(); + set_str_buffer(str_buffer_1, name_snake_case); + // Set the value const value = options[name]; if (typeof value == "number") { const r = setters.number(ptr, str_buffer_1, value); @@ -85,17 +88,17 @@ export async function createUMAP(count, input_dim, output_dim, data, options = { assertValid(); return ctx.umap_context_epoch(ptr_umap); }, - get n_epochs() { + get nEpochs() { assertValid(); return ctx.umap_context_n_epochs(ptr_umap); }, get count() { return count; }, - get output_dim() { + get outputDim() { return output_dim; }, - get input_dim() { + get inputDim() { return input_dim; }, get embedding() { @@ -145,7 +148,7 @@ export async function createKNN(count, input_dim, data, options) { } return { - query_by_index(index, k) { + queryByIndex(index, k) { let [ptr_i, ptr_d] = get_buffers(k); let rk = ctx.knn_context_query_by_index(ptr_knn, index, k, ptr_i, ptr_d); return { @@ -153,7 +156,7 @@ export async function createKNN(count, input_dim, data, options) { distances: ctx.f32_array(ptr_d, rk).slice(), }; }, - query_by_vector(data, k) { + queryByVector(data, k) { let [ptr_i, ptr_d] = get_buffers(k); ctx.f32_array(ptr_q, input_dim).set(data); let rk = ctx.knn_context_query_by_index(ptr_knn, ptr_q, k, ptr_i, ptr_d); diff --git a/packages/viewer/src/embedding/embedding.worker.ts b/packages/viewer/src/embedding/embedding.worker.ts index a64d6de..d761393 100644 --- a/packages/viewer/src/embedding/embedding.worker.ts +++ b/packages/viewer/src/embedding/embedding.worker.ts @@ -30,16 +30,16 @@ function makeEmbeddingComputer(runBatch: (data: any[]) => Promise): Embeddi }, async finalize() { let count = batches.reduce((a, b) => a + b.dims[0], 0); - let input_dim = batches[0].dims[1]; - let output_dim = 2; - let data = new Float32Array(count * input_dim); + let inputDim = batches[0].dims[1]; + let outputDim = 2; + let data = new Float32Array(count * inputDim); let offset = 0; for (let i = 0; i < batches.length; i++) { - let length = batches[i].dims[0] * input_dim; + let length = batches[i].dims[0] * inputDim; data.set(batches[i].data.subarray(0, length), offset); offset += length; } - let umap = await createUMAP(count, input_dim, output_dim, data, { + let umap = await createUMAP(count, inputDim, outputDim, data, { metric: "cosine", }); umap.run();