@aics/vole-core 3.14.0 → 3.14.1

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/es/Channel.js CHANGED
@@ -14,6 +14,7 @@ export default class Channel {
14
14
  };
15
15
  this.rawMin = 0;
16
16
  this.rawMax = 255;
17
+ this.time = 0;
17
18
 
18
19
  // on gpu
19
20
  this.dataTexture = new DataTexture(new Uint8Array(), 0, 0);
@@ -147,7 +148,7 @@ export default class Channel {
147
148
 
148
149
  // give the channel fresh data and initialize from that data
149
150
  // data is formatted as a texture atlas where each tile is a z slice of the volume
150
- setFromAtlas(bitsArray, w, h, dtype, rawMin, rawMax, subregionSize) {
151
+ setFromAtlas(bitsArray, w, h, dtype, rawMin, rawMax, subregionSize, time = 0) {
151
152
  this.dtype = dtype;
152
153
  this.imgData = {
153
154
  data: bitsArray,
@@ -157,6 +158,7 @@ export default class Channel {
157
158
  this.rebuildDataTexture(this.imgData.data, w, h);
158
159
  this.loaded = true;
159
160
  this.histogram = new Histogram(bitsArray);
161
+ this.time = time;
160
162
 
161
163
  // reuse old lut but auto-remap it to new data range
162
164
  this.setRawDataRange(rawMin, rawMax);
@@ -193,10 +195,11 @@ export default class Channel {
193
195
  }
194
196
 
195
197
  // give the channel fresh volume data and initialize from that data
196
- setFromVolumeData(bitsArray, vx, vy, vz, ax, ay, rawMin, rawMax, dtype) {
198
+ setFromVolumeData(bitsArray, vx, vy, vz, ax, ay, rawMin, rawMax, dtype, time = 0) {
197
199
  this.dims = [vx, vy, vz];
198
200
  this.volumeData = bitsArray;
199
201
  this.dtype = dtype;
202
+ this.time = time;
200
203
  // TODO FIXME performance hit for shuffling the data and storing 2 versions of it (could do this in worker at least?)
201
204
  this.packToAtlas(vx, vy, vz, ax, ay);
202
205
  this.loaded = true;
@@ -7,7 +7,7 @@ const fuseShaderSrcF = "precision highp float;\nprecision highp int;\nprecision
7
7
  /* babel-plugin-inline-import './constants/shaders/fuseI.frag' */
8
8
  const fuseShaderSrcI = "precision highp float;\nprecision highp int;\nprecision highp sampler2D;\nprecision highp sampler3D;\n\n// the lut texture is a 256x1 rgba texture for each channel\nuniform sampler2D lutSampler;\n\nuniform vec2 lutMinMax;\n\n// src texture is the raw volume intensity data\nuniform isampler2D srcTexture;\n\nvoid main()\n{\n ivec2 vUv = ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y));\n int intensity = texelFetch(srcTexture, vUv, 0).r;\n float ilookup = float(float(intensity) - lutMinMax.x) / float(lutMinMax.y - lutMinMax.x);\n // apply lut to intensity:\n vec4 pix = texture(lutSampler, vec2(ilookup, 0.5));\n gl_FragColor = vec4(pix.xyz*pix.w, pix.w);\n}\n";
9
9
  /* babel-plugin-inline-import './constants/shaders/colorizeUI.frag' */
10
- const colorizeSrcUI = "precision highp float;\nprecision highp int;\nprecision highp usampler2D;\nprecision highp sampler3D;\n\nuniform sampler2D featureData;\n/** Min and max feature values that define the endpoints of the color map. Values\n * outside the range will be clamped to the nearest endpoint.\n */\nuniform float featureColorRampMin;\nuniform float featureColorRampMax;\nuniform sampler2D colorRamp;\nuniform usampler2D inRangeIds;\nuniform usampler2D outlierData;\n\nuniform vec3 outlineColor;\n\n/** MUST be synchronized with the DrawMode enum in ColorizeCanvas! */\nconst uint DRAW_MODE_HIDE = 0u;\nconst uint DRAW_MODE_COLOR = 1u;\n\nuniform vec3 outlierColor;\nuniform uint outlierDrawMode;\nuniform vec3 outOfRangeColor;\nuniform uint outOfRangeDrawMode;\n\nuniform uint highlightedId;\n\nuniform bool hideOutOfRange;\n\n// src texture is the raw volume intensity data\nuniform usampler2D srcTexture;\n\nuint getId(ivec2 uv) {\n return texelFetch(srcTexture, uv, 0).r;\n}\n\nvec4 getFloatFromTex(sampler2D tex, int index) {\n int width = textureSize(tex, 0).x;\n ivec2 featurePos = ivec2(index % width, index / width);\n return texelFetch(tex, featurePos, 0);\n}\nuvec4 getUintFromTex(usampler2D tex, int index) {\n int width = textureSize(tex, 0).x;\n ivec2 featurePos = ivec2(index % width, index / width);\n return texelFetch(tex, featurePos, 0);\n}\nvec4 getColorRamp(float val) {\n float width = float(textureSize(colorRamp, 0).x);\n float range = (width - 1.0) / width;\n float adjustedVal = (0.5 / width) + (val * range);\n return texture(colorRamp, vec2(adjustedVal, 0.5));\n}\nvec4 getColorFromDrawMode(uint drawMode, vec3 defaultColor) {\n const uint DRAW_MODE_HIDE = 0u;\n vec3 backgroundColor = vec3(0.0, 0.0, 0.0);\n if (drawMode == DRAW_MODE_HIDE) {\n return vec4(backgroundColor, 0.0);\n } else {\n return vec4(defaultColor, 1.0);\n }\n}\n\nfloat getFeatureVal(uint id) {\n // Data buffer starts at 0, non-background segmentation IDs start at 1\n return getFloatFromTex(featureData, int(id) - 1).r;\n}\nuint getOutlierVal(uint id) {\n // Data buffer starts at 0, non-background segmentation IDs start at 1\n return getUintFromTex(outlierData, int(id) - 1).r;\n}\nbool getIsInRange(uint id) {\n return getUintFromTex(inRangeIds, int(id) - 1).r == 1u;\n}\nbool getIsOutlier(float featureVal, uint outlierVal) {\n return isinf(featureVal) || outlierVal != 0u;\n}\n\nvec4 getObjectColor(ivec2 sUv, float opacity) {\n // Get the segmentation id at this pixel\n uint id = getId(sUv);\n\n // A segmentation id of 0 represents background\n if (id == 0u) {\n return vec4(0,0,0,0);\n }\n\n // color the highlighted object\n if (id == highlightedId) {\n return vec4(outlineColor, 1.0);\n }\n\n\n float featureVal = getFeatureVal(id);\n uint outlierVal = getOutlierVal(id);\n float normFeatureVal = (featureVal - featureColorRampMin) / (featureColorRampMax - featureColorRampMin);\n\n // Use the selected draw mode to handle out of range and outlier values;\n // otherwise color with the color ramp as usual.\n bool isInRange = getIsInRange(id);\n bool isOutlier = getIsOutlier(featureVal, outlierVal);\n\n // Features outside the filtered/thresholded range will all be treated the same (use `outOfRangeDrawColor`).\n // Features inside the range can either be outliers or standard values, and are colored accordingly.\n vec4 color;\n if (isInRange) {\n if (isOutlier) {\n color = getColorFromDrawMode(outlierDrawMode, outlierColor);\n } else {\n color = getColorRamp(normFeatureVal);\n }\n } else {\n color = getColorFromDrawMode(outOfRangeDrawMode, outOfRangeColor);\n }\n color.a *= opacity;\n return color;\n}\n\nvoid main()\n{\n ivec2 vUv = ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y));\n gl_FragColor = getObjectColor(vUv, 1.0);\n}\n";
10
+ const colorizeSrcUI = "precision highp float;\nprecision highp int;\nprecision highp usampler2D;\nprecision highp sampler3D;\n\nuniform sampler2D featureData;\n/** Min and max feature values that define the endpoints of the color map. Values\n * outside the range will be clamped to the nearest endpoint.\n */\nuniform float featureColorRampMin;\nuniform float featureColorRampMax;\nuniform sampler2D colorRamp;\nuniform usampler2D inRangeIds;\nuniform usampler2D outlierData;\n\n/** \n * Offsets raw IDs sampled from the volume data to get the global\n * ID used to index into the feature and outlier data.\n*/\nuniform uint idOffset;\n\nuniform vec3 outlineColor;\n\n/** MUST be synchronized with the DrawMode enum in ColorizeCanvas! */\nconst uint DRAW_MODE_HIDE = 0u;\nconst uint DRAW_MODE_COLOR = 1u;\n\nuniform vec3 outlierColor;\nuniform uint outlierDrawMode;\nuniform vec3 outOfRangeColor;\nuniform uint outOfRangeDrawMode;\n\nuniform uint highlightedId;\n\nuniform bool hideOutOfRange;\n\n// src texture is the raw volume intensity data\nuniform usampler2D srcTexture;\n\nuint getId(ivec2 uv) {\n uint rawId = texelFetch(srcTexture, uv, 0).r;\n if (rawId == 0u) {\n return 0u;\n }\n return rawId + idOffset;\n}\nvec4 getFloatFromTex(sampler2D tex, int index) {\n int width = textureSize(tex, 0).x;\n ivec2 featurePos = ivec2(index % width, index / width);\n return texelFetch(tex, featurePos, 0);\n}\nuvec4 getUintFromTex(usampler2D tex, int index) {\n int width = textureSize(tex, 0).x;\n ivec2 featurePos = ivec2(index % width, index / width);\n return texelFetch(tex, featurePos, 0);\n}\nvec4 getColorRamp(float val) {\n float width = float(textureSize(colorRamp, 0).x);\n float range = (width - 1.0) / width;\n float adjustedVal = (0.5 / width) + (val * range);\n return texture(colorRamp, vec2(adjustedVal, 0.5));\n}\nvec4 getColorFromDrawMode(uint drawMode, vec3 defaultColor) {\n const uint DRAW_MODE_HIDE = 0u;\n vec3 backgroundColor = vec3(0.0, 0.0, 0.0);\n if (drawMode == DRAW_MODE_HIDE) {\n return vec4(backgroundColor, 0.0);\n } else {\n return vec4(defaultColor, 1.0);\n }\n}\n\nfloat getFeatureVal(uint id) {\n // Data buffer starts at 0, non-background segmentation IDs start at 1\n return getFloatFromTex(featureData, int(id) - 1).r;\n}\nuint getOutlierVal(uint id) {\n // Data buffer starts at 0, non-background segmentation IDs start at 1\n return getUintFromTex(outlierData, int(id) - 1).r;\n}\nbool getIsInRange(uint id) {\n return getUintFromTex(inRangeIds, int(id) - 1).r == 1u;\n}\nbool getIsOutlier(float featureVal, uint outlierVal) {\n return isinf(featureVal) || outlierVal != 0u;\n}\n\nvec4 getObjectColor(ivec2 sUv, float opacity) {\n // Get the segmentation id at this pixel\n uint id = getId(sUv);\n\n // A segmentation id of 0 represents background\n if (id == 0u) {\n return vec4(0, 0, 0, 0);\n }\n\n // color the highlighted object\n if (id == highlightedId) {\n return vec4(outlineColor, 1.0);\n }\n\n float featureVal = getFeatureVal(id);\n uint outlierVal = getOutlierVal(id);\n float normFeatureVal = (featureVal - featureColorRampMin) / (featureColorRampMax - featureColorRampMin);\n\n // Use the selected draw mode to handle out of range and outlier values;\n // otherwise color with the color ramp as usual.\n bool isInRange = getIsInRange(id);\n bool isOutlier = getIsOutlier(featureVal, outlierVal);\n\n // Features outside the filtered/thresholded range will all be treated the same (use `outOfRangeDrawColor`).\n // Features inside the range can either be outliers or standard values, and are colored accordingly.\n vec4 color;\n if (isInRange) {\n if (isOutlier) {\n color = getColorFromDrawMode(outlierDrawMode, outlierColor);\n } else {\n color = getColorRamp(normFeatureVal);\n }\n } else {\n color = getColorFromDrawMode(outOfRangeDrawMode, outOfRangeColor);\n }\n color.a *= opacity;\n return color;\n}\n\nvoid main() {\n ivec2 vUv = ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y));\n gl_FragColor = getObjectColor(vUv, 1.0);\n}\n";
11
11
  // This is the owner of the fused RGBA volume texture atlas, and the mask texture atlas.
12
12
  // This module is responsible for updating the fused texture, given the read-only volume channel data.
13
13
  export default class FusedChannelData {
@@ -124,6 +124,9 @@ export default class FusedChannelData {
124
124
  },
125
125
  hideOutOfRange: {
126
126
  value: false
127
+ },
128
+ idOffset: {
129
+ value: 0
127
130
  }
128
131
  },
129
132
  fragmentShader: fragShaderSrc,
@@ -205,19 +208,24 @@ export default class FusedChannelData {
205
208
  const mat = this.getShader(channels[chIndex].dtype, isColorize).clone();
206
209
  mat.uniforms.srcTexture.value = channels[chIndex].dataTexture;
207
210
  mat.uniforms.highlightedId.value = combination[i].selectedID == undefined ? -1 : combination[i].selectedID;
208
- if (isColorize) {
209
- mat.uniforms.featureData.value = combination[i].feature?.idsToFeatureValue;
210
- mat.uniforms.outlierData.value = combination[i].feature?.outlierData;
211
- mat.uniforms.inRangeIds.value = combination[i].feature?.inRangeIds;
212
- mat.uniforms.featureColorRampMin.value = combination[i].feature?.featureMin;
213
- mat.uniforms.featureColorRampMax.value = combination[i].feature?.featureMax;
214
- mat.uniforms.colorRamp.value = combination[i].feature?.featureValueToColor;
215
- mat.uniforms.outlineColor.value = combination[i].feature?.outlineColor;
216
- mat.uniforms.outlierColor.value = combination[i].feature?.outlierColor;
217
- mat.uniforms.outOfRangeColor.value = combination[i].feature?.outOfRangeColor;
218
- mat.uniforms.outlierDrawMode.value = combination[i].feature?.outlierDrawMode;
219
- mat.uniforms.outOfRangeDrawMode.value = combination[i].feature?.outOfRangeDrawMode;
220
- mat.uniforms.hideOutOfRange.value = combination[i].feature?.hideOutOfRange;
211
+ const feature = combination[i].feature;
212
+ if (isColorize && feature) {
213
+ mat.uniforms.featureData.value = feature.idsToFeatureValue;
214
+ mat.uniforms.outlierData.value = feature.outlierData;
215
+ mat.uniforms.inRangeIds.value = feature.inRangeIds;
216
+ mat.uniforms.featureColorRampMin.value = feature.featureMin;
217
+ mat.uniforms.featureColorRampMax.value = feature.featureMax;
218
+ mat.uniforms.colorRamp.value = feature.featureValueToColor;
219
+ mat.uniforms.outlineColor.value = feature.outlineColor;
220
+ mat.uniforms.outlierColor.value = feature.outlierColor;
221
+ mat.uniforms.outOfRangeColor.value = feature.outOfRangeColor;
222
+ mat.uniforms.outlierDrawMode.value = feature.outlierDrawMode;
223
+ mat.uniforms.outOfRangeDrawMode.value = feature.outOfRangeDrawMode;
224
+ mat.uniforms.hideOutOfRange.value = feature.hideOutOfRange;
225
+ // Offset IDs based on the current frame, for data without
226
+ // globally-unique IDs.
227
+ const idOffset = feature.timeToIdOffset[channels[chIndex].time] ?? 0;
228
+ mat.uniforms.idOffset.value = idOffset;
221
229
  } else {
222
230
  // the lut texture is spanning only the data range of the channel, not the datatype range
223
231
  mat.uniforms.lutMinMax.value = new Vector2(channels[chIndex].rawMin, channels[chIndex].rawMax);
package/es/Volume.js CHANGED
@@ -218,8 +218,8 @@ export default class Volume {
218
218
  * @param {number} atlaswidth
219
219
  * @param {number} atlasheight
220
220
  */
221
- setChannelDataFromAtlas(channelIndex, atlasdata, atlaswidth, atlasheight, range, dtype = "uint8") {
222
- this.channels[channelIndex].setFromAtlas(atlasdata, atlaswidth, atlasheight, dtype, range[0], range[1], this.imageInfo.subregionSize);
221
+ setChannelDataFromAtlas(channelIndex, atlasdata, atlaswidth, atlasheight, range, dtype = "uint8", time = 0) {
222
+ this.channels[channelIndex].setFromAtlas(atlasdata, atlaswidth, atlasheight, dtype, range[0], range[1], this.imageInfo.subregionSize, time);
223
223
  this.onChannelLoaded([channelIndex]);
224
224
  }
225
225
 
@@ -229,12 +229,12 @@ export default class Volume {
229
229
  * @param {number} channelIndex
230
230
  * @param {Uint8Array} volumeData
231
231
  */
232
- setChannelDataFromVolume(channelIndex, volumeData, range, dtype = "uint8") {
232
+ setChannelDataFromVolume(channelIndex, volumeData, range, dtype = "uint8", time = 0) {
233
233
  const {
234
234
  subregionSize,
235
235
  atlasTileDims
236
236
  } = this.imageInfo;
237
- this.channels[channelIndex].setFromVolumeData(volumeData, subregionSize.x, subregionSize.y, subregionSize.z, atlasTileDims.x * subregionSize.x, atlasTileDims.y * subregionSize.y, range[0], range[1], dtype);
237
+ this.channels[channelIndex].setFromVolumeData(volumeData, subregionSize.x, subregionSize.y, subregionSize.z, atlasTileDims.x * subregionSize.x, atlasTileDims.y * subregionSize.y, range[0], range[1], dtype, time);
238
238
  this.onChannelLoaded([channelIndex]);
239
239
  }
240
240
 
@@ -114,10 +114,11 @@ export class ThreadableVolumeLoader {
114
114
  const dtype = dtypes[i];
115
115
  const data = dataArrays[i];
116
116
  const range = ranges[i];
117
+ const time = volume.loadSpec.time;
117
118
  if (atlasDims) {
118
- volume.setChannelDataFromAtlas(channelIndex, data, atlasDims[0], atlasDims[1], range, dtype);
119
+ volume.setChannelDataFromAtlas(channelIndex, data, atlasDims[0], atlasDims[1], range, dtype, time);
119
120
  } else {
120
- volume.setChannelDataFromVolume(channelIndex, data, range, dtype);
121
+ volume.setChannelDataFromVolume(channelIndex, data, range, dtype, time);
121
122
  }
122
123
  onChannelLoaded?.(volume, channelIndex);
123
124
  }
@@ -25,6 +25,7 @@ export default class Channel {
25
25
  lutTexture: DataTexture;
26
26
  rawMin: number;
27
27
  rawMax: number;
28
+ time: number;
28
29
  constructor(name: string);
29
30
  combineLuts(rgbColor: [number, number, number] | number, out?: Uint8Array): Uint8Array;
30
31
  setRawDataRange(min: number, max: number): void;
@@ -33,9 +34,9 @@ export default class Channel {
33
34
  normalizeRaw(val: number): number;
34
35
  getIntensityFromAtlas(x: number, y: number, z: number): number;
35
36
  private rebuildDataTexture;
36
- setFromAtlas(bitsArray: TypedArray<NumberType>, w: number, h: number, dtype: NumberType, rawMin: number, rawMax: number, subregionSize: Vector3): void;
37
+ setFromAtlas(bitsArray: TypedArray<NumberType>, w: number, h: number, dtype: NumberType, rawMin: number, rawMax: number, subregionSize: Vector3, time?: number): void;
37
38
  private unpackFromAtlas;
38
- setFromVolumeData(bitsArray: TypedArray<NumberType>, vx: number, vy: number, vz: number, ax: number, ay: number, rawMin: number, rawMax: number, dtype: NumberType): void;
39
+ setFromVolumeData(bitsArray: TypedArray<NumberType>, vx: number, vy: number, vz: number, ax: number, ay: number, rawMin: number, rawMax: number, dtype: NumberType, time?: number): void;
39
40
  private packToAtlas;
40
41
  setLut(lut: Lut): void;
41
42
  setColorPalette(palette: Uint8Array): void;
@@ -82,13 +82,13 @@ export default class Volume {
82
82
  * @param {number} atlaswidth
83
83
  * @param {number} atlasheight
84
84
  */
85
- setChannelDataFromAtlas(channelIndex: number, atlasdata: TypedArray<NumberType>, atlaswidth: number, atlasheight: number, range: [number, number], dtype?: NumberType): void;
85
+ setChannelDataFromAtlas(channelIndex: number, atlasdata: TypedArray<NumberType>, atlaswidth: number, atlasheight: number, range: [number, number], dtype?: NumberType, time?: number): void;
86
86
  /**
87
87
  * Assign volume data as a 3d array ordered x,y,z. The xy size must be equal to tilewidth*tileheight from the imageInfo used to construct this Volume. Assumes that the incoming data is consistent with the image's pre-existing imageInfo tile metadata.
88
88
  * @param {number} channelIndex
89
89
  * @param {Uint8Array} volumeData
90
90
  */
91
- setChannelDataFromVolume(channelIndex: number, volumeData: TypedArray<NumberType>, range: [number, number], dtype?: NumberType): void;
91
+ setChannelDataFromVolume(channelIndex: number, volumeData: TypedArray<NumberType>, range: [number, number], dtype?: NumberType, time?: number): void;
92
92
  /**
93
93
  * Add a new channel ready to receive data from one of the setChannelDataFrom* calls.
94
94
  * Name and color will be defaulted if not provided. For now, leave imageInfo alone as the "original" data
@@ -30,6 +30,18 @@ export declare const ARRAY_CONSTRUCTORS: {
30
30
  export interface ColorizeFeature {
31
31
  idsToFeatureValue: DataTexture;
32
32
  featureValueToColor: DataTexture;
33
+ /**
34
+ * A mapping from the current frame number to the ID offset for that frame.
35
+ * This is used for data where the raw IDs are not globally-unique. Adding the
36
+ * offset to the raw ID gives the unique, global ID that can be used to index
37
+ * into the `idsToFeatureValue` and other colorize-related data textures.
38
+ *
39
+ * For each raw ID `i` at some time `t`, the global ID is `i +
40
+ * timeToIdOffset[t]`.
41
+ *
42
+ * If raw IDs are globally-unique, this array should be all zeros.
43
+ */
44
+ timeToIdOffset: Uint32Array;
33
45
  inRangeIds: DataTexture;
34
46
  outlierData: DataTexture;
35
47
  featureMin: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aics/vole-core",
3
- "version": "3.14.0",
3
+ "version": "3.14.1",
4
4
  "description": "volume renderer for 3d, 4d, or 5d imaging data with OME-Zarr support",
5
5
  "main": "es/index.js",
6
6
  "type": "module",