celldega 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -2
- package/src/celldega/static/widget.css +1 -0
- package/src/celldega/static/widget.js +1570 -0
- package/build.js +0 -16
- package/js/vendor/parquet-wasm/LICENSE_APACHE +0 -176
- package/js/vendor/parquet-wasm/LICENSE_MIT +0 -25
- package/js/vendor/parquet-wasm/arrow2_bg.wasm.base64 +0 -1
- package/js/vendor/parquet-wasm/parquet-wasm_unpkg.js +0 -1963
- package/js/vendor/parquet-wasm/wasmModuleBase64.js +0 -2
- package/js/vendor/parquet-wasm/wrapper.js +0 -3
- package/js/widget.css +0 -23
- package/js/widget.js +0 -701
- package/pyproject.toml +0 -31
- package/src/celldega/__init__.py +0 -25
- package/wasm-plugin.mjs +0 -39
package/js/widget.js
DELETED
|
@@ -1,701 +0,0 @@
|
|
|
1
|
-
import "./widget.css";
|
|
2
|
-
|
|
3
|
-
import { Deck, ScatterplotLayer, TileLayer, BitmapLayer, OrthographicView, PathLayer } from 'deck.gl';
|
|
4
|
-
import * as arrow from "apache-arrow";
|
|
5
|
-
// import { load } from 'https://esm.sh/@loaders.gl/core@4.1.1'; // 4.1.2 is not working???
|
|
6
|
-
import { load } from '@loaders.gl/core';
|
|
7
|
-
import * as mathGl from 'math.gl';
|
|
8
|
-
|
|
9
|
-
// local file from unpkg
|
|
10
|
-
console.log('using local version of unpng parquet-wasm')
|
|
11
|
-
import * as pq from "./vendor/parquet-wasm/parquet-wasm_unpkg.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export async function render({ model, el }) {
|
|
15
|
-
|
|
16
|
-
const cache_trx = new Map();
|
|
17
|
-
const cache_cell = new Map();
|
|
18
|
-
const MAX_CACHE_SIZE = 20; // Example size
|
|
19
|
-
|
|
20
|
-
// functions
|
|
21
|
-
////////////////
|
|
22
|
-
|
|
23
|
-
// console.log('function definitions')
|
|
24
|
-
|
|
25
|
-
const grab_trx_tiles_in_view = async (tiles_in_view) => {
|
|
26
|
-
|
|
27
|
-
const tile_trx_urls = tiles_in_view.map(tile => {
|
|
28
|
-
return `${base_url}/real_transcript_tiles_mosaic/transcripts_tile_${tile.tileX}_${tile.tileY}.parquet`;
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
var tile_trx_tables = await fetch_all_tables_v2(cache_trx, tile_trx_urls)
|
|
32
|
-
var trx_arrow_table = concatenate_arrow_tables(tile_trx_tables)
|
|
33
|
-
trx_names_array = trx_arrow_table.getChild("name").toArray();
|
|
34
|
-
var trx_scatter_data = get_scatter_data(trx_arrow_table)
|
|
35
|
-
|
|
36
|
-
return trx_scatter_data
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const grab_cell_tiles_in_view = async (tiles_in_view) => {
|
|
40
|
-
|
|
41
|
-
const tile_cell_urls = tiles_in_view.map(tile => {
|
|
42
|
-
return `${base_url}/cell_segmentation_v2/cell_tile_${tile.tileX}_${tile.tileY}.parquet`;
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
var tile_cell_tables = await fetch_all_tables_v2(cache_cell, tile_cell_urls)
|
|
46
|
-
|
|
47
|
-
var polygon_datas = tile_cell_tables.map(x => get_data(x))
|
|
48
|
-
|
|
49
|
-
// this can be used directly in the SolidPolygonLayer
|
|
50
|
-
var polygon_data = concatenate_polygon_data(polygon_datas);
|
|
51
|
-
|
|
52
|
-
var polygonPathsConcat = extractPolygonPaths(polygon_data)
|
|
53
|
-
|
|
54
|
-
return polygonPathsConcat
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const calc_viewport = async ({ height, width, zoom, target }) => {
|
|
58
|
-
|
|
59
|
-
const zoomFactor = Math.pow(2, zoom);
|
|
60
|
-
const [targetX, targetY] = target;
|
|
61
|
-
const halfWidthZoomed = width / (2 * zoomFactor);
|
|
62
|
-
const halfHeightZoomed = height / (2 * zoomFactor);
|
|
63
|
-
|
|
64
|
-
const minX = targetX - halfWidthZoomed;
|
|
65
|
-
const maxX = targetX + halfWidthZoomed;
|
|
66
|
-
const minY = targetY - halfHeightZoomed;
|
|
67
|
-
const maxY = targetY + halfHeightZoomed;
|
|
68
|
-
|
|
69
|
-
const tiles_in_view = visibleTiles(minX, maxX, minY, maxY, tileSize);
|
|
70
|
-
|
|
71
|
-
var num_tiles_to_viz = tiles_in_view.length
|
|
72
|
-
|
|
73
|
-
if (num_tiles_to_viz < max_tiles_to_view) {
|
|
74
|
-
|
|
75
|
-
// trx tiles
|
|
76
|
-
////////////////////////////////
|
|
77
|
-
const trx_scatter_data = grab_trx_tiles_in_view(tiles_in_view)
|
|
78
|
-
|
|
79
|
-
const trx_layer_new = new ScatterplotLayer({
|
|
80
|
-
// Re-use existing layer props
|
|
81
|
-
...trx_layer.props,
|
|
82
|
-
data: trx_scatter_data,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// cell tiles
|
|
86
|
-
////////////////////////////////
|
|
87
|
-
|
|
88
|
-
const polygonPathsConcat = grab_cell_tiles_in_view(tiles_in_view)
|
|
89
|
-
|
|
90
|
-
const polygon_layer_new = new PathLayer({
|
|
91
|
-
// Re-use existing layer props
|
|
92
|
-
...polygon_layer.props,
|
|
93
|
-
data: polygonPathsConcat,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// console.log('cache_trx size', cache_trx.size)
|
|
97
|
-
|
|
98
|
-
// update layer
|
|
99
|
-
deck.setProps({
|
|
100
|
-
// layers: [tile_layer, polygon_layer_new, cell_layer, trx_layer_new]
|
|
101
|
-
layers: [tile_layer_2, tile_layer, polygon_layer_new, cell_layer, trx_layer_new]
|
|
102
|
-
// layers: [tile_layer_2, tile_layer] // , trx_layer_new]
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
} else {
|
|
106
|
-
deck.setProps({
|
|
107
|
-
// layers: [tile_layer, cell_layer]
|
|
108
|
-
layers: [tile_layer_2, tile_layer, cell_layer]
|
|
109
|
-
// layers: [tile_layer_2, tile_layer]
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const arrayBufferToArrowTable = (arrayBuffer) => {
|
|
115
|
-
|
|
116
|
-
// console.log('arrayBufferToArrowTable: starting')
|
|
117
|
-
const arr = new Uint8Array(arrayBuffer);
|
|
118
|
-
|
|
119
|
-
// console.log('arr', arr.byteLength)
|
|
120
|
-
|
|
121
|
-
// console.log('about to use readParquet')
|
|
122
|
-
// console.log(pq.readParquet)
|
|
123
|
-
// console.log(pq.readParquet())
|
|
124
|
-
// console.log('***********************************')
|
|
125
|
-
const arrowIPC = pq.readParquet(arr);
|
|
126
|
-
// console.log('readParquet done!!!')
|
|
127
|
-
// console.log(arrowIPC)
|
|
128
|
-
|
|
129
|
-
return arrow.tableFromIPC(arrowIPC);
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const fetch_all_tables_v2 = async (cache_trx, urls) => {
|
|
133
|
-
return Promise.all(urls.map(url => get_arrow_table_and_cache(cache_trx, url)));
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Function to access (fetch or retrieve from cache) and cache the table
|
|
138
|
-
const get_arrow_table_and_cache = async (cache, url) => {
|
|
139
|
-
let data;
|
|
140
|
-
|
|
141
|
-
if (cache.has(url)) {
|
|
142
|
-
// console.log('Using cached data for:', url);
|
|
143
|
-
// Accessing the item, so move it to the end to mark it as most recently used
|
|
144
|
-
data = cache.get(url);
|
|
145
|
-
cache.delete(url); // Remove the item
|
|
146
|
-
cache.set(url, data); // Re-insert to update its position
|
|
147
|
-
} else {
|
|
148
|
-
// Item is not in the cache, fetch it
|
|
149
|
-
const response = await fetch(url, options.fetch);
|
|
150
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
151
|
-
data = arrayBufferToArrowTable(arrayBuffer);
|
|
152
|
-
|
|
153
|
-
// Check if the cache is exceeding the size limit before adding the new item
|
|
154
|
-
if (cache.size >= MAX_CACHE_SIZE) {
|
|
155
|
-
// Evict the least recently used item, which is the first item in the Map
|
|
156
|
-
const leastRecentlyUsedKey = cache.keys().next().value;
|
|
157
|
-
cache.delete(leastRecentlyUsedKey);
|
|
158
|
-
// console.log(`Evicted ${leastRecentlyUsedKey}`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Add the new item to the cache
|
|
162
|
-
cache.set(url, data);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// console.log(`Cache size: ${cache.size}`);
|
|
166
|
-
return data;
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const debounce = (func, wait, immediate) => {
|
|
170
|
-
let timeout;
|
|
171
|
-
return (...args) => {
|
|
172
|
-
const context = this;
|
|
173
|
-
const later = () => {
|
|
174
|
-
timeout = null;
|
|
175
|
-
if (!immediate) func.apply(context, args);
|
|
176
|
-
};
|
|
177
|
-
const callNow = immediate && !timeout;
|
|
178
|
-
clearTimeout(timeout);
|
|
179
|
-
timeout = setTimeout(later, wait);
|
|
180
|
-
if (callNow) func.apply(context, args);
|
|
181
|
-
};
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
const visibleTiles = (minX, maxX, minY, maxY, tileSize) => {
|
|
185
|
-
const startTileX = Math.floor(minX / tileSize);
|
|
186
|
-
const endTileX = Math.floor(maxX / tileSize);
|
|
187
|
-
const startTileY = Math.floor(minY / tileSize);
|
|
188
|
-
const endTileY = Math.floor(maxY / tileSize);
|
|
189
|
-
|
|
190
|
-
const tiles = [];
|
|
191
|
-
for (let x = startTileX; x <= endTileX; x++) {
|
|
192
|
-
for (let y = startTileY; y <= endTileY; y++) {
|
|
193
|
-
tiles.push({ tileX: x, tileY: y, name: x + '_' + y });
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return tiles;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const concatenate_polygon_data = (dataObjects) => {
|
|
200
|
-
// Initialize concatenated data structure
|
|
201
|
-
let concatenatedData = {
|
|
202
|
-
length: 0,
|
|
203
|
-
startIndices: new Int32Array(),
|
|
204
|
-
attributes: {
|
|
205
|
-
getPolygon: {
|
|
206
|
-
value: new Float64Array(),
|
|
207
|
-
size: 2 // Assuming 'size' remains constant
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
// Iterate over each data object to combine them
|
|
213
|
-
dataObjects.forEach((data, index) => {
|
|
214
|
-
concatenatedData.length += data.length;
|
|
215
|
-
|
|
216
|
-
// Handle startIndices
|
|
217
|
-
let lastValue = concatenatedData.attributes.getPolygon.value.length/2;
|
|
218
|
-
let adjustedStartIndices = data.startIndices;
|
|
219
|
-
|
|
220
|
-
if (index > 0) {
|
|
221
|
-
// Adjust startIndices (except for the first data object)
|
|
222
|
-
adjustedStartIndices = new Int32Array(data.startIndices.length);
|
|
223
|
-
for (let i = 0; i < data.startIndices.length; i++) {
|
|
224
|
-
adjustedStartIndices[i] = data.startIndices[i] + lastValue;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Combine startIndices and coordinate values
|
|
229
|
-
concatenatedData.startIndices = new Int32Array([...concatenatedData.startIndices, ...adjustedStartIndices.slice(index > 0 ? 1 : 0)]);
|
|
230
|
-
concatenatedData.attributes.getPolygon.value = new Float64Array([...concatenatedData.attributes.getPolygon.value, ...data.attributes.getPolygon.value]);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
return concatenatedData;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const concatenate_arrow_tables = (tables) => {
|
|
237
|
-
if (tables.length === 0) return null; // No tables to concatenate
|
|
238
|
-
let baseTable = tables[0]; // Use the first table as the base
|
|
239
|
-
for (let i = 1; i < tables.length; i++) { // Start from the second table
|
|
240
|
-
baseTable = baseTable.concat(tables[i]); // Concatenate each table to the base table
|
|
241
|
-
}
|
|
242
|
-
return baseTable;
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
const extractPolygonPaths = (data) => {
|
|
246
|
-
const paths = [];
|
|
247
|
-
const { startIndices, attributes } = data;
|
|
248
|
-
const coordinates = attributes.getPolygon.value;
|
|
249
|
-
const numPolygons = startIndices.length - 1;
|
|
250
|
-
|
|
251
|
-
for (let i = 0; i < numPolygons; ++i) {
|
|
252
|
-
// Multiply by 2 because each coordinate is a pair of values (x, y)
|
|
253
|
-
const startIndex = startIndices[i] * 2;
|
|
254
|
-
// The next start index marks the end of the current polygon
|
|
255
|
-
const endIndex = startIndices[i + 1] * 2;
|
|
256
|
-
const path = [];
|
|
257
|
-
|
|
258
|
-
for (let j = startIndex; j < endIndex; j += 2) {
|
|
259
|
-
const x = coordinates[j];
|
|
260
|
-
const y = coordinates[j + 1];
|
|
261
|
-
path.push([x, y]);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
paths.push(path);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return paths;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const get_arrow_table = async (url, fetch_options) => {
|
|
271
|
-
try {
|
|
272
|
-
// console.log('get_arrow_table: starting')
|
|
273
|
-
const response = await fetch(url, fetch_options);
|
|
274
|
-
// console.log('got response', response)
|
|
275
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
276
|
-
// console.log('got array buffer', arrayBuffer.byteLength)
|
|
277
|
-
const arrowTable = arrayBufferToArrowTable(arrayBuffer)
|
|
278
|
-
// console.log('got arrow table', arrowTable.numRows)
|
|
279
|
-
return arrowTable
|
|
280
|
-
} catch (error) {
|
|
281
|
-
console.error("Error loading data:", error);
|
|
282
|
-
return [];
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const get_scatter_data = (arrow_table) => {
|
|
287
|
-
try {
|
|
288
|
-
|
|
289
|
-
const flatCoordinateArray = arrow_table.getChild("geometry").getChildAt(0).data
|
|
290
|
-
.map(x => x.values)
|
|
291
|
-
.reduce((acc, val) => {
|
|
292
|
-
const combined = new Float64Array(acc.length + val.length);
|
|
293
|
-
combined.set(acc);
|
|
294
|
-
combined.set(val, acc.length);
|
|
295
|
-
return combined;
|
|
296
|
-
}, new Float64Array(0));
|
|
297
|
-
|
|
298
|
-
const scatter_data = {
|
|
299
|
-
length: arrow_table.numRows,
|
|
300
|
-
attributes: {
|
|
301
|
-
getPosition: { value: flatCoordinateArray, size: 2 },
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
return scatter_data
|
|
306
|
-
} catch (error) {
|
|
307
|
-
console.error("Error loading data:", error);
|
|
308
|
-
return [];
|
|
309
|
-
}
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
const make_tooltip = (info) => {
|
|
313
|
-
|
|
314
|
-
// Check if there's a valid index
|
|
315
|
-
if (info.index === -1 || !info.layer) return null;
|
|
316
|
-
|
|
317
|
-
const dataIndex = info.index;
|
|
318
|
-
|
|
319
|
-
let inst_name
|
|
320
|
-
|
|
321
|
-
if (info.layer.id === 'cell-layer'){
|
|
322
|
-
inst_name = cell_names_array[info.index]
|
|
323
|
-
|
|
324
|
-
} else if (info.layer.id === 'trx-layer') {
|
|
325
|
-
inst_name = trx_names_array[info.index]
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Make sure to check that `dataItem` and the property you want to display exist
|
|
329
|
-
return {
|
|
330
|
-
html: `<div>${inst_name}</div?`,
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
var get_data = (arrowTable) => {
|
|
337
|
-
|
|
338
|
-
var geometryColumn = arrowTable.getChildAt(0)
|
|
339
|
-
var polygonIndices = geometryColumn.data[0].valueOffsets
|
|
340
|
-
var ringIndices = geometryColumn.getChildAt(0).data[0].valueOffsets
|
|
341
|
-
var flatCoordinateVector = geometryColumn.getChildAt(0).getChildAt(0).getChildAt(0)
|
|
342
|
-
var flatCoordinateArray = flatCoordinateVector.data[0].values
|
|
343
|
-
const resolvedIndices = new Int32Array(polygonIndices.length);
|
|
344
|
-
|
|
345
|
-
for (let i = 0; i < resolvedIndices.length; ++i) {
|
|
346
|
-
// Perform the lookup into the ringIndices array using the polygonIndices array
|
|
347
|
-
resolvedIndices[i] = ringIndices[polygonIndices[i]]
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
var data = {
|
|
351
|
-
// Number of geometries
|
|
352
|
-
length: arrowTable.numRows,
|
|
353
|
-
// Indices into coordinateArray where each polygon starts
|
|
354
|
-
startIndices: resolvedIndices,
|
|
355
|
-
// Flat coordinates array
|
|
356
|
-
attributes: {
|
|
357
|
-
getPolygon: { value: flatCoordinateArray, size: 2 }
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
return data
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// console.log('getting traitlets')
|
|
364
|
-
|
|
365
|
-
// Deck.gl Viz
|
|
366
|
-
const token = model.get('token_traitlet')
|
|
367
|
-
const ini_x = model.get('ini_x');
|
|
368
|
-
const ini_y = model.get('ini_y');
|
|
369
|
-
const ini_zoom = model.get('ini_zoom');
|
|
370
|
-
const max_image_zoom = model.get('max_image_zoom')
|
|
371
|
-
const bounce_time = model.get('bounce_time')
|
|
372
|
-
const base_url = model.get('base_url')
|
|
373
|
-
|
|
374
|
-
// console.log('getting traitlets done')
|
|
375
|
-
// console.log('token', token)
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
// authorization token for bucket
|
|
380
|
-
const options = ({
|
|
381
|
-
fetch: {
|
|
382
|
-
headers: {
|
|
383
|
-
'Authorization': `Bearer ${token}` // Use the token in the Authorization header
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
const image_name = 'cellbound' // 'dapi' // 'brain';
|
|
389
|
-
|
|
390
|
-
const dzi_url = `${base_url}/pyramid_images/${image_name}.image.dzi`
|
|
391
|
-
const response = await fetch(dzi_url, options.fetch)
|
|
392
|
-
const xmlText = await response.text()
|
|
393
|
-
const dziXML = new DOMParser().parseFromString(xmlText, 'text/xml')
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
// Image Code
|
|
397
|
-
const dimensions = {
|
|
398
|
-
height: Number(dziXML.getElementsByTagName('Size')[0].attributes.Height.value),
|
|
399
|
-
width: Number(dziXML.getElementsByTagName('Size')[0].attributes.Width.value),
|
|
400
|
-
tileSize: Number(dziXML.getElementsByTagName('Image')[0].attributes.TileSize.value)
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
// console.log(dimensions)
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
// class CustomBitmapLayer extends BitmapLayer {
|
|
409
|
-
// getShaders() {
|
|
410
|
-
// const shaders = super.getShaders();
|
|
411
|
-
// shaders.inject = {
|
|
412
|
-
// 'fs:DECKGL_FILTER_COLOR': `
|
|
413
|
-
// // Convert color to grayscale
|
|
414
|
-
// float grayscale = (color.r + color.g + color.b) / 2.5;
|
|
415
|
-
// // Map grayscale to blue color, varying alpha with intensity
|
|
416
|
-
// color = vec4(0.0, 1.0, 0.0, grayscale); `
|
|
417
|
-
// };
|
|
418
|
-
// return shaders;
|
|
419
|
-
// }
|
|
420
|
-
// }
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
class CustomBitmapLayer extends BitmapLayer {
|
|
424
|
-
getShaders() {
|
|
425
|
-
const shaders = super.getShaders();
|
|
426
|
-
// Directly injecting shader code
|
|
427
|
-
shaders.inject = {
|
|
428
|
-
'fs:#decl': `uniform vec3 uColor; uniform float uOpacityScale;`,
|
|
429
|
-
'fs:DECKGL_FILTER_COLOR': `
|
|
430
|
-
// Convert color to grayscale and apply opacity scale
|
|
431
|
-
float grayscale = ((color.r + color.g + color.b) / 3.0) * uOpacityScale;
|
|
432
|
-
// Clamp grayscale to valid range
|
|
433
|
-
grayscale = clamp(grayscale, 0.0, 1.0);
|
|
434
|
-
// Apply custom color and scaled opacity
|
|
435
|
-
color = vec4(uColor, grayscale);
|
|
436
|
-
`
|
|
437
|
-
};
|
|
438
|
-
return shaders;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Properly passing uniforms through updateState lifecycle hook
|
|
442
|
-
updateState(params) {
|
|
443
|
-
super.updateState(params);
|
|
444
|
-
// Extracting custom props
|
|
445
|
-
const {props, oldProps} = params;
|
|
446
|
-
if (props.color !== oldProps.color || props.opacityScale !== oldProps.opacityScale) {
|
|
447
|
-
// Update uniforms when props change
|
|
448
|
-
this.setState({
|
|
449
|
-
uniforms: {
|
|
450
|
-
uColor: props.color.map(c => c / 255), // Normalize RGB to [0, 1] range
|
|
451
|
-
uOpacityScale: props.opacityScale
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
draw(opts) {
|
|
458
|
-
// Ensuring custom uniforms are passed to the shader program
|
|
459
|
-
const {uniforms} = this.state;
|
|
460
|
-
super.draw({
|
|
461
|
-
...opts,
|
|
462
|
-
uniforms: {
|
|
463
|
-
...opts.uniforms,
|
|
464
|
-
...uniforms, // Spread in custom uniforms
|
|
465
|
-
},
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// console.log('creating tile layer')
|
|
471
|
-
const tile_layer = new TileLayer({
|
|
472
|
-
tileSize: dimensions.tileSize,
|
|
473
|
-
refinementStrategy: 'no-overlap',
|
|
474
|
-
minZoom: -7,
|
|
475
|
-
maxZoom: 0,
|
|
476
|
-
maxCacheSize: 20, // 5
|
|
477
|
-
extent: [0, 0, dimensions.width, dimensions.height],
|
|
478
|
-
getTileData: ({index}) => {
|
|
479
|
-
const {x, y, z} = index;
|
|
480
|
-
const full_url = `${base_url}/pyramid_images/${image_name}.image_files/${max_image_zoom + z}/${x}_${y}.jpeg`;
|
|
481
|
-
|
|
482
|
-
return load(full_url, options).then(data => {
|
|
483
|
-
return data; // Successfully loaded the tile data
|
|
484
|
-
}).catch(error => {
|
|
485
|
-
console.error('Failed to load tile:', error);
|
|
486
|
-
// Handle the error, e.g., return a fallback value or null
|
|
487
|
-
return null;
|
|
488
|
-
});
|
|
489
|
-
},
|
|
490
|
-
|
|
491
|
-
renderSubLayers: props => {
|
|
492
|
-
const {
|
|
493
|
-
bbox: {left, bottom, right, top}
|
|
494
|
-
} = props.tile;
|
|
495
|
-
const {width, height} = dimensions;
|
|
496
|
-
|
|
497
|
-
// return new BitmapLayer(props, {
|
|
498
|
-
return new CustomBitmapLayer(props, {
|
|
499
|
-
data: null,
|
|
500
|
-
image: props.data,
|
|
501
|
-
bounds: [
|
|
502
|
-
mathGl.clamp(left, 0, width),
|
|
503
|
-
mathGl.clamp(bottom, 0, height),
|
|
504
|
-
mathGl.clamp(right, 0, width),
|
|
505
|
-
mathGl.clamp(top, 0, height)
|
|
506
|
-
],
|
|
507
|
-
color: [255, 0, 0], // Custom color
|
|
508
|
-
opacityScale: 3.0, // Custom opacity scale
|
|
509
|
-
});
|
|
510
|
-
},
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
const image_name_2 = 'dapi'
|
|
514
|
-
|
|
515
|
-
// console.log('creating tile layer 2')
|
|
516
|
-
|
|
517
|
-
const tile_layer_2 = new TileLayer({
|
|
518
|
-
id: 'tile_layer_2',
|
|
519
|
-
tileSize: dimensions.tileSize,
|
|
520
|
-
refinementStrategy: 'no-overlap',
|
|
521
|
-
minZoom: -7,
|
|
522
|
-
maxZoom: 0,
|
|
523
|
-
maxCacheSize: 20, // 5
|
|
524
|
-
extent: [0, 0, dimensions.width, dimensions.height],
|
|
525
|
-
getTileData: ({index}) => {
|
|
526
|
-
const {x, y, z} = index;
|
|
527
|
-
const full_url = `${base_url}/pyramid_images/${image_name_2}.image_files/${max_image_zoom + z}/${x}_${y}.jpeg`;
|
|
528
|
-
|
|
529
|
-
return load(full_url, options).then(data => {
|
|
530
|
-
return data; // Successfully loaded the tile data
|
|
531
|
-
}).catch(error => {
|
|
532
|
-
console.error('Failed to load tile:', error);
|
|
533
|
-
// Handle the error, e.g., return a fallback value or null
|
|
534
|
-
return null;
|
|
535
|
-
});
|
|
536
|
-
},
|
|
537
|
-
|
|
538
|
-
renderSubLayers: props => {
|
|
539
|
-
const {
|
|
540
|
-
bbox: {left, bottom, right, top}
|
|
541
|
-
} = props.tile;
|
|
542
|
-
const {width, height} = dimensions;
|
|
543
|
-
|
|
544
|
-
return new BitmapLayer(props, {
|
|
545
|
-
// return new CustomBitmapLayer(props, {
|
|
546
|
-
data: null,
|
|
547
|
-
image: props.data,
|
|
548
|
-
bounds: [
|
|
549
|
-
mathGl.clamp(left, 0, width),
|
|
550
|
-
mathGl.clamp(bottom, 0, height),
|
|
551
|
-
mathGl.clamp(right, 0, width),
|
|
552
|
-
mathGl.clamp(top, 0, height)
|
|
553
|
-
]
|
|
554
|
-
});
|
|
555
|
-
},
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
//////////////////////////////////////////
|
|
559
|
-
// Cell/Trx Code
|
|
560
|
-
//////////////////////////////////////////
|
|
561
|
-
|
|
562
|
-
const tileSize = 1000;
|
|
563
|
-
const max_tiles_to_view = 15
|
|
564
|
-
|
|
565
|
-
const debounced_calc_viewport = debounce(calc_viewport, bounce_time);
|
|
566
|
-
|
|
567
|
-
await pq.default();
|
|
568
|
-
|
|
569
|
-
// console.log('pq', pq)
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
// console.log('pq pre-default', pq)
|
|
573
|
-
|
|
574
|
-
// console.log('pq post-default', pq)
|
|
575
|
-
|
|
576
|
-
// console.log('reading cell data as parquet file')
|
|
577
|
-
const cell_url = base_url + `/real_cells_mosaic.parquet`;
|
|
578
|
-
var cell_arrow_table = await get_arrow_table(cell_url, options.fetch)
|
|
579
|
-
|
|
580
|
-
// console.log('finished reading and have cell_arrow_table', cell_arrow_table)
|
|
581
|
-
|
|
582
|
-
var cell_scatter_data = get_scatter_data(cell_arrow_table)
|
|
583
|
-
|
|
584
|
-
const meta_gene_url = base_url + `/meta_gene.parquet`;
|
|
585
|
-
var meta_gene = await get_arrow_table(meta_gene_url, options.fetch)
|
|
586
|
-
|
|
587
|
-
let geneNames = [];
|
|
588
|
-
let colors = [];
|
|
589
|
-
|
|
590
|
-
// Assuming 'cell_arrow_table' is your ArrowTable
|
|
591
|
-
// And it has columns named '__index_level_0__' for gene names and 'color' for colors
|
|
592
|
-
const geneNameColumn = meta_gene.getChild('__index_level_0__');
|
|
593
|
-
const colorColumn = meta_gene.getChild('color');
|
|
594
|
-
|
|
595
|
-
if (geneNameColumn && colorColumn) {
|
|
596
|
-
// If the table is large, consider a more efficient way to handle data extraction
|
|
597
|
-
geneNames = geneNameColumn.toArray();
|
|
598
|
-
colors = colorColumn.toArray();
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
let color_dict = {};
|
|
602
|
-
|
|
603
|
-
function hexToRgb(hex) {
|
|
604
|
-
// Remove the hash at the start if it's there
|
|
605
|
-
hex = hex.replace(/^#/, '');
|
|
606
|
-
let r = 0, g = 0, b = 0;
|
|
607
|
-
// 3 digits
|
|
608
|
-
if (hex.length === 3) {
|
|
609
|
-
r = parseInt(hex.charAt(0) + hex.charAt(0), 16);
|
|
610
|
-
g = parseInt(hex.charAt(1) + hex.charAt(1), 16);
|
|
611
|
-
b = parseInt(hex.charAt(2) + hex.charAt(2), 16);
|
|
612
|
-
}
|
|
613
|
-
// 6 digits
|
|
614
|
-
else if (hex.length === 6) {
|
|
615
|
-
r = parseInt(hex.substring(0, 2), 16);
|
|
616
|
-
g = parseInt(hex.substring(2, 4), 16);
|
|
617
|
-
b = parseInt(hex.substring(4, 6), 16);
|
|
618
|
-
}
|
|
619
|
-
// return `rgb(${r}, ${g}, ${b})`;
|
|
620
|
-
return [r, g, b];
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
geneNames.forEach((geneName, index) => {
|
|
624
|
-
color_dict[geneName] = hexToRgb(colors[index]);
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
const cell_names_array = cell_arrow_table.getChild("name").toArray();
|
|
628
|
-
|
|
629
|
-
let trx_names_array;
|
|
630
|
-
|
|
631
|
-
const cell_layer = new ScatterplotLayer({
|
|
632
|
-
id: 'cell-layer',
|
|
633
|
-
data: cell_scatter_data,
|
|
634
|
-
getRadius: 5.0,
|
|
635
|
-
pickable: true,
|
|
636
|
-
getColor: [0, 0, 255, 240],
|
|
637
|
-
onClick: info => {
|
|
638
|
-
console.log('click!!')
|
|
639
|
-
console.log(info.index)
|
|
640
|
-
console.log(cell_names_array[info.index])
|
|
641
|
-
},
|
|
642
|
-
});
|
|
643
|
-
|
|
644
|
-
// mutable transcript data is initialized as an empty array
|
|
645
|
-
var trx_data = []
|
|
646
|
-
|
|
647
|
-
// console.log('creating trx layer')
|
|
648
|
-
|
|
649
|
-
const trx_layer = new ScatterplotLayer({
|
|
650
|
-
id: 'trx-layer',
|
|
651
|
-
data: trx_data,
|
|
652
|
-
getRadius: 0.5,
|
|
653
|
-
pickable: true,
|
|
654
|
-
// getColor: [200, 0, 0, 150],
|
|
655
|
-
getColor: (i, d) => {
|
|
656
|
-
var inst_gene = trx_names_array[d.index]
|
|
657
|
-
var inst_color = color_dict[inst_gene]
|
|
658
|
-
return [inst_color[0], inst_color[1], inst_color[2], 255]
|
|
659
|
-
},
|
|
660
|
-
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
// console.log('creating polygonlayer')
|
|
664
|
-
const polygon_layer = new PathLayer({
|
|
665
|
-
id: 'path_layer',
|
|
666
|
-
data: [],
|
|
667
|
-
pickable: true,
|
|
668
|
-
widthScale: 3,
|
|
669
|
-
widthMinPixels: 1,
|
|
670
|
-
getPath: d => d,
|
|
671
|
-
getColor: d => [255, 255, 255, 150], // white outline
|
|
672
|
-
widthUnits: 'pixels',
|
|
673
|
-
})
|
|
674
|
-
|
|
675
|
-
// Create and append the visualization.
|
|
676
|
-
let root = document.createElement("div");
|
|
677
|
-
root.style.height = "800px";
|
|
678
|
-
let deck = new Deck({
|
|
679
|
-
parent: root,
|
|
680
|
-
controller: {doubleClickZoom: false},
|
|
681
|
-
initialViewState: {target: [ini_x, ini_y, 0], zoom: ini_zoom},
|
|
682
|
-
views: [new OrthographicView({id: 'ortho'})],
|
|
683
|
-
|
|
684
|
-
layers: [tile_layer_2, tile_layer, cell_layer],
|
|
685
|
-
// layers: [tile_layer, cell_layer],
|
|
686
|
-
|
|
687
|
-
onViewStateChange: ({viewState}) => {
|
|
688
|
-
debounced_calc_viewport(viewState)
|
|
689
|
-
return viewState
|
|
690
|
-
},
|
|
691
|
-
getTooltip: make_tooltip,
|
|
692
|
-
});
|
|
693
|
-
el.appendChild(root);
|
|
694
|
-
|
|
695
|
-
// console.log('returning deck')
|
|
696
|
-
|
|
697
|
-
return () => deck.finalize();
|
|
698
|
-
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
export default { render };
|