@vitessce/vit-s 4.0.4 → 4.0.6

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.
@@ -10,6 +10,7 @@ import {
10
10
  useObsLocationsMultiLevel,
11
11
  useObsSetsMultiLevel,
12
12
  useObsLabelsMultiLevel,
13
+ useObsColorsMultiLevel,
13
14
  } from './data-hooks-multilevel-utils.js';
14
15
 
15
16
 
@@ -319,3 +320,34 @@ export function useSegmentationMultiObsSets(
319
320
  );
320
321
  return [indicesData, indicesDataStatus, indicesDataErrors];
321
322
  }
323
+
324
+ /**
325
+ * Wrapper around useObsColorsMultiLevel.
326
+ * @param {object} coordinationScopes
327
+ * @param {object} coordinationScopesBy
328
+ * @param {object} loaders
329
+ * @param {string} dataset
330
+ * @returns {array} [data, status, errors]
331
+ */
332
+ export function useSegmentationMultiObsColors(
333
+ coordinationScopes, coordinationScopesBy, loaders, dataset,
334
+ ) {
335
+ const obsTypeCoordination = useComplexCoordinationSecondary(
336
+ [
337
+ CoordinationType.OBS_TYPE,
338
+ ],
339
+ coordinationScopes,
340
+ coordinationScopesBy,
341
+ CoordinationType.SEGMENTATION_LAYER,
342
+ CoordinationType.SEGMENTATION_CHANNEL,
343
+ );
344
+ const matchOnObj = useMemo(() => obsTypeCoordination[0],
345
+ // imageCoordination reference changes each render,
346
+ // use coordinationScopes and coordinationScopesBy which are
347
+ // indirect dependencies here.
348
+ [coordinationScopes, coordinationScopesBy]);
349
+ const [colorsData, colorsDataStatus, colorsDataErrors] = useObsColorsMultiLevel(
350
+ loaders, dataset, false, matchOnObj, 2,
351
+ );
352
+ return [colorsData, colorsDataStatus, colorsDataErrors];
353
+ }
package/src/hooks.js CHANGED
@@ -5,6 +5,7 @@ import { debounce, every } from 'lodash-es';
5
5
  import { extent } from 'd3-array';
6
6
  import { useQuery } from '@tanstack/react-query';
7
7
  import { capitalize } from '@vitessce/utils';
8
+ import { getObsIndexMap } from '@vitessce/sets-utils';
8
9
  import { STATUS, AsyncFunctionType } from '@vitessce/constants-internal';
9
10
  import { VITESSCE_CONTAINER } from './classNames.js';
10
11
  import { useGridResize, useEmitGridResize } from './state/hooks.js';
@@ -258,6 +259,10 @@ export function useUint8FeatureSelection(expressionData) {
258
259
  }, [expressionData]);
259
260
  }
260
261
 
262
+ // Marks an instance index that equals the matrix row index, so that no
263
+ // per-observation mapping needs to be built.
264
+ const IDENTITY_MAPPING = 'identity';
265
+
261
266
  export function useExpressionValueGetter(
262
267
  { instanceObsIndex, matrixObsIndex, expressionData },
263
268
  ) {
@@ -267,18 +272,32 @@ export function useExpressionValueGetter(
267
272
  // we need a way to look up an obsFeatureMatrix obsIndex index
268
273
  // given an obsEmbedding obsIndex index.
269
274
  const toMatrixIndexMap = useMemo(() => {
270
- if (instanceObsIndex && matrixObsIndex) {
271
- const matrixIndexMap = new Map(matrixObsIndex.map((key, i) => ([key, i])));
272
- return instanceObsIndex.map(key => matrixIndexMap.get(key));
275
+ if (!instanceObsIndex || !matrixObsIndex) {
276
+ return null;
273
277
  }
274
- return null;
278
+ if (instanceObsIndex === matrixObsIndex) {
279
+ // The same array (the usual case when both come from one data source)
280
+ // is already aligned by position.
281
+ return IDENTITY_MAPPING;
282
+ }
283
+ // The map is shared with other consumers of this index via getObsIndexMap.
284
+ const matrixIndexMap = getObsIndexMap(matrixObsIndex);
285
+ const mapping = new Int32Array(instanceObsIndex.length);
286
+ for (let i = 0; i < instanceObsIndex.length; i += 1) {
287
+ const rowIndex = matrixIndexMap.get(instanceObsIndex[i]);
288
+ mapping[i] = rowIndex === undefined ? -1 : rowIndex;
289
+ }
290
+ return mapping;
275
291
  }, [instanceObsIndex, matrixObsIndex]);
276
292
 
277
293
  // Set up a getter function for gene expression values, to be used
278
294
  // by the DeckGL layer to obtain values for instanced attributes.
279
295
  const getExpressionValue = useCallback((entry, { index: instanceIndex }) => {
280
296
  if (toMatrixIndexMap && expressionData && expressionData[0]) {
281
- const rowIndex = toMatrixIndexMap[instanceIndex];
297
+ const rowIndex = toMatrixIndexMap === IDENTITY_MAPPING
298
+ ? instanceIndex
299
+ : toMatrixIndexMap[instanceIndex];
300
+ // An observation absent from the matrix (-1) reads as undefined, as before.
282
301
  const val = expressionData[0][rowIndex];
283
302
  return val;
284
303
  }
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { renderHook } from '@testing-library/react';
3
+ import { useExpressionValueGetter } from './hooks.js';
4
+
5
+ describe('useExpressionValueGetter', () => {
6
+ const expressionData = [new Uint8Array([10, 20, 30])];
7
+
8
+ it('reads by position when the instance and matrix indices are the same array', () => {
9
+ const obsIndex = ['a', 'b', 'c'];
10
+ const { result } = renderHook(() => useExpressionValueGetter({
11
+ instanceObsIndex: obsIndex, matrixObsIndex: obsIndex, expressionData,
12
+ }));
13
+ expect(result.current(null, { index: 0 })).toEqual(10);
14
+ expect(result.current(null, { index: 2 })).toEqual(30);
15
+ });
16
+
17
+ it('maps through observation IDs when the indices differ', () => {
18
+ const { result } = renderHook(() => useExpressionValueGetter({
19
+ instanceObsIndex: ['c', 'unknown', 'a'],
20
+ matrixObsIndex: ['a', 'b', 'c'],
21
+ expressionData,
22
+ }));
23
+ expect(result.current(null, { index: 0 })).toEqual(30);
24
+ // An observation absent from the matrix reads as undefined, as before.
25
+ expect(result.current(null, { index: 1 })).toEqual(undefined);
26
+ expect(result.current(null, { index: 2 })).toEqual(10);
27
+ });
28
+
29
+ it('returns 0 without indices or without data', () => {
30
+ const { result: noIndices } = renderHook(() => useExpressionValueGetter({
31
+ instanceObsIndex: null, matrixObsIndex: ['a'], expressionData,
32
+ }));
33
+ expect(noIndices.current(null, { index: 0 })).toEqual(0);
34
+ const { result: noData } = renderHook(() => useExpressionValueGetter({
35
+ instanceObsIndex: ['a'], matrixObsIndex: ['a'], expressionData: null,
36
+ }));
37
+ expect(noData.current(null, { index: 0 })).toEqual(0);
38
+ });
39
+ });
package/src/index.js CHANGED
@@ -93,6 +93,7 @@ export {
93
93
  useSegmentationMultiObsFeatureMatrixIndices,
94
94
  useSegmentationMultiObsLocations,
95
95
  useSegmentationMultiObsSets,
96
+ useSegmentationMultiObsColors,
96
97
  } from './data-hooks-multilevel.js';
97
98
  export {
98
99
  useHasLoader,