@aics/vole-core 3.15.6 → 4.0.0
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/ContourPass.js +2 -2
- package/es/Histogram.js +55 -12
- package/es/Line3d.js +4 -4
- package/es/loaders/TiffLoader.js +29 -12
- package/es/loaders/index.js +2 -1
- package/es/types/ContourPass.d.ts +1 -1
- package/es/types/Histogram.d.ts +18 -2
- package/es/types/Line3d.d.ts +1 -1
- package/es/types/loaders/TiffLoader.d.ts +2 -2
- package/es/types/types.d.ts +1 -0
- package/es/types.js +3 -0
- package/es/workers/VolumeLoaderContext.js +3 -1
- package/package.json +1 -1
package/es/ContourPass.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Color, DataTexture, FloatType, RedIntegerFormat, RGBAFormat, Uniform, UnsignedIntType } from "three";
|
|
2
|
-
import
|
|
2
|
+
import { clamp } from "three/src/math/MathUtils.js";
|
|
3
|
+
import RenderToBuffer, { RenderPassType } from "./RenderToBuffer.js";
|
|
3
4
|
/* babel-plugin-inline-import './constants/shaders/contour.frag' */
|
|
4
5
|
const contourFragShader = "precision highp float;\nprecision highp int;\nprecision highp usampler2D;\nprecision highp sampler3D;\n\n/**\n * LUT mapping from the segmentation ID (raw pixel value) to the\n * global ID (index in data buffers like `featureData` and `outlierData`).\n * \n * For a given local pixel ID `localId`, the global ID is given by:\n * `localIdToGlobalId[localId - localIdOffset] - 1`.\n*/\nuniform usampler2D localIdToGlobalId;\nuniform uint localIdOffset;\nuniform bool useGlobalIdLookup;\n/* Pick buffer. Used to determine IDs. */\nuniform sampler2D pickBuffer;\n\nuniform int highlightedId;\nuniform int outlineThickness;\nuniform float outlineAlpha;\nuniform vec3 outlineColor;\nuniform float devicePixelRatio;\n\nconst uint BACKGROUND_ID = 0u;\nconst uint MISSING_DATA_ID = 0xFFFFFFFFu;\nconst int ID_OFFSET = 1;\n\nuvec4 getUintFromTex(usampler2D tex, int index) {\n int width = textureSize(tex, 0).x;\n ivec2 featurePos = ivec2(index % width, index / width);\n return uvec4(texelFetch(tex, featurePos, 0));\n}\n\nuint getId(ivec2 uv) {\n float rawId = texelFetch(pickBuffer, uv, 0).g;\n if (rawId == 0.0) {\n return BACKGROUND_ID;\n }\n int localId = int(rawId) - int(localIdOffset);\n if (!useGlobalIdLookup) {\n return uint(localId + ID_OFFSET);\n }\n uvec4 c = getUintFromTex(localIdToGlobalId, localId);\n // Note: IDs are offset by `ID_OFFSET` (`=1`) to reserve `0` for local IDs\n // that don't have associated data in the global lookup. `ID_OFFSET` MUST be\n // subtracted from the ID when accessing data buffers.\n uint globalId = c.r;\n if (globalId == 0u) {\n return MISSING_DATA_ID;\n }\n return globalId;\n}\n\nbool isEdge(ivec2 uv, int id, int thickness) {\n float wStep = 1.0;\n float hStep = 1.0;\n float thicknessFloat = float(thickness);\n // sample around the pixel to see if we are on an edge\n int R = int(getId(uv + ivec2(thicknessFloat * wStep, 0))) - ID_OFFSET;\n int L = int(getId(uv + ivec2(-thicknessFloat * wStep, 0))) - ID_OFFSET;\n int T = int(getId(uv + ivec2(0, thicknessFloat * hStep))) - ID_OFFSET;\n int B = int(getId(uv + ivec2(0, -thicknessFloat * hStep))) - ID_OFFSET;\n // if any neighbors are not id then this is an edge\n return id != -1 && (R != id || L != id || T != id || B != id);\n}\n\nvoid main(void) {\n ivec2 vUv = ivec2(int(gl_FragCoord.x / devicePixelRatio), int(gl_FragCoord.y / devicePixelRatio));\n\n uint rawId = getId(vUv);\n int id = int(rawId) - ID_OFFSET;\n\n if (id == highlightedId && isEdge(vUv, id, outlineThickness)) {\n gl_FragColor = vec4(outlineColor, outlineAlpha);\n } else {\n gl_FragColor = vec4(0, 0, 0, 0.0);\n }\n}";
|
|
5
|
-
import { clamp } from "three/src/math/MathUtils";
|
|
6
6
|
const makeDefaultUniforms = () => {
|
|
7
7
|
const pickBufferTex = new DataTexture(new Float32Array([1, 0, 0, 0]), 1, 1, RGBAFormat, FloatType);
|
|
8
8
|
const localIdToGlobalId = new DataTexture(new Uint32Array([0]), 1, 1, RedIntegerFormat, UnsignedIntType);
|
package/es/Histogram.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isFloatTypeArray } from "./types.js";
|
|
1
2
|
const NBINS = 256;
|
|
2
3
|
/**
|
|
3
4
|
* Builds a histogram with 256 bins from a data array. Assume data is 8 bit single channel grayscale.
|
|
@@ -59,20 +60,38 @@ export default class Histogram {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// for values that lie exactly on last bin we need to subtract one
|
|
67
|
-
if (binIndex === numBins) {
|
|
68
|
-
binIndex--;
|
|
63
|
+
static findBin(dataValue, dataMin, binSize, castToInt) {
|
|
64
|
+
const binIndex = (dataValue - dataMin) / binSize;
|
|
65
|
+
if (!castToInt) {
|
|
66
|
+
return binIndex;
|
|
69
67
|
}
|
|
70
|
-
return binIndex;
|
|
68
|
+
return Math.max(Math.min(Math.floor(binIndex), NBINS - 1), 0);
|
|
71
69
|
}
|
|
72
70
|
|
|
73
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Returns the integer bin index for the given value. If a value is outside
|
|
73
|
+
* the histogram range, it will be clamped to the nearest bin.
|
|
74
|
+
*/
|
|
74
75
|
findBinOfValue(value) {
|
|
75
|
-
return Histogram.findBin(value, this.min, this.binSize,
|
|
76
|
+
return Histogram.findBin(value, this.min, this.binSize, true);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Returns a fractional bin index for the given value. If a value is not a bin
|
|
81
|
+
* boundary, returns an interpolated index. Note that this can return a value
|
|
82
|
+
* outside the range of valid bins.
|
|
83
|
+
*/
|
|
84
|
+
findFractionalBinOfValue(value) {
|
|
85
|
+
return Histogram.findBin(value, this.min, this.binSize, false);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Returns an absolute data value from a given (integer or fractional) bin index.
|
|
90
|
+
* Note that, if the bin index is outside of the bin range, the returned value
|
|
91
|
+
* will also be outside the value range.
|
|
92
|
+
*/
|
|
93
|
+
getValueFromBinIndex(binIndex) {
|
|
94
|
+
return this.min + binIndex * this.binSize;
|
|
76
95
|
}
|
|
77
96
|
|
|
78
97
|
/**
|
|
@@ -234,10 +253,34 @@ export default class Histogram {
|
|
|
234
253
|
}
|
|
235
254
|
}
|
|
236
255
|
const bins = new Uint32Array(numBins).fill(0);
|
|
237
|
-
|
|
256
|
+
|
|
257
|
+
// Bins should have equal widths and span the data min to the data max. The
|
|
258
|
+
// handling of the max value is slightly different between integers and
|
|
259
|
+
// floats; in the float case, the last bin should have `max` as its
|
|
260
|
+
// inclusive upper bound, while in the integer case, the last bin should
|
|
261
|
+
// have an exclusive upper bound of `max + 1`.
|
|
262
|
+
//
|
|
263
|
+
// For example, let's say we have a data range of `[min=0, max=3]` and 4
|
|
264
|
+
// bins.
|
|
265
|
+
//
|
|
266
|
+
// If this is integer data, we want each bin to have ranges [0, 1), [1, 2),
|
|
267
|
+
// [2, 3), [3, 4), and a bin size of 1.
|
|
268
|
+
//
|
|
269
|
+
// |----|----|----|----|
|
|
270
|
+
// 0 1 2 3 4 <- exclusive
|
|
271
|
+
//
|
|
272
|
+
// For continuous (float) data, our bins should have ranges [0, 0.75),
|
|
273
|
+
// [0.75, 1.5), [1.5, 2.25), [2.25, 3] and a bin size of 0.75.
|
|
274
|
+
//
|
|
275
|
+
// |----|----|----|----|
|
|
276
|
+
// 0 0.75 1.5 2.25 3 <- inclusive
|
|
277
|
+
//
|
|
278
|
+
|
|
279
|
+
const binMax = isFloatTypeArray(arr) ? max : max + 1;
|
|
280
|
+
const binSize = binMax <= min ? 1 : (binMax - min) / numBins;
|
|
238
281
|
for (let i = 0; i < arr.length; i++) {
|
|
239
282
|
const item = arr[i];
|
|
240
|
-
const binIndex = Histogram.findBin(item, min, binSize,
|
|
283
|
+
const binIndex = Histogram.findBin(item, min, binSize, true);
|
|
241
284
|
bins[binIndex]++;
|
|
242
285
|
}
|
|
243
286
|
return {
|
package/es/Line3d.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Group, Vector3 } from "three";
|
|
2
|
-
import { LineMaterial } from "three/addons/lines/LineMaterial";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { LineMaterial } from "three/addons/lines/LineMaterial.js";
|
|
3
|
+
import { LineSegments2 } from "three/addons/lines/LineSegments2.js";
|
|
4
|
+
import { LineSegmentsGeometry } from "three/addons/lines/LineSegmentsGeometry.js";
|
|
5
|
+
import { MESH_NO_PICK_OCCLUSION_LAYER, OVERLAY_LAYER } from "./ThreeJsPanel.js";
|
|
6
6
|
const DEFAULT_VERTEX_BUFFER_SIZE = 1020;
|
|
7
7
|
|
|
8
8
|
/**
|
package/es/loaders/TiffLoader.js
CHANGED
|
@@ -11,15 +11,16 @@ function prepareXML(xml) {
|
|
|
11
11
|
return xml.trim().replace(expr, "").trim();
|
|
12
12
|
}
|
|
13
13
|
function getOME(xml) {
|
|
14
|
+
if (xml === undefined) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
const prepared = prepareXML(xml);
|
|
14
18
|
const parser = new DOMParser();
|
|
15
19
|
try {
|
|
16
|
-
const xmlDoc = parser.parseFromString(
|
|
20
|
+
const xmlDoc = parser.parseFromString(prepared, "text/xml");
|
|
17
21
|
return xmlDoc.getElementsByTagName("OME")[0];
|
|
18
22
|
} catch (e) {
|
|
19
|
-
|
|
20
|
-
type: VolumeLoadErrorType.INVALID_METADATA,
|
|
21
|
-
cause: e
|
|
22
|
-
});
|
|
23
|
+
return undefined;
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
class OMEDims {
|
|
@@ -85,6 +86,7 @@ function getOMEDims(imageEl) {
|
|
|
85
86
|
return dims;
|
|
86
87
|
}
|
|
87
88
|
const getBytesPerSample = type => type === "uint8" ? 1 : type === "uint16" ? 2 : 4;
|
|
89
|
+
const getPixelType = pxSize => pxSize === 1 ? "uint8" : pxSize === 2 ? "uint16" : "uint32";
|
|
88
90
|
|
|
89
91
|
// Despite the class `TiffLoader` extends, this loader is not threadable, since geotiff internally uses features that
|
|
90
92
|
// aren't available on workers. It uses its own specialized workers anyways.
|
|
@@ -95,17 +97,32 @@ class TiffLoader extends ThreadableVolumeLoader {
|
|
|
95
97
|
}
|
|
96
98
|
async loadOmeDims() {
|
|
97
99
|
if (!this.dims) {
|
|
98
|
-
const tiff = await fromUrl(this.url, {
|
|
100
|
+
const tiff = await fromUrl(this.url[0], {
|
|
99
101
|
allowFullFile: true
|
|
100
|
-
}).catch(wrapVolumeLoadError(`Could not open TIFF file at ${this.url}`, VolumeLoadErrorType.NOT_FOUND));
|
|
102
|
+
}).catch(wrapVolumeLoadError(`Could not open TIFF file at ${this.url[0]}`, VolumeLoadErrorType.NOT_FOUND));
|
|
101
103
|
// DO NOT DO THIS, ITS SLOW
|
|
102
104
|
// const imagecount = await tiff.getImageCount();
|
|
103
105
|
// read the FIRST image
|
|
104
106
|
const image = await tiff.getImage().catch(wrapVolumeLoadError("Failed to open TIFF image", VolumeLoadErrorType.NOT_FOUND));
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
const omeEl = getOME(image.getFileDirectory().ImageDescription);
|
|
108
|
+
if (omeEl !== undefined) {
|
|
109
|
+
const image0El = omeEl.getElementsByTagName("Image")[0];
|
|
110
|
+
this.dims = getOMEDims(image0El);
|
|
111
|
+
} else {
|
|
112
|
+
console.warn("Could not read OME-TIFF metadata from file. Doing our best with base TIFF metadata.");
|
|
113
|
+
this.dims = new OMEDims();
|
|
114
|
+
this.dims.sizex = image.getWidth();
|
|
115
|
+
this.dims.sizey = image.getHeight();
|
|
116
|
+
// TODO this is a big hack/assumption about only loading multi-source tiffs that are not OMETIFF.
|
|
117
|
+
// We really have to check each url in the array for sizec to get the total number of channels
|
|
118
|
+
// See combinedNumChannels in ImageInfo below.
|
|
119
|
+
// Also compare with how OMEZarrLoader does this.
|
|
120
|
+
this.dims.sizec = this.url.length > 1 ? this.url.length : 1; // if multiple urls, assume one channel per url
|
|
121
|
+
this.dims.pixeltype = getPixelType(image.getBytesPerPixel());
|
|
122
|
+
this.dims.channelnames = Array.from({
|
|
123
|
+
length: this.dims.sizec
|
|
124
|
+
}, (_, i) => "Channel" + i);
|
|
125
|
+
}
|
|
109
126
|
}
|
|
110
127
|
return this.dims;
|
|
111
128
|
}
|
|
@@ -189,7 +206,7 @@ class TiffLoader extends ThreadableVolumeLoader {
|
|
|
189
206
|
sizez: volumeSize.z,
|
|
190
207
|
dimensionOrder: dims.dimensionorder,
|
|
191
208
|
bytesPerSample: getBytesPerSample(dims.pixeltype),
|
|
192
|
-
url: this.url
|
|
209
|
+
url: this.url.length > 1 ? this.url[channel] : this.url[0] // if multiple urls, use the channel index to select the right one
|
|
193
210
|
};
|
|
194
211
|
const worker = new Worker(new URL("../workers/FetchTiffWorker", import.meta.url), {
|
|
195
212
|
type: "module"
|
package/es/loaders/index.js
CHANGED
|
@@ -24,13 +24,14 @@ export function pathToFileType(path) {
|
|
|
24
24
|
export async function createVolumeLoader(path, options) {
|
|
25
25
|
const pathString = Array.isArray(path) ? path[0] : path;
|
|
26
26
|
const fileType = options?.fileType || pathToFileType(pathString);
|
|
27
|
+
const pathArrayForTiffLoader = Array.isArray(path) ? path : [path];
|
|
27
28
|
switch (fileType) {
|
|
28
29
|
case VolumeFileFormat.ZARR:
|
|
29
30
|
return await OMEZarrLoader.createLoader(path, options?.scene, options?.cache, options?.queue, options?.fetchOptions);
|
|
30
31
|
case VolumeFileFormat.JSON:
|
|
31
32
|
return new JsonImageInfoLoader(path, options?.cache);
|
|
32
33
|
case VolumeFileFormat.TIFF:
|
|
33
|
-
return new TiffLoader(
|
|
34
|
+
return new TiffLoader(pathArrayForTiffLoader);
|
|
34
35
|
case VolumeFileFormat.DATA:
|
|
35
36
|
if (!options?.rawArrayOptions) {
|
|
36
37
|
throw new Error("Must provide RawArrayOptions for RawArrayLoader");
|
package/es/types/Histogram.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type TypedArray, type NumberType } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Builds a histogram with 256 bins from a data array. Assume data is 8 bit single channel grayscale.
|
|
4
4
|
* @class
|
|
@@ -19,8 +19,24 @@ export default class Histogram {
|
|
|
19
19
|
private pixelCount;
|
|
20
20
|
maxBin: number;
|
|
21
21
|
constructor(data: TypedArray<NumberType>);
|
|
22
|
-
static findBin
|
|
22
|
+
private static findBin;
|
|
23
|
+
/**
|
|
24
|
+
* Returns the integer bin index for the given value. If a value is outside
|
|
25
|
+
* the histogram range, it will be clamped to the nearest bin.
|
|
26
|
+
*/
|
|
23
27
|
findBinOfValue(value: number): number;
|
|
28
|
+
/**
|
|
29
|
+
* Returns a fractional bin index for the given value. If a value is not a bin
|
|
30
|
+
* boundary, returns an interpolated index. Note that this can return a value
|
|
31
|
+
* outside the range of valid bins.
|
|
32
|
+
*/
|
|
33
|
+
findFractionalBinOfValue(value: number): number;
|
|
34
|
+
/**
|
|
35
|
+
* Returns an absolute data value from a given (integer or fractional) bin index.
|
|
36
|
+
* Note that, if the bin index is outside of the bin range, the returned value
|
|
37
|
+
* will also be outside the value range.
|
|
38
|
+
*/
|
|
39
|
+
getValueFromBinIndex(binIndex: number): number;
|
|
24
40
|
/**
|
|
25
41
|
* Return the min data value
|
|
26
42
|
* @return {number}
|
package/es/types/Line3d.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Color, Euler, Group, Vector3 } from "three";
|
|
2
|
-
import { IDrawableObject } from "./types";
|
|
2
|
+
import { IDrawableObject } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* Simple wrapper for a 3D line segments object, with controls for vertex data,
|
|
5
5
|
* color, width, and segments visible.
|
|
@@ -34,9 +34,9 @@ export type TiffLoadResult = {
|
|
|
34
34
|
range: [number, number];
|
|
35
35
|
};
|
|
36
36
|
declare class TiffLoader extends ThreadableVolumeLoader {
|
|
37
|
-
url: string;
|
|
37
|
+
url: string[];
|
|
38
38
|
dims?: OMEDims;
|
|
39
|
-
constructor(url: string);
|
|
39
|
+
constructor(url: string[]);
|
|
40
40
|
private loadOmeDims;
|
|
41
41
|
loadDims(_loadSpec: LoadSpec): Promise<VolumeDims[]>;
|
|
42
42
|
createImageInfo(_loadSpec: LoadSpec): Promise<LoadedVolumeInfo>;
|
package/es/types/types.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export declare const ARRAY_CONSTRUCTORS: {
|
|
|
27
27
|
float32: Float32ArrayConstructor;
|
|
28
28
|
float64: Float64ArrayConstructor;
|
|
29
29
|
};
|
|
30
|
+
export declare function isFloatTypeArray(array: TypedArray<NumberType>): array is Float32Array | Float64Array;
|
|
30
31
|
export interface ColorizeFeature {
|
|
31
32
|
idsToFeatureValue: DataTexture;
|
|
32
33
|
featureValueToColor: DataTexture;
|
package/es/types.js
CHANGED
|
@@ -13,6 +13,9 @@ export const ARRAY_CONSTRUCTORS = {
|
|
|
13
13
|
float32: Float32Array,
|
|
14
14
|
float64: Float64Array
|
|
15
15
|
};
|
|
16
|
+
export function isFloatTypeArray(array) {
|
|
17
|
+
return array instanceof Float32Array || array instanceof Float64Array;
|
|
18
|
+
}
|
|
16
19
|
/** If `FuseChannel.rgbColor` is this value, it is disabled from fusion. */
|
|
17
20
|
export const FUSE_DISABLED_RGB_COLOR = 0;
|
|
18
21
|
|
|
@@ -164,7 +164,9 @@ class VolumeLoaderContext {
|
|
|
164
164
|
const pathString = Array.isArray(path) ? path[0] : path;
|
|
165
165
|
const fileType = options?.fileType || pathToFileType(pathString);
|
|
166
166
|
if (fileType === VolumeFileFormat.TIFF) {
|
|
167
|
-
|
|
167
|
+
// tiff loader accepts array of paths for separate channel sources
|
|
168
|
+
const pathArray = Array.isArray(path) ? path : [path];
|
|
169
|
+
return new TiffLoader(pathArray);
|
|
168
170
|
} else if (fileType === VolumeFileFormat.DATA) {
|
|
169
171
|
if (!options?.rawArrayOptions) {
|
|
170
172
|
throw new Error("Failed to create loader: Must provide RawArrayOptions for RawArrayLoader");
|