@vitessce/gl 3.3.1 → 3.3.3

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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { f as distEs6 } from "./index-0d084e33.js";
2
- import { o, n, A, r, q, p, i, j, C, l, D, G, e, H, c, M, k, m, d, P, S, h, T, t, b, s, v } from "./index-0d084e33.js";
1
+ import { f as distEs6 } from "./index-901cd86d.js";
2
+ import { o, n, A, r, q, p, i, j, C, l, D, G, e, H, c, M, k, m, d, P, S, h, T, t, b, s, v } from "./index-901cd86d.js";
3
3
  import "react";
4
4
  const SELECTION_TYPE = distEs6.SELECTION_TYPE;
5
5
  export {
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-0d084e33.js";
1
+ import { B as BaseDecoder } from "./index-901cd86d.js";
2
2
  import "react";
3
3
  const dctZigZag = new Int32Array([
4
4
  0,
@@ -1,5 +1,5 @@
1
1
  import { i as inflate_1 } from "./pako.esm-68f84e2a.js";
2
- import { g as getDefaultExportFromCjs, B as BaseDecoder, L as LercParameters, a as LercAddCompression } from "./index-0d084e33.js";
2
+ import { g as getDefaultExportFromCjs, B as BaseDecoder, L as LercParameters, a as LercAddCompression } from "./index-901cd86d.js";
3
3
  import "react";
4
4
  var LercDecode = { exports: {} };
5
5
  (function(module) {
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-0d084e33.js";
1
+ import { B as BaseDecoder } from "./index-901cd86d.js";
2
2
  import "react";
3
3
  const MIN_BITS = 9;
4
4
  const CLEAR_CODE = 256;
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-0d084e33.js";
1
+ import { B as BaseDecoder } from "./index-901cd86d.js";
2
2
  import "react";
3
3
  class PackbitsDecoder extends BaseDecoder {
4
4
  decodeBlock(buffer) {
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-0d084e33.js";
1
+ import { B as BaseDecoder } from "./index-901cd86d.js";
2
2
  import "react";
3
3
  class RawDecoder extends BaseDecoder {
4
4
  decodeBlock(buffer) {
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-0d084e33.js";
1
+ import { B as BaseDecoder } from "./index-901cd86d.js";
2
2
  import "react";
3
3
  class WebImageDecoder extends BaseDecoder {
4
4
  constructor() {
@@ -39,7 +39,7 @@ declare class BitmaskLayer extends BitmaskLayer_base {
39
39
  * This function creates textures from the data
40
40
  */
41
41
  dataToTexture(data: any, width: any, height: any): any;
42
- multiSetsToTexture(multiFeatureValues: any, setColorValues: any, channelIsSetColorMode: any): any[];
42
+ multiSetsToTexture(multiFeatureValues: any, multiMatrixObsIndex: any, setColorValues: any, channelIsSetColorMode: any): any[];
43
43
  }
44
44
  declare namespace BitmaskLayer {
45
45
  export const layerName: string;
@@ -107,7 +107,7 @@ declare namespace defaultProps {
107
107
  const compare_7: boolean;
108
108
  export { compare_7 as compare };
109
109
  }
110
- namespace setColorValues {
110
+ namespace multiMatrixObsIndex {
111
111
  const type_8: string;
112
112
  export { type_8 as type };
113
113
  const value_7: null;
@@ -115,7 +115,7 @@ declare namespace defaultProps {
115
115
  const compare_8: boolean;
116
116
  export { compare_8 as compare };
117
117
  }
118
- namespace channelFeatureValueColormaps {
118
+ namespace setColorValues {
119
119
  const type_9: string;
120
120
  export { type_9 as type };
121
121
  const value_8: null;
@@ -123,7 +123,7 @@ declare namespace defaultProps {
123
123
  const compare_9: boolean;
124
124
  export { compare_9 as compare };
125
125
  }
126
- namespace channelFeatureValueColormapRanges {
126
+ namespace channelFeatureValueColormaps {
127
127
  const type_10: string;
128
128
  export { type_10 as type };
129
129
  const value_9: null;
@@ -131,7 +131,7 @@ declare namespace defaultProps {
131
131
  const compare_10: boolean;
132
132
  export { compare_10 as compare };
133
133
  }
134
- namespace channelIsStaticColorMode {
134
+ namespace channelFeatureValueColormapRanges {
135
135
  const type_11: string;
136
136
  export { type_11 as type };
137
137
  const value_10: null;
@@ -139,7 +139,7 @@ declare namespace defaultProps {
139
139
  const compare_11: boolean;
140
140
  export { compare_11 as compare };
141
141
  }
142
- namespace channelIsSetColorMode {
142
+ namespace channelIsStaticColorMode {
143
143
  const type_12: string;
144
144
  export { type_12 as type };
145
145
  const value_11: null;
@@ -147,6 +147,14 @@ declare namespace defaultProps {
147
147
  const compare_12: boolean;
148
148
  export { compare_12 as compare };
149
149
  }
150
+ namespace channelIsSetColorMode {
151
+ const type_13: string;
152
+ export { type_13 as type };
153
+ const value_12: null;
154
+ export { value_12 as value };
155
+ const compare_13: boolean;
156
+ export { compare_13 as compare };
157
+ }
150
158
  }
151
159
  import { GLSL_COLORMAP_DEFAULT } from "./constants.js";
152
160
  //# sourceMappingURL=BitmaskLayerBeta.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BitmaskLayerBeta.d.ts","sourceRoot":"","sources":["../src/BitmaskLayerBeta.js"],"names":[],"mappings":";;;;;;;;;;;AAgDA;;;GAGG;AACH;;IAEE;;;;;;;MAYC;IAED;;;;MAIE;IACF,4CA0BC;IAED;;;;aAyCC;IAED,sBAuHC;IAED;;OAEG;IACH,uDAqBC;IAED,oGAiEC;CACF"}
1
+ {"version":3,"file":"BitmaskLayerBeta.d.ts","sourceRoot":"","sources":["../src/BitmaskLayerBeta.js"],"names":[],"mappings":";;;;;;;;;;;AAiEA;;;GAGG;AACH;;IAEE;;;;;;;MAYC;IAED;;;;MAIE;IACF,4CA0BC;IAED;;;;aA6CC;IAED,sBAuHC;IAED;;OAEG;IACH,uDAqBC;IAED,8HAuEC;CACF"}
@@ -20,6 +20,18 @@ function padWithDefault(arr, defaultValue, padWidth) {
20
20
  function getColor(arr) {
21
21
  return arr ? arr.map(v => v / 255) : [0, 0, 0];
22
22
  }
23
+ function isEqualShallow(prevArr, nextArr) {
24
+ if (prevArr === nextArr) {
25
+ return true;
26
+ }
27
+ if (Array.isArray(prevArr) && Array.isArray(nextArr)) {
28
+ if (prevArr.length === nextArr.length) {
29
+ return !prevArr.some((v, i) => (v !== nextArr[i] && ((!Array.isArray(v) && !Array.isArray(nextArr[i]))
30
+ || (v.length > 0 || nextArr[i].length > 0))));
31
+ }
32
+ }
33
+ return prevArr === nextArr;
34
+ }
23
35
  const defaultProps = {
24
36
  channelStrokeWidths: { type: 'array', value: null, compare: true },
25
37
  channelsFilled: { type: 'array', value: null, compare: true },
@@ -29,6 +41,7 @@ const defaultProps = {
29
41
  colormap: { type: 'string', value: GLSL_COLORMAP_DEFAULT, compare: true },
30
42
  expressionData: { type: 'object', value: null, compare: true },
31
43
  multiFeatureValues: { type: 'array', value: null, compare: true },
44
+ multiMatrixObsIndex: { type: 'array', value: null, compare: true },
32
45
  setColorValues: { type: 'array', value: null, compare: true },
33
46
  channelFeatureValueColormaps: { type: 'array', value: null, compare: true },
34
47
  channelFeatureValueColormapRanges: { type: 'array', value: null, compare: true },
@@ -83,13 +96,14 @@ export default class BitmaskLayer extends XRLayer {
83
96
  }
84
97
  updateState({ props, oldProps, changeFlags }) {
85
98
  super.updateState({ props, oldProps, changeFlags });
86
- if (props.multiFeatureValues !== oldProps.multiFeatureValues
87
- || props.setColorValues !== oldProps.setColorValues
88
- || props.channelIsSetColorMode !== oldProps.channelIsSetColorMode) {
89
- const { multiFeatureValues, setColorValues, channelIsSetColorMode } = this.props;
99
+ if (!isEqualShallow(props.multiFeatureValues, oldProps.multiFeatureValues)
100
+ || !isEqualShallow(props.multiMatrixObsIndex, oldProps.multiMatrixObsIndex)
101
+ || !isEqualShallow(props.setColorValues, oldProps.setColorValues)
102
+ || !isEqualShallow(props.channelIsSetColorMode, oldProps.channelIsSetColorMode)) {
103
+ const { multiFeatureValues, multiMatrixObsIndex, setColorValues, channelIsSetColorMode, } = this.props;
90
104
  // Use one expressionTex for all channels,
91
105
  // using an offset mechanism.
92
- const [valueTex, colorTex, valueTexOffsets, colorTexOffsets, valueTexHeight, colorTexHeight,] = this.multiSetsToTexture(multiFeatureValues, setColorValues, channelIsSetColorMode);
106
+ const [valueTex, colorTex, valueTexOffsets, colorTexOffsets, valueTexHeight, colorTexHeight,] = this.multiSetsToTexture(multiFeatureValues, multiMatrixObsIndex, setColorValues, channelIsSetColorMode);
93
107
  this.setState({
94
108
  valueTex,
95
109
  colorTex,
@@ -196,9 +210,9 @@ export default class BitmaskLayer extends XRLayer {
196
210
  type: GL.FLOAT,
197
211
  });
198
212
  }
199
- multiSetsToTexture(multiFeatureValues, setColorValues, channelIsSetColorMode) {
213
+ multiSetsToTexture(multiFeatureValues, multiMatrixObsIndex, setColorValues, channelIsSetColorMode) {
200
214
  const isWebGL2On = isWebGL2(this.context.gl);
201
- const [totalData, valueTexHeight, indicesOffsets, totalColors, colorTexHeight, colorsOffsets,] = multiSetsToTextureData(multiFeatureValues, setColorValues, channelIsSetColorMode, MULTI_FEATURE_TEX_SIZE);
215
+ const [totalData, valueTexHeight, indicesOffsets, totalColors, colorTexHeight, colorsOffsets,] = multiSetsToTextureData(multiFeatureValues, multiMatrixObsIndex, setColorValues, channelIsSetColorMode, MULTI_FEATURE_TEX_SIZE);
202
216
  return [
203
217
  // Color indices texture
204
218
  new Texture2D(this.context.gl, {
@@ -8,5 +8,5 @@
8
8
  * will be determined by the length of the concatenated values.
9
9
  * @returns
10
10
  */
11
- export function multiSetsToTextureData(multiFeatureValues: array[], setColorValues: object[], channelIsSetColorMode: boolean[], texSize: number): (number | any[] | Uint8Array)[];
11
+ export function multiSetsToTextureData(multiFeatureValues: array[], multiMatrixObsIndex: any, setColorValues: object[], channelIsSetColorMode: boolean[], texSize: number): (number | any[] | Uint8Array)[];
12
12
  //# sourceMappingURL=bitmask-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bitmask-utils.d.ts","sourceRoot":"","sources":["../src/bitmask-utils.js"],"names":[],"mappings":"AAYA;;;;;;;;;GASG;AACH,2DARW,OAAO,kBACP,MAAM,EAAE,yBACR,OAAO,EAAE,WAET,MAAM,mCAgFhB"}
1
+ {"version":3,"file":"bitmask-utils.d.ts","sourceRoot":"","sources":["../src/bitmask-utils.js"],"names":[],"mappings":"AAcA;;;;;;;;;GASG;AACH,2DARW,OAAO,4CACP,MAAM,EAAE,yBACR,OAAO,EAAE,WAET,MAAM,mCAwHhB"}
@@ -1,4 +1,6 @@
1
- import { extent } from 'd3-array';
1
+ /* eslint-disable radix */
2
+ /* eslint-disable-next-line camelcase */
3
+ import { extent, max as d3_max } from 'd3-array';
2
4
  function normalize(arr) {
3
5
  const [min, max] = extent(arr);
4
6
  const ratio = 255 / (max - min);
@@ -15,16 +17,26 @@ function normalize(arr) {
15
17
  * will be determined by the length of the concatenated values.
16
18
  * @returns
17
19
  */
18
- export function multiSetsToTextureData(multiFeatureValues, setColorValues, channelIsSetColorMode, texSize) {
20
+ export function multiSetsToTextureData(multiFeatureValues, multiMatrixObsIndex, setColorValues, channelIsSetColorMode, texSize) {
19
21
  let totalValuesLength = 0;
20
22
  let totalColorsLength = 0;
21
23
  channelIsSetColorMode.forEach((isSetColorMode, channelIndex) => {
22
24
  if (isSetColorMode) {
23
- totalValuesLength += setColorValues[channelIndex]?.obsIndex?.length || 0;
25
+ // totalValuesLength += setColorValues[channelIndex]?.obsIndex?.length || 0;
24
26
  totalColorsLength += (setColorValues[channelIndex]?.setColors?.length || 0) * 3;
27
+ // TODO: if we can assume values are monotonically increasing,
28
+ // we can just use the final array value arr[-1] directly as the max.
29
+ totalValuesLength += (setColorValues[channelIndex]?.obsIndex
30
+ ? d3_max(setColorValues[channelIndex].obsIndex.map(d => parseInt(d)))
31
+ : 0);
25
32
  }
26
33
  else {
27
- totalValuesLength += multiFeatureValues[channelIndex]?.length || 0;
34
+ // totalValuesLength += multiFeatureValues[channelIndex]?.length || 0;
35
+ // TODO: if we can assume values are monotonically increasing,
36
+ // we can just use the final array value arr[-1] directly as the max.
37
+ totalValuesLength += (multiMatrixObsIndex[channelIndex]
38
+ ? d3_max(multiMatrixObsIndex[channelIndex].map(d => parseInt(d)))
39
+ : (multiFeatureValues[channelIndex]?.length || 0));
28
40
  }
29
41
  });
30
42
  const valueTexHeight = Math.max(2, Math.ceil(totalValuesLength / texSize));
@@ -46,15 +58,27 @@ export function multiSetsToTextureData(multiFeatureValues, setColorValues, chann
46
58
  let colorOffset = 0;
47
59
  // Iterate over the data for each channel.
48
60
  channelIsSetColorMode.forEach((isSetColorMode, channelIndex) => {
61
+ const matrixObsIndex = multiMatrixObsIndex[channelIndex];
62
+ // Assume the bitmask values correspond to the values in the matrixObsIndex (off by one).
63
+ const bitmaskValueIsIndex = matrixObsIndex === null;
49
64
  if (isSetColorMode) {
50
65
  const { setColorIndices, setColors, obsIndex } = setColorValues[channelIndex] || {};
51
66
  if (setColorIndices && setColors && obsIndex) {
52
67
  for (let i = 0; i < obsIndex.length; i++) {
68
+ let obsId = String(i + 1);
69
+ let obsI = i;
70
+ // TODO: this uses the matrixObsIndex to determine the value of the flag, is that correct?
71
+ if (!bitmaskValueIsIndex) {
72
+ // We cannot assume that the values in the bitmask
73
+ // correspond to the values in the matrixObsIndex.
74
+ obsId = obsIndex[i];
75
+ obsI = parseInt(obsId) - 1;
76
+ }
53
77
  // We add one here to account for i being 0-based but the pixel values being 1-based
54
78
  // (to account for zero indicating the background of the segmentation bitmask).
55
- const colorIndex = setColorIndices.get(String(i + 1));
79
+ const colorIndex = setColorIndices.get(obsId);
56
80
  // Add one to the color index, so that we can use zero to indicate a "null" set color.
57
- totalData[indexOffset + i] = colorIndex === undefined ? 0 : colorIndex + 1;
81
+ totalData[indexOffset + obsI] = colorIndex === undefined ? 0 : colorIndex + 1;
58
82
  }
59
83
  for (let i = 0; i < setColors.length; i++) {
60
84
  const { color: [r, g, b] } = setColors[i];
@@ -70,8 +94,20 @@ export function multiSetsToTextureData(multiFeatureValues, setColorValues, chann
70
94
  }
71
95
  else {
72
96
  const featureArr = multiFeatureValues[channelIndex];
73
- // TODO: are these values always already normalized?
74
- totalData.set(normalize(featureArr), indexOffset);
97
+ const normalizedFeatureArr = normalize(featureArr);
98
+ if (!bitmaskValueIsIndex && matrixObsIndex) {
99
+ // We cannot assume that the values in the bitmask
100
+ // correspond to the values in the matrixObsIndex.
101
+ for (let i = 0; i < matrixObsIndex.length; i++) {
102
+ const obsId = matrixObsIndex[i];
103
+ const obsI = parseInt(obsId) - 1;
104
+ totalData[indexOffset + obsI] = normalizedFeatureArr[i];
105
+ }
106
+ }
107
+ else {
108
+ // TODO: are these values always already normalized?
109
+ totalData.set(normalizedFeatureArr, indexOffset);
110
+ }
75
111
  indicesOffsets.push(indexOffset);
76
112
  indexOffset += featureArr.length;
77
113
  // Add a color offset so that the number of offsets still equals the number of channels.
@@ -7,6 +7,10 @@ describe('bitmask utils', () => {
7
7
  [1, 2, 3, 255, 1000],
8
8
  [10, 20, 30], // 3 expression values, one per glomerulus
9
9
  ];
10
+ const multiObsIndex = [
11
+ ['1', '2', '3', '4', '5'],
12
+ ['1', '2', '3'], // 3 glomeruli
13
+ ];
10
14
  const setColorValues = [
11
15
  {
12
16
  setColorIndices: (new Map([
@@ -39,7 +43,7 @@ describe('bitmask utils', () => {
39
43
  true,
40
44
  false,
41
45
  ];
42
- const [totalData, valueTexHeight, indicesOffsets, totalColors, colorTexHeight, colorsOffsets,] = multiSetsToTextureData(multiFeatureValues, setColorValues, channelIsSetColorMode, 10);
46
+ const [totalData, valueTexHeight, indicesOffsets, totalColors, colorTexHeight, colorsOffsets,] = multiSetsToTextureData(multiFeatureValues, multiObsIndex, setColorValues, channelIsSetColorMode, 10);
43
47
  expect(valueTexHeight).toEqual(2);
44
48
  expect(colorTexHeight).toEqual(2);
45
49
  expect(indicesOffsets).toEqual([0, 5]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/gl",
3
- "version": "3.3.1",
3
+ "version": "3.3.3",
4
4
  "author": "Gehlenborg Lab",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -52,7 +52,7 @@
52
52
  "math.gl": "^3.5.6",
53
53
  "mathjs": "^9.2.0",
54
54
  "nebula.gl": "0.23.8",
55
- "@vitessce/utils": "3.3.1"
55
+ "@vitessce/utils": "3.3.3"
56
56
  },
57
57
  "devDependencies": {
58
58
  "glsl-colormap": "^1.0.1"
@@ -29,6 +29,22 @@ function getColor(arr) {
29
29
  return arr ? arr.map(v => v / 255) : [0, 0, 0];
30
30
  }
31
31
 
32
+ function isEqualShallow(prevArr, nextArr) {
33
+ if (prevArr === nextArr) {
34
+ return true;
35
+ }
36
+ if (Array.isArray(prevArr) && Array.isArray(nextArr)) {
37
+ if (prevArr.length === nextArr.length) {
38
+ return !prevArr.some((v, i) => (v !== nextArr[i] && (
39
+ (!Array.isArray(v) && !Array.isArray(nextArr[i]))
40
+ || (v.length > 0 || nextArr[i].length > 0)
41
+ )
42
+ ));
43
+ }
44
+ }
45
+ return prevArr === nextArr;
46
+ }
47
+
32
48
 
33
49
  const defaultProps = {
34
50
  channelStrokeWidths: { type: 'array', value: null, compare: true },
@@ -39,6 +55,7 @@ const defaultProps = {
39
55
  colormap: { type: 'string', value: GLSL_COLORMAP_DEFAULT, compare: true },
40
56
  expressionData: { type: 'object', value: null, compare: true },
41
57
  multiFeatureValues: { type: 'array', value: null, compare: true },
58
+ multiMatrixObsIndex: { type: 'array', value: null, compare: true },
42
59
  setColorValues: { type: 'array', value: null, compare: true },
43
60
  channelFeatureValueColormaps: { type: 'array', value: null, compare: true },
44
61
  channelFeatureValueColormapRanges: { type: 'array', value: null, compare: true },
@@ -102,11 +119,14 @@ export default class BitmaskLayer extends XRLayer {
102
119
  updateState({ props, oldProps, changeFlags }) {
103
120
  super.updateState({ props, oldProps, changeFlags });
104
121
  if (
105
- props.multiFeatureValues !== oldProps.multiFeatureValues
106
- || props.setColorValues !== oldProps.setColorValues
107
- || props.channelIsSetColorMode !== oldProps.channelIsSetColorMode
122
+ !isEqualShallow(props.multiFeatureValues, oldProps.multiFeatureValues)
123
+ || !isEqualShallow(props.multiMatrixObsIndex, oldProps.multiMatrixObsIndex)
124
+ || !isEqualShallow(props.setColorValues, oldProps.setColorValues)
125
+ || !isEqualShallow(props.channelIsSetColorMode, oldProps.channelIsSetColorMode)
108
126
  ) {
109
- const { multiFeatureValues, setColorValues, channelIsSetColorMode } = this.props;
127
+ const {
128
+ multiFeatureValues, multiMatrixObsIndex, setColorValues, channelIsSetColorMode,
129
+ } = this.props;
110
130
  // Use one expressionTex for all channels,
111
131
  // using an offset mechanism.
112
132
  const [
@@ -118,6 +138,7 @@ export default class BitmaskLayer extends XRLayer {
118
138
  colorTexHeight,
119
139
  ] = this.multiSetsToTexture(
120
140
  multiFeatureValues,
141
+ multiMatrixObsIndex,
121
142
  setColorValues,
122
143
  channelIsSetColorMode,
123
144
  );
@@ -289,7 +310,12 @@ export default class BitmaskLayer extends XRLayer {
289
310
  });
290
311
  }
291
312
 
292
- multiSetsToTexture(multiFeatureValues, setColorValues, channelIsSetColorMode) {
313
+ multiSetsToTexture(
314
+ multiFeatureValues,
315
+ multiMatrixObsIndex,
316
+ setColorValues,
317
+ channelIsSetColorMode,
318
+ ) {
293
319
  const isWebGL2On = isWebGL2(this.context.gl);
294
320
 
295
321
  const [
@@ -301,6 +327,7 @@ export default class BitmaskLayer extends XRLayer {
301
327
  colorsOffsets,
302
328
  ] = multiSetsToTextureData(
303
329
  multiFeatureValues,
330
+ multiMatrixObsIndex,
304
331
  setColorValues,
305
332
  channelIsSetColorMode,
306
333
  MULTI_FEATURE_TEX_SIZE,
@@ -1,4 +1,6 @@
1
- import { extent } from 'd3-array';
1
+ /* eslint-disable radix */
2
+ /* eslint-disable-next-line camelcase */
3
+ import { extent, max as d3_max } from 'd3-array';
2
4
 
3
5
 
4
6
  function normalize(arr) {
@@ -21,17 +23,33 @@ function normalize(arr) {
21
23
  * @returns
22
24
  */
23
25
  export function multiSetsToTextureData(
24
- multiFeatureValues, setColorValues, channelIsSetColorMode, texSize,
26
+ multiFeatureValues, multiMatrixObsIndex, setColorValues, channelIsSetColorMode, texSize,
25
27
  ) {
26
28
  let totalValuesLength = 0;
27
29
  let totalColorsLength = 0;
28
30
 
29
31
  channelIsSetColorMode.forEach((isSetColorMode, channelIndex) => {
30
32
  if (isSetColorMode) {
31
- totalValuesLength += setColorValues[channelIndex]?.obsIndex?.length || 0;
33
+ // totalValuesLength += setColorValues[channelIndex]?.obsIndex?.length || 0;
32
34
  totalColorsLength += (setColorValues[channelIndex]?.setColors?.length || 0) * 3;
35
+
36
+ // TODO: if we can assume values are monotonically increasing,
37
+ // we can just use the final array value arr[-1] directly as the max.
38
+ totalValuesLength += (
39
+ setColorValues[channelIndex]?.obsIndex
40
+ ? d3_max(setColorValues[channelIndex].obsIndex.map(d => parseInt(d)))
41
+ : 0
42
+ );
33
43
  } else {
34
- totalValuesLength += multiFeatureValues[channelIndex]?.length || 0;
44
+ // totalValuesLength += multiFeatureValues[channelIndex]?.length || 0;
45
+
46
+ // TODO: if we can assume values are monotonically increasing,
47
+ // we can just use the final array value arr[-1] directly as the max.
48
+ totalValuesLength += (
49
+ multiMatrixObsIndex[channelIndex]
50
+ ? d3_max(multiMatrixObsIndex[channelIndex].map(d => parseInt(d)))
51
+ : (multiFeatureValues[channelIndex]?.length || 0)
52
+ );
35
53
  }
36
54
  });
37
55
 
@@ -56,15 +74,27 @@ export function multiSetsToTextureData(
56
74
  let colorOffset = 0;
57
75
  // Iterate over the data for each channel.
58
76
  channelIsSetColorMode.forEach((isSetColorMode, channelIndex) => {
77
+ const matrixObsIndex = multiMatrixObsIndex[channelIndex];
78
+ // Assume the bitmask values correspond to the values in the matrixObsIndex (off by one).
79
+ const bitmaskValueIsIndex = matrixObsIndex === null;
59
80
  if (isSetColorMode) {
60
81
  const { setColorIndices, setColors, obsIndex } = setColorValues[channelIndex] || {};
61
82
  if (setColorIndices && setColors && obsIndex) {
62
83
  for (let i = 0; i < obsIndex.length; i++) {
84
+ let obsId = String(i + 1);
85
+ let obsI = i;
86
+ // TODO: this uses the matrixObsIndex to determine the value of the flag, is that correct?
87
+ if (!bitmaskValueIsIndex) {
88
+ // We cannot assume that the values in the bitmask
89
+ // correspond to the values in the matrixObsIndex.
90
+ obsId = obsIndex[i];
91
+ obsI = parseInt(obsId) - 1;
92
+ }
63
93
  // We add one here to account for i being 0-based but the pixel values being 1-based
64
94
  // (to account for zero indicating the background of the segmentation bitmask).
65
- const colorIndex = setColorIndices.get(String(i + 1));
95
+ const colorIndex = setColorIndices.get(obsId);
66
96
  // Add one to the color index, so that we can use zero to indicate a "null" set color.
67
- totalData[indexOffset + i] = colorIndex === undefined ? 0 : colorIndex + 1;
97
+ totalData[indexOffset + obsI] = colorIndex === undefined ? 0 : colorIndex + 1;
68
98
  }
69
99
  for (let i = 0; i < setColors.length; i++) {
70
100
  const { color: [r, g, b] } = setColors[i];
@@ -79,8 +109,20 @@ export function multiSetsToTextureData(
79
109
  colorOffset += (setColors?.length || 0);
80
110
  } else {
81
111
  const featureArr = multiFeatureValues[channelIndex];
82
- // TODO: are these values always already normalized?
83
- totalData.set(normalize(featureArr), indexOffset);
112
+ const normalizedFeatureArr = normalize(featureArr);
113
+
114
+ if (!bitmaskValueIsIndex && matrixObsIndex) {
115
+ // We cannot assume that the values in the bitmask
116
+ // correspond to the values in the matrixObsIndex.
117
+ for (let i = 0; i < matrixObsIndex.length; i++) {
118
+ const obsId = matrixObsIndex[i];
119
+ const obsI = parseInt(obsId) - 1;
120
+ totalData[indexOffset + obsI] = normalizedFeatureArr[i];
121
+ }
122
+ } else {
123
+ // TODO: are these values always already normalized?
124
+ totalData.set(normalizedFeatureArr, indexOffset);
125
+ }
84
126
  indicesOffsets.push(indexOffset);
85
127
  indexOffset += featureArr.length;
86
128
  // Add a color offset so that the number of offsets still equals the number of channels.
@@ -11,6 +11,11 @@ describe('bitmask utils', () => {
11
11
  [10, 20, 30], // 3 expression values, one per glomerulus
12
12
  ];
13
13
 
14
+ const multiObsIndex = [
15
+ ['1', '2', '3', '4', '5'], // 5 cells
16
+ ['1', '2', '3'], // 3 glomeruli
17
+ ];
18
+
14
19
  const setColorValues = [
15
20
  {
16
21
  setColorIndices: (new Map([
@@ -54,6 +59,7 @@ describe('bitmask utils', () => {
54
59
  colorsOffsets,
55
60
  ] = multiSetsToTextureData(
56
61
  multiFeatureValues,
62
+ multiObsIndex,
57
63
  setColorValues,
58
64
  channelIsSetColorMode,
59
65
  10,