@aics/vole-core 4.3.1 → 4.4.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/BaseDrawableMeshObject.js +72 -0
- package/es/Channel.js +2 -2
- package/es/Histogram.js +11 -15
- package/es/Line3d.js +5 -49
- package/es/MeshVolume.js +2 -1
- package/es/VectorArrows3d.js +324 -0
- package/es/View3d.js +35 -10
- package/es/VolumeDrawable.js +20 -20
- package/es/index.js +2 -1
- package/es/loaders/OmeZarrLoader.js +3 -14
- package/es/loaders/TiffLoader.js +7 -5
- package/es/loaders/zarr_utils/utils.js +2 -1
- package/es/types/BaseDrawableMeshObject.d.ts +35 -0
- package/es/types/Histogram.d.ts +1 -1
- package/es/types/ImageInfo.d.ts +1 -1
- package/es/types/Line3d.d.ts +3 -17
- package/es/types/MeshVolume.d.ts +1 -1
- package/es/types/VectorArrows3d.d.ts +92 -0
- package/es/types/View3d.d.ts +21 -2
- package/es/types/Volume.d.ts +1 -1
- package/es/types/VolumeDrawable.d.ts +11 -13
- package/es/types/index.d.ts +2 -1
- package/es/types/loaders/TiffLoader.d.ts +1 -0
- package/es/types/types.d.ts +4 -0
- package/es/utils/num_utils.js +6 -2
- package/es/workers/FetchTiffWorker.js +2 -11
- package/package.json +2 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Group, Mesh, Vector3 } from "three";
|
|
2
|
+
/**
|
|
3
|
+
* Abstract base class for drawable 3D mesh objects.
|
|
4
|
+
*
|
|
5
|
+
* Provides default implementations for some methods in the IDrawableObject
|
|
6
|
+
* interface, including handling for visibility, transformations, and cleanup.
|
|
7
|
+
*
|
|
8
|
+
* As a default, subclasses should call `this.addChildMesh(mesh)` to register
|
|
9
|
+
* any meshes that should be managed by the base class.
|
|
10
|
+
*/
|
|
11
|
+
export default class BaseDrawableMeshObject {
|
|
12
|
+
/**
|
|
13
|
+
* Pivot group that all child meshes are parented to. Transformations are
|
|
14
|
+
* applied to this group.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
this.meshPivot = new Group();
|
|
19
|
+
this.scale = new Vector3(1, 1, 1);
|
|
20
|
+
this.flipAxes = new Vector3(1, 1, 1);
|
|
21
|
+
}
|
|
22
|
+
addChildMesh(mesh) {
|
|
23
|
+
this.meshPivot.add(mesh);
|
|
24
|
+
}
|
|
25
|
+
removeChildMesh(mesh) {
|
|
26
|
+
this.meshPivot.remove(mesh);
|
|
27
|
+
}
|
|
28
|
+
cleanup() {
|
|
29
|
+
this.meshPivot.traverse(obj => {
|
|
30
|
+
if (obj instanceof Mesh) {
|
|
31
|
+
obj.geometry.dispose();
|
|
32
|
+
obj.material.dispose();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
this.meshPivot.clear();
|
|
36
|
+
}
|
|
37
|
+
doRender() {
|
|
38
|
+
// no op
|
|
39
|
+
}
|
|
40
|
+
setVisible(visible) {
|
|
41
|
+
this.meshPivot.visible = visible;
|
|
42
|
+
}
|
|
43
|
+
get3dObject() {
|
|
44
|
+
return this.meshPivot;
|
|
45
|
+
}
|
|
46
|
+
setTranslation(translation) {
|
|
47
|
+
this.meshPivot.position.copy(translation);
|
|
48
|
+
}
|
|
49
|
+
setScale(scale) {
|
|
50
|
+
this.scale.copy(scale);
|
|
51
|
+
this.meshPivot.scale.copy(scale).multiply(this.flipAxes);
|
|
52
|
+
}
|
|
53
|
+
setRotation(eulerXYZ) {
|
|
54
|
+
this.meshPivot.rotation.copy(eulerXYZ);
|
|
55
|
+
}
|
|
56
|
+
setFlipAxes(flipX, flipY, flipZ) {
|
|
57
|
+
this.flipAxes.set(flipX, flipY, flipZ);
|
|
58
|
+
this.meshPivot.scale.copy(this.scale).multiply(this.flipAxes);
|
|
59
|
+
}
|
|
60
|
+
setOrthoThickness(_thickness) {
|
|
61
|
+
// no op
|
|
62
|
+
}
|
|
63
|
+
setResolution(_x, _y) {
|
|
64
|
+
// no op
|
|
65
|
+
}
|
|
66
|
+
setAxisClip(_axis, _minval, _maxval, _isOrthoAxis) {
|
|
67
|
+
// no op
|
|
68
|
+
}
|
|
69
|
+
updateClipRegion(_xmin, _xmax, _ymin, _ymax, _zmin, _zmax) {
|
|
70
|
+
// no op
|
|
71
|
+
}
|
|
72
|
+
}
|
package/es/Channel.js
CHANGED
|
@@ -157,7 +157,7 @@ export default class Channel {
|
|
|
157
157
|
};
|
|
158
158
|
this.rebuildDataTexture(this.imgData.data, w, h);
|
|
159
159
|
this.loaded = true;
|
|
160
|
-
this.histogram = new Histogram(bitsArray);
|
|
160
|
+
this.histogram = new Histogram(bitsArray, rawMin, rawMax);
|
|
161
161
|
this.frame = frame;
|
|
162
162
|
|
|
163
163
|
// reuse old lut but auto-remap it to new data range
|
|
@@ -205,7 +205,7 @@ export default class Channel {
|
|
|
205
205
|
this.loaded = true;
|
|
206
206
|
// update from current histogram?
|
|
207
207
|
this.setRawDataRange(rawMin, rawMax);
|
|
208
|
-
this.histogram = new Histogram(this.volumeData);
|
|
208
|
+
this.histogram = new Histogram(this.volumeData, rawMin, rawMax);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
// given this.volumeData, let's unpack it into a flat textureatlas and fill up this.imgData.
|
package/es/Histogram.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isFloatTypeArray } from "./types.js";
|
|
2
|
+
import { getDataRange } from "./utils/num_utils.js";
|
|
2
3
|
const NBINS = 256;
|
|
3
4
|
/**
|
|
4
5
|
* Builds a histogram with 256 bins from a data array. Assume data is 8 bit single channel grayscale.
|
|
@@ -18,7 +19,7 @@ export default class Histogram {
|
|
|
18
19
|
|
|
19
20
|
/** Index of the last bin (other than 0) with at least 1 value. */
|
|
20
21
|
|
|
21
|
-
constructor(data) {
|
|
22
|
+
constructor(data, dataMin = undefined, dataMax = undefined) {
|
|
22
23
|
this.dataMinBin = 0;
|
|
23
24
|
this.dataMaxBin = 0;
|
|
24
25
|
this.maxBin = 0;
|
|
@@ -28,7 +29,7 @@ export default class Histogram {
|
|
|
28
29
|
this.binSize = 0;
|
|
29
30
|
|
|
30
31
|
// build up the histogram
|
|
31
|
-
const hinfo = Histogram.calculateHistogram(data, NBINS);
|
|
32
|
+
const hinfo = Histogram.calculateHistogram(data, NBINS, dataMin, dataMax);
|
|
32
33
|
this.bins = hinfo.bins;
|
|
33
34
|
this.min = hinfo.min;
|
|
34
35
|
this.max = hinfo.max;
|
|
@@ -233,24 +234,19 @@ export default class Histogram {
|
|
|
233
234
|
}
|
|
234
235
|
return [b, e];
|
|
235
236
|
}
|
|
236
|
-
static calculateHistogram(arr, numBins = 1) {
|
|
237
|
+
static calculateHistogram(arr, numBins = 1, dataMin = undefined, dataMax = undefined) {
|
|
237
238
|
if (numBins < 1) {
|
|
238
239
|
numBins = 1;
|
|
239
240
|
}
|
|
240
241
|
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
// need to be careful about computing over chunks or whole ready-to-display volume
|
|
242
|
+
// ASSUMPTION: we will trust the min and max if provided.
|
|
243
|
+
let min = dataMin !== undefined ? dataMin : arr[0];
|
|
244
|
+
let max = dataMax !== undefined ? dataMax : arr[0];
|
|
245
245
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
min = arr[i];
|
|
251
|
-
} else if (arr[i] > max) {
|
|
252
|
-
max = arr[i];
|
|
253
|
-
}
|
|
246
|
+
// Find min and max in the array if the user did not provide them.
|
|
247
|
+
// Note that this is a completely separate walk through the data array which could be expensive.
|
|
248
|
+
if (dataMin === undefined || dataMax === undefined) {
|
|
249
|
+
[min, max] = getDataRange(arr);
|
|
254
250
|
}
|
|
255
251
|
const bins = new Uint32Array(numBins).fill(0);
|
|
256
252
|
|
package/es/Line3d.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { Group, Vector3 } from "three";
|
|
2
1
|
import { LineMaterial } from "three/addons/lines/LineMaterial.js";
|
|
3
2
|
import { LineSegments2 } from "three/addons/lines/LineSegments2.js";
|
|
4
3
|
import { LineSegmentsGeometry } from "three/addons/lines/LineSegmentsGeometry.js";
|
|
5
4
|
import { MESH_NO_PICK_OCCLUSION_LAYER, OVERLAY_LAYER } from "./ThreeJsPanel.js";
|
|
5
|
+
import BaseDrawableMeshObject from "./BaseDrawableMeshObject.js";
|
|
6
6
|
const DEFAULT_VERTEX_BUFFER_SIZE = 1020;
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Simple wrapper for a 3D line segments object, with controls for vertex data,
|
|
10
10
|
* color, width, and segments visible.
|
|
11
11
|
*/
|
|
12
|
-
export default class Line3d {
|
|
12
|
+
export default class Line3d extends BaseDrawableMeshObject {
|
|
13
13
|
constructor() {
|
|
14
|
+
super();
|
|
14
15
|
this.bufferSize = DEFAULT_VERTEX_BUFFER_SIZE;
|
|
15
16
|
const geometry = new LineSegmentsGeometry();
|
|
16
17
|
geometry.setPositions(new Float32Array(this.bufferSize));
|
|
@@ -27,55 +28,10 @@ export default class Line3d {
|
|
|
27
28
|
// artifacts can occur where contours are drawn around lines. This layer
|
|
28
29
|
// (MESH_NO_PICK_OCCLUSION_LAYER) does not occlude/interact with the pick
|
|
29
30
|
// buffer but still writes depth information for the volume.
|
|
31
|
+
this.meshPivot.layers.set(MESH_NO_PICK_OCCLUSION_LAYER);
|
|
30
32
|
this.lineMesh.layers.set(MESH_NO_PICK_OCCLUSION_LAYER);
|
|
31
33
|
this.lineMesh.frustumCulled = false;
|
|
32
|
-
this.
|
|
33
|
-
this.meshPivot.add(this.lineMesh);
|
|
34
|
-
this.meshPivot.layers.set(MESH_NO_PICK_OCCLUSION_LAYER);
|
|
35
|
-
this.scale = new Vector3(1, 1, 1);
|
|
36
|
-
this.flipAxes = new Vector3(1, 1, 1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// IDrawableObject interface methods
|
|
40
|
-
|
|
41
|
-
cleanup() {
|
|
42
|
-
this.lineMesh.geometry.dispose();
|
|
43
|
-
this.lineMesh.material.dispose();
|
|
44
|
-
}
|
|
45
|
-
setVisible(visible) {
|
|
46
|
-
this.lineMesh.visible = visible;
|
|
47
|
-
}
|
|
48
|
-
doRender() {
|
|
49
|
-
// no op
|
|
50
|
-
}
|
|
51
|
-
get3dObject() {
|
|
52
|
-
return this.meshPivot;
|
|
53
|
-
}
|
|
54
|
-
setTranslation(translation) {
|
|
55
|
-
this.meshPivot.position.copy(translation);
|
|
56
|
-
}
|
|
57
|
-
setScale(scale) {
|
|
58
|
-
this.scale.copy(scale);
|
|
59
|
-
this.meshPivot.scale.copy(scale).multiply(this.flipAxes);
|
|
60
|
-
}
|
|
61
|
-
setRotation(eulerXYZ) {
|
|
62
|
-
this.meshPivot.rotation.copy(eulerXYZ);
|
|
63
|
-
}
|
|
64
|
-
setFlipAxes(flipX, flipY, flipZ) {
|
|
65
|
-
this.flipAxes.set(flipX, flipY, flipZ);
|
|
66
|
-
this.meshPivot.scale.copy(this.scale).multiply(this.flipAxes);
|
|
67
|
-
}
|
|
68
|
-
setOrthoThickness(_thickness) {
|
|
69
|
-
// no op
|
|
70
|
-
}
|
|
71
|
-
setResolution(_x, _y) {
|
|
72
|
-
// no op
|
|
73
|
-
}
|
|
74
|
-
setAxisClip(_axis, _minval, _maxval, _isOrthoAxis) {
|
|
75
|
-
// no op
|
|
76
|
-
}
|
|
77
|
-
updateClipRegion(_xmin, _xmax, _ymin, _ymax, _zmin, _zmax) {
|
|
78
|
-
// no op
|
|
34
|
+
this.addChildMesh(this.lineMesh);
|
|
79
35
|
}
|
|
80
36
|
|
|
81
37
|
// Line-specific functions
|
package/es/MeshVolume.js
CHANGED
|
@@ -250,11 +250,12 @@ export default class MeshVolume {
|
|
|
250
250
|
this.meshrep[channel] = null;
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
|
-
saveChannelIsosurface(channelIndex, type,
|
|
253
|
+
saveChannelIsosurface(channelIndex, type, name) {
|
|
254
254
|
const meshrep = this.meshrep[channelIndex];
|
|
255
255
|
if (!meshrep) {
|
|
256
256
|
return;
|
|
257
257
|
}
|
|
258
|
+
const namePrefix = name !== undefined ? `${name}_` : "";
|
|
258
259
|
if (type === "STL") {
|
|
259
260
|
this.exportSTL(meshrep, namePrefix + "_" + this.volume.channelNames[channelIndex]);
|
|
260
261
|
} else if (type === "GLTF") {
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import BaseDrawableMeshObject from "./BaseDrawableMeshObject.js";
|
|
2
|
+
import { MESH_NO_PICK_OCCLUSION_LAYER } from "./ThreeJsPanel.js";
|
|
3
|
+
import { InstancedMesh, CylinderGeometry, ConeGeometry, Object3D, Vector3, MeshBasicMaterial, Color, DynamicDrawUsage, Matrix4 } from "three";
|
|
4
|
+
// Unscaled arrowhead dimensions.
|
|
5
|
+
const SHAFT_BASE_RADIUS = 0.5;
|
|
6
|
+
const HEAD_BASE_RADIUS = 1.5;
|
|
7
|
+
const HEAD_BASE_HEIGHT = 4;
|
|
8
|
+
|
|
9
|
+
/** Default arrow shaft thickness, in world units. */
|
|
10
|
+
const DEFAULT_DIAMETER = 0.002;
|
|
11
|
+
const DEFAULT_INSTANCE_COUNT = 256;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A drawable vector arrow field, which uses instanced meshes for performance.
|
|
15
|
+
*/
|
|
16
|
+
export default class VectorArrows3d extends BaseDrawableMeshObject {
|
|
17
|
+
/**
|
|
18
|
+
* Scale of this object in world coordinates, when unscaled. Used to
|
|
19
|
+
* compensate for parent transforms in order to keep arrow meshes from being
|
|
20
|
+
* distorted.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// Temporary calculation objects. Optimization taken from three.js examples.
|
|
24
|
+
|
|
25
|
+
constructor() {
|
|
26
|
+
super();
|
|
27
|
+
this.worldScale = new Vector3(1, 1, 1);
|
|
28
|
+
this.meshPivot.layers.set(MESH_NO_PICK_OCCLUSION_LAYER);
|
|
29
|
+
this.maxInstanceCount = DEFAULT_INSTANCE_COUNT;
|
|
30
|
+
const {
|
|
31
|
+
headMesh: headMesh,
|
|
32
|
+
shaftMesh: shaftMesh
|
|
33
|
+
} = this.initInstancedMeshes(DEFAULT_INSTANCE_COUNT);
|
|
34
|
+
this.headInstancedMesh = headMesh;
|
|
35
|
+
this.shaftInstancedMesh = shaftMesh;
|
|
36
|
+
this.headInstancedMesh.count = 0;
|
|
37
|
+
this.shaftInstancedMesh.count = 0;
|
|
38
|
+
this.positions = null;
|
|
39
|
+
this.deltas = null;
|
|
40
|
+
this.colors = null;
|
|
41
|
+
this.diameter = new Float32Array([DEFAULT_DIAMETER]);
|
|
42
|
+
this.tempDst = new Vector3();
|
|
43
|
+
this.tempScale = new Vector3();
|
|
44
|
+
this.tempMatrix = new Object3D();
|
|
45
|
+
this.onParentTransformUpdated();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns (unscaled) buffer geometry for the head and shaft parts of the
|
|
50
|
+
* arrow.
|
|
51
|
+
* @returns
|
|
52
|
+
* - `head`: BufferGeometry for the arrowhead, a cone pointing along the +Z
|
|
53
|
+
* axis, with the pivot at the tip of the cone. Height and radius are based
|
|
54
|
+
* on constant values (`HEAD_BASE_HEIGHT` and `HEAD_BASE_RADIUS`).
|
|
55
|
+
* - `shaft`: BufferGeometry for the cylindrical arrow shaft. The cylinder
|
|
56
|
+
* points along the +Z axis, with the pivot at the base of the cylinder.
|
|
57
|
+
* Height is 1 and diameter is 1.
|
|
58
|
+
*
|
|
59
|
+
* ```txt
|
|
60
|
+
* ^ +Z axis ^
|
|
61
|
+
* _____ x
|
|
62
|
+
* | | / \
|
|
63
|
+
* | | / \
|
|
64
|
+
* --x-- /-----\
|
|
65
|
+
*
|
|
66
|
+
* x = pivot (0,0,0)
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
static generateGeometry() {
|
|
70
|
+
// TODO: Currently the shape of the arrow head is fixed. Allow configuring
|
|
71
|
+
// this in the future?
|
|
72
|
+
const cylinderGeometry = new CylinderGeometry(SHAFT_BASE_RADIUS, SHAFT_BASE_RADIUS, 2 * SHAFT_BASE_RADIUS,
|
|
73
|
+
// height
|
|
74
|
+
8,
|
|
75
|
+
// radial segments
|
|
76
|
+
1,
|
|
77
|
+
// height segments
|
|
78
|
+
false // capped ends
|
|
79
|
+
);
|
|
80
|
+
const coneRadius = HEAD_BASE_RADIUS;
|
|
81
|
+
const coneHeight = HEAD_BASE_HEIGHT;
|
|
82
|
+
const coneGeometry = new ConeGeometry(coneRadius, coneHeight, 12);
|
|
83
|
+
|
|
84
|
+
// Rotate both to point along +Z axis
|
|
85
|
+
const rotateToPositiveZ = new Matrix4().makeRotationX(Math.PI / 2);
|
|
86
|
+
// Change cone pivot to be at the tip.
|
|
87
|
+
const coneTranslation = new Matrix4().makeTranslation(0, 0, -coneHeight / 2);
|
|
88
|
+
coneGeometry.applyMatrix4(coneTranslation.multiply(rotateToPositiveZ));
|
|
89
|
+
// Change cylinder pivot to be at the base.
|
|
90
|
+
const cylinderTranslation = new Matrix4().makeTranslation(0, 0, 0.5);
|
|
91
|
+
cylinderGeometry.applyMatrix4(cylinderTranslation.multiply(rotateToPositiveZ));
|
|
92
|
+
return {
|
|
93
|
+
head: coneGeometry,
|
|
94
|
+
shaft: cylinderGeometry
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create new instanced meshes with the specified instance count, and adds
|
|
100
|
+
* them to the mesh pivot and internal meshes array for future cleanup.
|
|
101
|
+
*
|
|
102
|
+
* If calling outside of the constructor, be sure to call `cleanup()` first.
|
|
103
|
+
*/
|
|
104
|
+
initInstancedMeshes(instanceCount) {
|
|
105
|
+
this.cleanup();
|
|
106
|
+
this.meshPivot.clear();
|
|
107
|
+
const basicMaterial = new MeshBasicMaterial({
|
|
108
|
+
color: "#fff"
|
|
109
|
+
});
|
|
110
|
+
const {
|
|
111
|
+
head: headGeometry,
|
|
112
|
+
shaft: shaftGeometry
|
|
113
|
+
} = VectorArrows3d.generateGeometry();
|
|
114
|
+
const headMesh = new InstancedMesh(headGeometry, basicMaterial, instanceCount);
|
|
115
|
+
const shaftMesh = new InstancedMesh(shaftGeometry, basicMaterial, instanceCount);
|
|
116
|
+
headMesh.layers.set(MESH_NO_PICK_OCCLUSION_LAYER);
|
|
117
|
+
shaftMesh.layers.set(MESH_NO_PICK_OCCLUSION_LAYER);
|
|
118
|
+
headMesh.frustumCulled = false;
|
|
119
|
+
shaftMesh.frustumCulled = false;
|
|
120
|
+
headMesh.instanceMatrix.setUsage(DynamicDrawUsage);
|
|
121
|
+
shaftMesh.instanceMatrix.setUsage(DynamicDrawUsage);
|
|
122
|
+
this.addChildMesh(headMesh);
|
|
123
|
+
this.addChildMesh(shaftMesh);
|
|
124
|
+
return {
|
|
125
|
+
headMesh,
|
|
126
|
+
shaftMesh
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
increaseInstanceCountMax(instanceCount) {
|
|
130
|
+
// Max instance count is set when instanced meshes are created. If we need
|
|
131
|
+
// to increase the max, we need to recreate the instanced meshes.
|
|
132
|
+
let newInstanceCount = this.maxInstanceCount;
|
|
133
|
+
while (newInstanceCount < instanceCount) {
|
|
134
|
+
newInstanceCount *= 2;
|
|
135
|
+
}
|
|
136
|
+
// Delete existing meshes
|
|
137
|
+
this.cleanup();
|
|
138
|
+
const {
|
|
139
|
+
headMesh,
|
|
140
|
+
shaftMesh
|
|
141
|
+
} = this.initInstancedMeshes(newInstanceCount);
|
|
142
|
+
this.headInstancedMesh = headMesh;
|
|
143
|
+
this.shaftInstancedMesh = shaftMesh;
|
|
144
|
+
this.maxInstanceCount = newInstanceCount;
|
|
145
|
+
}
|
|
146
|
+
setScale(scale) {
|
|
147
|
+
if (scale !== this.scale) {
|
|
148
|
+
this.onParentTransformUpdated();
|
|
149
|
+
this.scale.copy(scale);
|
|
150
|
+
if (this.positions && this.deltas) {
|
|
151
|
+
// Update arrows
|
|
152
|
+
this.setArrowData(this.positions, this.deltas);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Called when scaling of parent transforms has been updated or whenever
|
|
159
|
+
* vector data is updated.
|
|
160
|
+
*/
|
|
161
|
+
onParentTransformUpdated() {
|
|
162
|
+
// Measure world scale by temporarily resetting mesh pivot scale
|
|
163
|
+
this.meshPivot.scale.set(1, 1, 1);
|
|
164
|
+
let newWorldScale = new Vector3();
|
|
165
|
+
newWorldScale = this.meshPivot.getWorldScale(newWorldScale);
|
|
166
|
+
|
|
167
|
+
// Scale is inverted on mesh pivot to cancel out parent transforms (though
|
|
168
|
+
// translation and rotation are still affected by any parent transforms).
|
|
169
|
+
// This allows arrows meshes to be scaled 1:1 with world space, regardless
|
|
170
|
+
// of parent transforms, and prevents distortion or skewing of the mesh.
|
|
171
|
+
// Parent scaling is applied to arrow positions and deltas (see
|
|
172
|
+
// `updateAllArrowTransforms`), rather than the meshes themselves.
|
|
173
|
+
const invertScale = new Vector3(1, 1, 1).divide(newWorldScale);
|
|
174
|
+
this.meshPivot.scale.copy(invertScale);
|
|
175
|
+
if (!newWorldScale.equals(this.worldScale)) {
|
|
176
|
+
this.worldScale.copy(newWorldScale);
|
|
177
|
+
if (this.positions && this.deltas) {
|
|
178
|
+
this.setArrowData(this.positions, this.deltas);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
updateSingleArrowTransform(index, src, delta, diameter) {
|
|
183
|
+
// Update the arrow shaft
|
|
184
|
+
const headHeight = HEAD_BASE_HEIGHT * diameter;
|
|
185
|
+
const length = delta.length();
|
|
186
|
+
const shaftHeight = Math.max(length - headHeight, 0);
|
|
187
|
+
if (shaftHeight < 1e-6) {
|
|
188
|
+
// If the shaft height is too small, scale to 0.
|
|
189
|
+
this.tempScale.set(0, 0, 0);
|
|
190
|
+
} else {
|
|
191
|
+
this.tempScale.set(diameter, diameter, shaftHeight);
|
|
192
|
+
}
|
|
193
|
+
this.tempMatrix.scale.copy(this.tempScale);
|
|
194
|
+
this.tempMatrix.position.copy(src);
|
|
195
|
+
this.tempDst.copy(src).add(delta);
|
|
196
|
+
this.tempMatrix.lookAt(this.tempDst);
|
|
197
|
+
this.tempMatrix.updateMatrix();
|
|
198
|
+
this.shaftInstancedMesh.setMatrixAt(index, this.tempMatrix.matrix);
|
|
199
|
+
if (length < headHeight) {
|
|
200
|
+
// If head is longer than the total length, shrink the head to match
|
|
201
|
+
// length. TODO: Is it okay to do this automatically?
|
|
202
|
+
const newDiameter = length / HEAD_BASE_HEIGHT;
|
|
203
|
+
this.tempScale.set(newDiameter, newDiameter, newDiameter);
|
|
204
|
+
} else {
|
|
205
|
+
this.tempScale.set(diameter, diameter, diameter);
|
|
206
|
+
}
|
|
207
|
+
this.tempMatrix.scale.copy(this.tempScale);
|
|
208
|
+
this.tempMatrix.position.copy(this.tempDst);
|
|
209
|
+
this.tempDst.add(delta);
|
|
210
|
+
this.tempMatrix.lookAt(this.tempDst);
|
|
211
|
+
this.tempMatrix.updateMatrix();
|
|
212
|
+
this.headInstancedMesh.setMatrixAt(index, this.tempMatrix.matrix);
|
|
213
|
+
}
|
|
214
|
+
updateAllArrowTransforms() {
|
|
215
|
+
if (!this.positions || !this.deltas) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const count = this.positions.length / 3;
|
|
219
|
+
const combinedScale = new Vector3().copy(this.scale).multiply(this.flipAxes).multiply(this.worldScale);
|
|
220
|
+
const tempSrc = new Vector3();
|
|
221
|
+
const tempDelta = new Vector3();
|
|
222
|
+
let tempDiameter;
|
|
223
|
+
for (let i = 0; i < count; i++) {
|
|
224
|
+
// Points and deltas scaled to volume space.
|
|
225
|
+
tempSrc.fromArray(this.positions, i * 3).multiply(combinedScale);
|
|
226
|
+
tempDelta.fromArray(this.deltas, i * 3).multiply(combinedScale);
|
|
227
|
+
tempDiameter = this.diameter[i % this.diameter.length] ?? DEFAULT_DIAMETER;
|
|
228
|
+
this.updateSingleArrowTransform(i, tempSrc, tempDelta, tempDiameter);
|
|
229
|
+
}
|
|
230
|
+
this.headInstancedMesh.instanceMatrix.needsUpdate = true;
|
|
231
|
+
this.shaftInstancedMesh.instanceMatrix.needsUpdate = true;
|
|
232
|
+
}
|
|
233
|
+
applyColors() {
|
|
234
|
+
if (!this.colors) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const colorCount = Math.round(this.colors.length / 3);
|
|
238
|
+
const color = new Color();
|
|
239
|
+
for (let i = 0; i < this.headInstancedMesh.count; i++) {
|
|
240
|
+
// Wrap colors if there are fewer colors than arrows
|
|
241
|
+
const colorIndex = i % colorCount;
|
|
242
|
+
color.fromArray(this.colors, colorIndex * 3);
|
|
243
|
+
this.headInstancedMesh.setColorAt(i, color);
|
|
244
|
+
this.shaftInstancedMesh.setColorAt(i, color);
|
|
245
|
+
}
|
|
246
|
+
if (this.headInstancedMesh.instanceColor) {
|
|
247
|
+
this.headInstancedMesh.instanceColor.needsUpdate = true;
|
|
248
|
+
}
|
|
249
|
+
if (this.shaftInstancedMesh.instanceColor) {
|
|
250
|
+
this.shaftInstancedMesh.instanceColor.needsUpdate = true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Sets the colors for the arrows as either a single Color or an array of RGB values.
|
|
256
|
+
* If there are more arrows than colors, colors will be repeated in order.
|
|
257
|
+
* @param colors Color object or numeric array of RGB values in the [0, 1] range.
|
|
258
|
+
* @throws {Error} If colors array length is not a multiple of 3.
|
|
259
|
+
*/
|
|
260
|
+
setColors(colors) {
|
|
261
|
+
if (colors instanceof Color) {
|
|
262
|
+
this.colors = new Float32Array(3);
|
|
263
|
+
colors.toArray(this.colors);
|
|
264
|
+
} else {
|
|
265
|
+
if (colors.length % 3 !== 0) {
|
|
266
|
+
throw new Error("VectorArrows.setColors: colors array length must be a multiple of 3.");
|
|
267
|
+
}
|
|
268
|
+
this.colors = new Float32Array(colors);
|
|
269
|
+
}
|
|
270
|
+
this.applyColors();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Sets all arrows to a uniform diameter (default is `0.002`). To set
|
|
275
|
+
* per-arrow diameter, pass an array of values into `setArrowData` instead.
|
|
276
|
+
* @param diameter Diameter value to set for all arrows.
|
|
277
|
+
*/
|
|
278
|
+
setDiameter(diameter) {
|
|
279
|
+
this.diameter = new Float32Array([diameter]);
|
|
280
|
+
this.updateAllArrowTransforms();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Sets the per-arrow data. The number of rendered arrows is equal to
|
|
285
|
+
* `positions.length / 3`.
|
|
286
|
+
* @param positions Float32Array, where every three values is the XYZ position
|
|
287
|
+
* of the base of an arrow.
|
|
288
|
+
* @param deltas Float32Array, where every three values is the XYZ delta
|
|
289
|
+
* vector for each arrow.
|
|
290
|
+
* @param diameters Optional Float32Array of diameter thickness values for
|
|
291
|
+
* each arrow's shaft. If provided, overrides a single diameter value set by
|
|
292
|
+
* `setDiameter`. If fewer diameter values are provided than arrows, the
|
|
293
|
+
* values will be repeated in order.
|
|
294
|
+
* @throws {Error} If positions and deltas arrays have different lengths or if
|
|
295
|
+
* their length is not a multiple of 3.
|
|
296
|
+
*/
|
|
297
|
+
setArrowData(positions, deltas, diameters) {
|
|
298
|
+
if (positions.length !== deltas.length) {
|
|
299
|
+
throw new Error("VectorArrows.setArrowData: positions and deltas arrays must have the same length");
|
|
300
|
+
}
|
|
301
|
+
if (positions.length % 3 !== 0) {
|
|
302
|
+
throw new Error("VectorArrows.setArrowData: positions and deltas arrays length must be a multiple of 3");
|
|
303
|
+
}
|
|
304
|
+
this.positions = positions;
|
|
305
|
+
this.deltas = deltas;
|
|
306
|
+
if (diameters) {
|
|
307
|
+
this.diameter = diameters;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Update instance count, add more instances as needed.
|
|
311
|
+
const count = positions.length / 3;
|
|
312
|
+
const didInstanceCountIncrease = this.headInstancedMesh.count < count;
|
|
313
|
+
if (this.maxInstanceCount < count) {
|
|
314
|
+
this.increaseInstanceCountMax(count);
|
|
315
|
+
}
|
|
316
|
+
this.headInstancedMesh.count = count;
|
|
317
|
+
this.shaftInstancedMesh.count = count;
|
|
318
|
+
this.updateAllArrowTransforms();
|
|
319
|
+
if (didInstanceCountIncrease) {
|
|
320
|
+
// Apply colors to new arrows as needed
|
|
321
|
+
this.applyColors();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
package/es/View3d.js
CHANGED
|
@@ -852,29 +852,54 @@ export class View3d {
|
|
|
852
852
|
* be in the normalized coordinate space of the Volume, where the origin
|
|
853
853
|
* (0,0,0) is at the center of the Volume and the extent is from -0.5 to 0.5
|
|
854
854
|
* in each axis.
|
|
855
|
+
* @deprecated Will be removed in the next major release. Use `addDrawableObject` instead.
|
|
855
856
|
*/
|
|
856
857
|
addLineObject(line) {
|
|
857
|
-
|
|
858
|
-
this.image.addLineObject(line);
|
|
859
|
-
this.redraw();
|
|
860
|
-
}
|
|
858
|
+
return this.addDrawableObject(line);
|
|
861
859
|
}
|
|
862
860
|
|
|
863
|
-
/**
|
|
861
|
+
/**
|
|
862
|
+
* Returns whether a Line3d object exists as a child of the volume.
|
|
863
|
+
* @deprecated Will be removed in the next major release. Use `hasDrawableObject` instead.
|
|
864
|
+
*/
|
|
864
865
|
hasLineObject(line) {
|
|
865
|
-
|
|
866
|
-
return this.image.hasLineObject(line);
|
|
867
|
-
}
|
|
868
|
-
return false;
|
|
866
|
+
return this.hasDrawableObject(line);
|
|
869
867
|
}
|
|
870
868
|
|
|
871
869
|
/**
|
|
872
870
|
* Removes a Line3d object from the Volume, if it exists. Note that the
|
|
873
871
|
* object's resources are not freed automatically (e.g. via `line.cleanup()`).
|
|
872
|
+
* @deprecated Will be removed in the next major release. Use `removeDrawableObject` instead.
|
|
874
873
|
*/
|
|
875
874
|
removeLineObject(line) {
|
|
875
|
+
return this.removeDrawableObject(line);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Adds a drawable object as a child of the Volume, if it does not already
|
|
880
|
+
* exist. Objects will be in the normalized coordinate space of the Volume,
|
|
881
|
+
* where the origin (0,0,0) is at the center of the Volume and the extent is
|
|
882
|
+
* from -0.5 to 0.5 in each axis.
|
|
883
|
+
*/
|
|
884
|
+
addDrawableObject(object) {
|
|
885
|
+
if (this.image) {
|
|
886
|
+
this.image.addDrawableObject(object);
|
|
887
|
+
this.redraw();
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/** Returns whether a drawable object exists as a child of the volume. */
|
|
892
|
+
hasDrawableObject(object) {
|
|
893
|
+
return this.image ? this.image.hasDrawableObject(object) : false;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* Removes a drawable object from the Volume, if it exists. Note that the
|
|
898
|
+
* object's resources are not freed automatically (e.g. via `object.cleanup()`).
|
|
899
|
+
*/
|
|
900
|
+
removeDrawableObject(object) {
|
|
876
901
|
if (this.image) {
|
|
877
|
-
this.image.
|
|
902
|
+
this.image.removeDrawableObject(object);
|
|
878
903
|
this.redraw();
|
|
879
904
|
}
|
|
880
905
|
}
|
package/es/VolumeDrawable.js
CHANGED
|
@@ -240,6 +240,8 @@ export default class VolumeDrawable {
|
|
|
240
240
|
const scale = normPhysicalSize.clone().multiply(normRegionSize).multiply(this.settings.scale);
|
|
241
241
|
this.childObjectsGroup.scale.copy(scale);
|
|
242
242
|
this.childObjectsGroup.position.copy(this.volume.getContentCenter().multiply(this.settings.scale));
|
|
243
|
+
this.childObjects.forEach(obj => obj.onParentTransformUpdated?.());
|
|
244
|
+
|
|
243
245
|
// TODO only `RayMarchedAtlasVolume` handles scale properly. Get the others on board too!
|
|
244
246
|
this.volumeRendering.updateVolumeDimensions();
|
|
245
247
|
this.volumeRendering.updateSettings(this.settings, SettingsFlags.TRANSFORM);
|
|
@@ -773,33 +775,31 @@ export default class VolumeDrawable {
|
|
|
773
775
|
}
|
|
774
776
|
|
|
775
777
|
/**
|
|
776
|
-
* Adds a
|
|
777
|
-
* exist.
|
|
778
|
-
*
|
|
779
|
-
*
|
|
778
|
+
* Adds a drawable object as a child of the Volume, if it does not already
|
|
779
|
+
* exist. Objects will be in the normalized coordinate space of the Volume,
|
|
780
|
+
* where the origin (0,0,0) is at the center of the Volume and the extent is
|
|
781
|
+
* from -0.5 to 0.5 in each axis.
|
|
780
782
|
*/
|
|
781
|
-
|
|
782
|
-
if (!this.childObjects.has(
|
|
783
|
-
this.
|
|
784
|
-
this.
|
|
785
|
-
|
|
786
|
-
line.setFlipAxes(this.settings.flipAxes.x, this.settings.flipAxes.y, this.settings.flipAxes.z);
|
|
783
|
+
addDrawableObject(object) {
|
|
784
|
+
if (!this.childObjects.has(object)) {
|
|
785
|
+
this.childObjectsGroup.add(object.get3dObject());
|
|
786
|
+
this.childObjects.add(object);
|
|
787
|
+
this.updateScale();
|
|
787
788
|
}
|
|
788
789
|
}
|
|
789
790
|
|
|
790
|
-
/** Returns whether a
|
|
791
|
-
|
|
792
|
-
return this.childObjects.has(
|
|
791
|
+
/** Returns whether a drawable object exists as a child of the volume. */
|
|
792
|
+
hasDrawableObject(object) {
|
|
793
|
+
return this.childObjects.has(object);
|
|
793
794
|
}
|
|
794
795
|
|
|
795
|
-
/**
|
|
796
|
-
*
|
|
797
|
-
* object's resources are not freed automatically (e.g. via `line.cleanup()`).
|
|
796
|
+
/** Removes a drawable object from the Volume, if it exists. Note that the
|
|
797
|
+
* object's resources are not freed automatically (e.g. via `object.cleanup()`).
|
|
798
798
|
*/
|
|
799
|
-
|
|
800
|
-
if (this.childObjects.has(
|
|
801
|
-
this.childObjects.delete(
|
|
802
|
-
this.childObjectsGroup.remove(
|
|
799
|
+
removeDrawableObject(object) {
|
|
800
|
+
if (this.childObjects.has(object)) {
|
|
801
|
+
this.childObjects.delete(object);
|
|
802
|
+
this.childObjectsGroup.remove(object.get3dObject());
|
|
803
803
|
}
|
|
804
804
|
}
|
|
805
805
|
setupGui(pane) {
|
package/es/index.js
CHANGED
|
@@ -19,4 +19,5 @@ import VolumeLoaderContext from "./workers/VolumeLoaderContext.js";
|
|
|
19
19
|
import { VolumeLoadError, VolumeLoadErrorType } from "./loaders/VolumeLoadError.js";
|
|
20
20
|
import { Light, AREA_LIGHT, SKY_LIGHT } from "./Light.js";
|
|
21
21
|
import Line3d from "./Line3d.js";
|
|
22
|
-
|
|
22
|
+
import VectorArrows3d from "./VectorArrows3d.js";
|
|
23
|
+
export { Histogram, Lut, Line3d, VectorArrows3d, remapControlPoints, View3d, Volume, VolumeDrawable, LoadSpec, VolumeMaker, VolumeCache, RequestQueue, SubscribableRequestQueue, PrefetchDirection, OMEZarrLoader, JsonImageInfoLoader, RawArrayLoader, TiffLoader, VolumeLoaderContext, VolumeLoadError, VolumeLoadErrorType, VolumeFileFormat, createVolumeLoader, Channel, Light, ViewportCorner, AREA_LIGHT, RENDERMODE_PATHTRACE, RENDERMODE_RAYMARCH, SKY_LIGHT };
|
|
@@ -3,6 +3,7 @@ import * as zarr from "zarrita";
|
|
|
3
3
|
const {
|
|
4
4
|
slice
|
|
5
5
|
} = zarr;
|
|
6
|
+
import { getDataRange } from "../utils/num_utils.js";
|
|
6
7
|
import SubscribableRequestQueue from "../utils/SubscribableRequestQueue.js";
|
|
7
8
|
import { ThreadableVolumeLoader } from "./IVolumeLoader.js";
|
|
8
9
|
import { composeSubregion, computePackedAtlasDims, convertSubregionToPixels, pickLevelToLoad, unitNameToSymbol } from "./VolumeLoaderUtils.js";
|
|
@@ -16,18 +17,7 @@ const CHUNK_REQUEST_CANCEL_REASON = "chunk request cancelled";
|
|
|
16
17
|
// returns the converted data and the original min and max values
|
|
17
18
|
function convertChannel(channelData, dtype) {
|
|
18
19
|
// get min and max
|
|
19
|
-
|
|
20
|
-
let min = channelData[0];
|
|
21
|
-
let max = channelData[0];
|
|
22
|
-
for (let i = 0; i < channelData.length; i++) {
|
|
23
|
-
const val = channelData[i];
|
|
24
|
-
if (val < min) {
|
|
25
|
-
min = val;
|
|
26
|
-
}
|
|
27
|
-
if (val > max) {
|
|
28
|
-
max = val;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
20
|
+
const [min, max] = getDataRange(channelData);
|
|
31
21
|
if (dtype === "float64") {
|
|
32
22
|
// convert to float32
|
|
33
23
|
const f32 = new Float32Array(channelData.length);
|
|
@@ -315,7 +305,7 @@ class OMEZarrLoader extends ThreadableVolumeLoader {
|
|
|
315
305
|
return dims;
|
|
316
306
|
});
|
|
317
307
|
const imgdata = {
|
|
318
|
-
name: source0.omeroMetadata?.name
|
|
308
|
+
name: source0.omeroMetadata?.name,
|
|
319
309
|
atlasTileDims: [atlasTileDims.x, atlasTileDims.y],
|
|
320
310
|
subregionSize: [pxSizeLv.x, pxSizeLv.y, pxSizeLv.z],
|
|
321
311
|
subregionOffset: [0, 0, 0],
|
|
@@ -468,7 +458,6 @@ class OMEZarrLoader extends ThreadableVolumeLoader {
|
|
|
468
458
|
const level = this.sources[sourceIdx].scaleLevels[multiscaleLevel];
|
|
469
459
|
const sliceSpec = this.orderByDimension(unorderedSpec, sourceIdx);
|
|
470
460
|
const reportChunk = (coords, sub) => reportChunkBase(sourceIdx, coords, sub);
|
|
471
|
-
console.log(level);
|
|
472
461
|
const result = await zarr.get(level, sliceSpec, {
|
|
473
462
|
opts: {
|
|
474
463
|
subscriber,
|
package/es/loaders/TiffLoader.js
CHANGED
|
@@ -21,6 +21,7 @@ function getOME(xml) {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
class OMEDims {
|
|
24
|
+
name = undefined;
|
|
24
25
|
sizex = 0;
|
|
25
26
|
sizey = 0;
|
|
26
27
|
sizez = 1;
|
|
@@ -63,14 +64,15 @@ function getAttributeOrError(el, attr) {
|
|
|
63
64
|
function getOMEDims(imageEl) {
|
|
64
65
|
const dims = new OMEDims();
|
|
65
66
|
const pixelsEl = imageEl.getElementsByTagName("Pixels")[0];
|
|
67
|
+
dims.name = imageEl.getAttribute("Name") ?? "";
|
|
66
68
|
dims.sizex = Number(getAttributeOrError(pixelsEl, "SizeX"));
|
|
67
69
|
dims.sizey = Number(getAttributeOrError(pixelsEl, "SizeY"));
|
|
68
70
|
dims.sizez = Number(pixelsEl.getAttribute("SizeZ"));
|
|
69
71
|
dims.sizec = Number(pixelsEl.getAttribute("SizeC"));
|
|
70
72
|
dims.sizet = Number(pixelsEl.getAttribute("SizeT"));
|
|
71
|
-
dims.unit = pixelsEl.getAttribute("PhysicalSizeXUnit")
|
|
72
|
-
dims.pixeltype = pixelsEl.getAttribute("Type")
|
|
73
|
-
dims.dimensionorder = pixelsEl.getAttribute("DimensionOrder")
|
|
73
|
+
dims.unit = pixelsEl.getAttribute("PhysicalSizeXUnit") ?? "";
|
|
74
|
+
dims.pixeltype = pixelsEl.getAttribute("Type") ?? "";
|
|
75
|
+
dims.dimensionorder = pixelsEl.getAttribute("DimensionOrder") ?? "XYZCT";
|
|
74
76
|
dims.pixelsizex = Number(pixelsEl.getAttribute("PhysicalSizeX"));
|
|
75
77
|
dims.pixelsizey = Number(pixelsEl.getAttribute("PhysicalSizeY"));
|
|
76
78
|
dims.pixelsizez = Number(pixelsEl.getAttribute("PhysicalSizeZ"));
|
|
@@ -178,7 +180,7 @@ class TiffLoader extends ThreadableVolumeLoader {
|
|
|
178
180
|
// load tiff and check metadata
|
|
179
181
|
const numChannelsPerSource = this.url.length > 1 ? Array(this.url.length).fill(1) : [dims.sizec];
|
|
180
182
|
const imgdata = {
|
|
181
|
-
name:
|
|
183
|
+
name: dims.name,
|
|
182
184
|
atlasTileDims: [atlasDims.x, atlasDims.y],
|
|
183
185
|
subregionSize: [tilesizex, tilesizey, dims.sizez],
|
|
184
186
|
subregionOffset: [0, 0, 0],
|
|
@@ -188,7 +190,7 @@ class TiffLoader extends ThreadableVolumeLoader {
|
|
|
188
190
|
multiscaleLevelDims: [{
|
|
189
191
|
shape: [dims.sizet, dims.sizec, dims.sizez, tilesizey, tilesizex],
|
|
190
192
|
spacing: [1, 1, dims.pixelsizez, dims.pixelsizey * dims.sizey / tilesizey, dims.pixelsizex * dims.sizex / tilesizex],
|
|
191
|
-
spaceUnit: dims.unit
|
|
193
|
+
spaceUnit: dims.unit ?? "",
|
|
192
194
|
timeUnit: "",
|
|
193
195
|
dataType: getDtype(dims.pixeltype)
|
|
194
196
|
}],
|
|
@@ -78,6 +78,7 @@ export function remapAxesToTCZYX(axes) {
|
|
|
78
78
|
export function orderByDimension(valsTCZYX, orderTCZYX) {
|
|
79
79
|
const specLen = getDimensionCount(orderTCZYX);
|
|
80
80
|
const result = Array(specLen);
|
|
81
|
+
let curIdx = 0;
|
|
81
82
|
orderTCZYX.forEach((val, idx) => {
|
|
82
83
|
if (val >= 0) {
|
|
83
84
|
if (val >= specLen) {
|
|
@@ -85,7 +86,7 @@ export function orderByDimension(valsTCZYX, orderTCZYX) {
|
|
|
85
86
|
type: VolumeLoadErrorType.INVALID_METADATA
|
|
86
87
|
});
|
|
87
88
|
}
|
|
88
|
-
result[
|
|
89
|
+
result[curIdx++] = valsTCZYX[idx];
|
|
89
90
|
}
|
|
90
91
|
});
|
|
91
92
|
return result;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Euler, Group, Mesh, Vector3 } from "three";
|
|
2
|
+
import { IDrawableObject } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Abstract base class for drawable 3D mesh objects.
|
|
5
|
+
*
|
|
6
|
+
* Provides default implementations for some methods in the IDrawableObject
|
|
7
|
+
* interface, including handling for visibility, transformations, and cleanup.
|
|
8
|
+
*
|
|
9
|
+
* As a default, subclasses should call `this.addChildMesh(mesh)` to register
|
|
10
|
+
* any meshes that should be managed by the base class.
|
|
11
|
+
*/
|
|
12
|
+
export default abstract class BaseDrawableMeshObject implements IDrawableObject {
|
|
13
|
+
/**
|
|
14
|
+
* Pivot group that all child meshes are parented to. Transformations are
|
|
15
|
+
* applied to this group.
|
|
16
|
+
*/
|
|
17
|
+
protected readonly meshPivot: Group;
|
|
18
|
+
protected scale: Vector3;
|
|
19
|
+
protected flipAxes: Vector3;
|
|
20
|
+
constructor();
|
|
21
|
+
protected addChildMesh(mesh: Mesh): void;
|
|
22
|
+
protected removeChildMesh(mesh: Mesh): void;
|
|
23
|
+
cleanup(): void;
|
|
24
|
+
doRender(): void;
|
|
25
|
+
setVisible(visible: boolean): void;
|
|
26
|
+
get3dObject(): Group;
|
|
27
|
+
setTranslation(translation: Vector3): void;
|
|
28
|
+
setScale(scale: Vector3): void;
|
|
29
|
+
setRotation(eulerXYZ: Euler): void;
|
|
30
|
+
setFlipAxes(flipX: number, flipY: number, flipZ: number): void;
|
|
31
|
+
setOrthoThickness(_thickness: number): void;
|
|
32
|
+
setResolution(_x: number, _y: number): void;
|
|
33
|
+
setAxisClip(_axis: "x" | "y" | "z", _minval: number, _maxval: number, _isOrthoAxis: boolean): void;
|
|
34
|
+
updateClipRegion(_xmin: number, _xmax: number, _ymin: number, _ymax: number, _zmin: number, _zmax: number): void;
|
|
35
|
+
}
|
package/es/types/Histogram.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export default class Histogram {
|
|
|
18
18
|
private dataMaxBin;
|
|
19
19
|
private pixelCount;
|
|
20
20
|
maxBin: number;
|
|
21
|
-
constructor(data: TypedArray<NumberType
|
|
21
|
+
constructor(data: TypedArray<NumberType>, dataMin?: number | undefined, dataMax?: number | undefined);
|
|
22
22
|
private static findBin;
|
|
23
23
|
/**
|
|
24
24
|
* Returns the integer bin index for the given value. If a value is outside
|
package/es/types/ImageInfo.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type VolumeDims } from "./VolumeDims.js";
|
|
2
2
|
import { Vector3, Vector2 } from "three";
|
|
3
3
|
export type ImageInfo = Readonly<{
|
|
4
|
-
name: string;
|
|
4
|
+
name: string | undefined;
|
|
5
5
|
/**
|
|
6
6
|
* XY dimensions of the texture atlas used by `RayMarchedAtlasVolume` and
|
|
7
7
|
* `Atlas2DSlice`, in number of z-slice tiles (not pixels). Chosen by the
|
package/es/types/Line3d.d.ts
CHANGED
|
@@ -1,28 +1,14 @@
|
|
|
1
|
-
import { Color
|
|
1
|
+
import { Color } from "three";
|
|
2
2
|
import { IDrawableObject } from "./types.js";
|
|
3
|
+
import BaseDrawableMeshObject from "./BaseDrawableMeshObject.js";
|
|
3
4
|
/**
|
|
4
5
|
* Simple wrapper for a 3D line segments object, with controls for vertex data,
|
|
5
6
|
* color, width, and segments visible.
|
|
6
7
|
*/
|
|
7
|
-
export default class Line3d implements IDrawableObject {
|
|
8
|
-
private meshPivot;
|
|
9
|
-
private scale;
|
|
10
|
-
private flipAxes;
|
|
8
|
+
export default class Line3d extends BaseDrawableMeshObject implements IDrawableObject {
|
|
11
9
|
private lineMesh;
|
|
12
10
|
private bufferSize;
|
|
13
11
|
constructor();
|
|
14
|
-
cleanup(): void;
|
|
15
|
-
setVisible(visible: boolean): void;
|
|
16
|
-
doRender(): void;
|
|
17
|
-
get3dObject(): Group;
|
|
18
|
-
setTranslation(translation: Vector3): void;
|
|
19
|
-
setScale(scale: Vector3): void;
|
|
20
|
-
setRotation(eulerXYZ: Euler): void;
|
|
21
|
-
setFlipAxes(flipX: number, flipY: number, flipZ: number): void;
|
|
22
|
-
setOrthoThickness(_thickness: number): void;
|
|
23
|
-
setResolution(_x: number, _y: number): void;
|
|
24
|
-
setAxisClip(_axis: "x" | "y" | "z", _minval: number, _maxval: number, _isOrthoAxis: boolean): void;
|
|
25
|
-
updateClipRegion(_xmin: number, _xmax: number, _ymin: number, _ymax: number, _zmin: number, _zmax: number): void;
|
|
26
12
|
/**
|
|
27
13
|
* Sets the color of the line material.
|
|
28
14
|
* @param color Base line color.
|
package/es/types/MeshVolume.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export default class MeshVolume implements IDrawableObject {
|
|
|
34
34
|
hasIsosurface(channel: number): boolean;
|
|
35
35
|
createIsosurface(channel: number, color: [number, number, number], value?: number, alpha?: number, transp?: boolean): void;
|
|
36
36
|
destroyIsosurface(channel: number): void;
|
|
37
|
-
saveChannelIsosurface(channelIndex: number, type: string,
|
|
37
|
+
saveChannelIsosurface(channelIndex: number, type: string, name?: string): void;
|
|
38
38
|
exportSTL(input: Object3D, fname: string): void;
|
|
39
39
|
exportGLTF(input: Object3D, fname: string): void;
|
|
40
40
|
generateIsosurfaceGeometry(channelIndex: number, isovalue: number): BufferGeometry[];
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import BaseDrawableMeshObject from "./BaseDrawableMeshObject.js";
|
|
2
|
+
import { Vector3, Color } from "three";
|
|
3
|
+
import { IDrawableObject } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* A drawable vector arrow field, which uses instanced meshes for performance.
|
|
6
|
+
*/
|
|
7
|
+
export default class VectorArrows3d extends BaseDrawableMeshObject implements IDrawableObject {
|
|
8
|
+
/**
|
|
9
|
+
* Scale of this object in world coordinates, when unscaled. Used to
|
|
10
|
+
* compensate for parent transforms in order to keep arrow meshes from being
|
|
11
|
+
* distorted.
|
|
12
|
+
*/
|
|
13
|
+
protected worldScale: Vector3;
|
|
14
|
+
private headInstancedMesh;
|
|
15
|
+
private shaftInstancedMesh;
|
|
16
|
+
private maxInstanceCount;
|
|
17
|
+
private positions;
|
|
18
|
+
private deltas;
|
|
19
|
+
private colors;
|
|
20
|
+
private diameter;
|
|
21
|
+
private tempDst;
|
|
22
|
+
private tempScale;
|
|
23
|
+
private tempMatrix;
|
|
24
|
+
constructor();
|
|
25
|
+
/**
|
|
26
|
+
* Returns (unscaled) buffer geometry for the head and shaft parts of the
|
|
27
|
+
* arrow.
|
|
28
|
+
* @returns
|
|
29
|
+
* - `head`: BufferGeometry for the arrowhead, a cone pointing along the +Z
|
|
30
|
+
* axis, with the pivot at the tip of the cone. Height and radius are based
|
|
31
|
+
* on constant values (`HEAD_BASE_HEIGHT` and `HEAD_BASE_RADIUS`).
|
|
32
|
+
* - `shaft`: BufferGeometry for the cylindrical arrow shaft. The cylinder
|
|
33
|
+
* points along the +Z axis, with the pivot at the base of the cylinder.
|
|
34
|
+
* Height is 1 and diameter is 1.
|
|
35
|
+
*
|
|
36
|
+
* ```txt
|
|
37
|
+
* ^ +Z axis ^
|
|
38
|
+
* _____ x
|
|
39
|
+
* | | / \
|
|
40
|
+
* | | / \
|
|
41
|
+
* --x-- /-----\
|
|
42
|
+
*
|
|
43
|
+
* x = pivot (0,0,0)
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
private static generateGeometry;
|
|
47
|
+
/**
|
|
48
|
+
* Create new instanced meshes with the specified instance count, and adds
|
|
49
|
+
* them to the mesh pivot and internal meshes array for future cleanup.
|
|
50
|
+
*
|
|
51
|
+
* If calling outside of the constructor, be sure to call `cleanup()` first.
|
|
52
|
+
*/
|
|
53
|
+
private initInstancedMeshes;
|
|
54
|
+
private increaseInstanceCountMax;
|
|
55
|
+
setScale(scale: Vector3): void;
|
|
56
|
+
/**
|
|
57
|
+
* Called when scaling of parent transforms has been updated or whenever
|
|
58
|
+
* vector data is updated.
|
|
59
|
+
*/
|
|
60
|
+
onParentTransformUpdated(): void;
|
|
61
|
+
private updateSingleArrowTransform;
|
|
62
|
+
private updateAllArrowTransforms;
|
|
63
|
+
private applyColors;
|
|
64
|
+
/**
|
|
65
|
+
* Sets the colors for the arrows as either a single Color or an array of RGB values.
|
|
66
|
+
* If there are more arrows than colors, colors will be repeated in order.
|
|
67
|
+
* @param colors Color object or numeric array of RGB values in the [0, 1] range.
|
|
68
|
+
* @throws {Error} If colors array length is not a multiple of 3.
|
|
69
|
+
*/
|
|
70
|
+
setColors(colors: Float32Array | Color): void;
|
|
71
|
+
/**
|
|
72
|
+
* Sets all arrows to a uniform diameter (default is `0.002`). To set
|
|
73
|
+
* per-arrow diameter, pass an array of values into `setArrowData` instead.
|
|
74
|
+
* @param diameter Diameter value to set for all arrows.
|
|
75
|
+
*/
|
|
76
|
+
setDiameter(diameter: number): void;
|
|
77
|
+
/**
|
|
78
|
+
* Sets the per-arrow data. The number of rendered arrows is equal to
|
|
79
|
+
* `positions.length / 3`.
|
|
80
|
+
* @param positions Float32Array, where every three values is the XYZ position
|
|
81
|
+
* of the base of an arrow.
|
|
82
|
+
* @param deltas Float32Array, where every three values is the XYZ delta
|
|
83
|
+
* vector for each arrow.
|
|
84
|
+
* @param diameters Optional Float32Array of diameter thickness values for
|
|
85
|
+
* each arrow's shaft. If provided, overrides a single diameter value set by
|
|
86
|
+
* `setDiameter`. If fewer diameter values are provided than arrows, the
|
|
87
|
+
* values will be repeated in order.
|
|
88
|
+
* @throws {Error} If positions and deltas arrays have different lengths or if
|
|
89
|
+
* their length is not a multiple of 3.
|
|
90
|
+
*/
|
|
91
|
+
setArrowData(positions: Float32Array, deltas: Float32Array, diameters?: Float32Array): void;
|
|
92
|
+
}
|
package/es/types/View3d.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { CameraState } from "./ThreeJsPanel.js";
|
|
|
3
3
|
import VolumeDrawable from "./VolumeDrawable.js";
|
|
4
4
|
import { Light } from "./Light.js";
|
|
5
5
|
import Volume from "./Volume.js";
|
|
6
|
-
import { type ColorizeFeature, type VolumeChannelDisplayOptions, type VolumeDisplayOptions, ViewportCorner, RenderMode } from "./types.js";
|
|
6
|
+
import { type ColorizeFeature, type VolumeChannelDisplayOptions, type VolumeDisplayOptions, ViewportCorner, RenderMode, IDrawableObject } from "./types.js";
|
|
7
7
|
import { PerChannelCallback } from "./loaders/IVolumeLoader.js";
|
|
8
8
|
import Line3d from "./Line3d.js";
|
|
9
9
|
export declare const RENDERMODE_RAYMARCH = RenderMode.RAYMARCH;
|
|
@@ -389,15 +389,34 @@ export declare class View3d {
|
|
|
389
389
|
* be in the normalized coordinate space of the Volume, where the origin
|
|
390
390
|
* (0,0,0) is at the center of the Volume and the extent is from -0.5 to 0.5
|
|
391
391
|
* in each axis.
|
|
392
|
+
* @deprecated Will be removed in the next major release. Use `addDrawableObject` instead.
|
|
392
393
|
*/
|
|
393
394
|
addLineObject(line: Line3d): void;
|
|
394
|
-
/**
|
|
395
|
+
/**
|
|
396
|
+
* Returns whether a Line3d object exists as a child of the volume.
|
|
397
|
+
* @deprecated Will be removed in the next major release. Use `hasDrawableObject` instead.
|
|
398
|
+
*/
|
|
395
399
|
hasLineObject(line: Line3d): boolean;
|
|
396
400
|
/**
|
|
397
401
|
* Removes a Line3d object from the Volume, if it exists. Note that the
|
|
398
402
|
* object's resources are not freed automatically (e.g. via `line.cleanup()`).
|
|
403
|
+
* @deprecated Will be removed in the next major release. Use `removeDrawableObject` instead.
|
|
399
404
|
*/
|
|
400
405
|
removeLineObject(line: Line3d): void;
|
|
406
|
+
/**
|
|
407
|
+
* Adds a drawable object as a child of the Volume, if it does not already
|
|
408
|
+
* exist. Objects will be in the normalized coordinate space of the Volume,
|
|
409
|
+
* where the origin (0,0,0) is at the center of the Volume and the extent is
|
|
410
|
+
* from -0.5 to 0.5 in each axis.
|
|
411
|
+
*/
|
|
412
|
+
addDrawableObject(object: IDrawableObject): void;
|
|
413
|
+
/** Returns whether a drawable object exists as a child of the volume. */
|
|
414
|
+
hasDrawableObject(object: IDrawableObject): boolean;
|
|
415
|
+
/**
|
|
416
|
+
* Removes a drawable object from the Volume, if it exists. Note that the
|
|
417
|
+
* object's resources are not freed automatically (e.g. via `object.cleanup()`).
|
|
418
|
+
*/
|
|
419
|
+
removeDrawableObject(object: IDrawableObject): void;
|
|
401
420
|
/**
|
|
402
421
|
* @description Enable or disable picking on a volume. If enabled, the channelIndex is used to determine which channel to pick.
|
|
403
422
|
* @param volume the image to enable picking on
|
package/es/types/Volume.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export default class Volume {
|
|
|
25
25
|
loadSpecRequired: Required<LoadSpec>;
|
|
26
26
|
channelLoadCallback?: PerChannelCallback;
|
|
27
27
|
imageMetadata: Record<string, unknown>;
|
|
28
|
-
name: string;
|
|
28
|
+
name: string | undefined;
|
|
29
29
|
channels: Channel[];
|
|
30
30
|
numChannels: number;
|
|
31
31
|
channelNames: string[];
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Vector3, Object3D, Euler, DepthTexture, OrthographicCamera, PerspectiveCamera, WebGLRenderer, WebGLRenderTarget, Texture } from "three";
|
|
2
2
|
import { Pane } from "tweakpane";
|
|
3
3
|
import Volume from "./Volume.js";
|
|
4
|
-
import type { VolumeDisplayOptions, VolumeChannelDisplayOptions, ColorizeFeature } from "./types.js";
|
|
4
|
+
import type { VolumeDisplayOptions, VolumeChannelDisplayOptions, ColorizeFeature, IDrawableObject } from "./types.js";
|
|
5
5
|
import { RenderMode } from "./types.js";
|
|
6
6
|
import { Light } from "./Light.js";
|
|
7
7
|
import Channel from "./Channel.js";
|
|
8
8
|
import { Axis } from "./VolumeRenderSettings.js";
|
|
9
|
-
import Line3d from "./Line3d.js";
|
|
10
9
|
type ColorArray = [number, number, number];
|
|
11
10
|
type ColorObject = {
|
|
12
11
|
r: number;
|
|
@@ -120,19 +119,18 @@ export default class VolumeDrawable {
|
|
|
120
119
|
setRotation(eulerXYZ: Euler): void;
|
|
121
120
|
setScale(xyz: Vector3): void;
|
|
122
121
|
/**
|
|
123
|
-
* Adds a
|
|
124
|
-
* exist.
|
|
125
|
-
*
|
|
126
|
-
*
|
|
122
|
+
* Adds a drawable object as a child of the Volume, if it does not already
|
|
123
|
+
* exist. Objects will be in the normalized coordinate space of the Volume,
|
|
124
|
+
* where the origin (0,0,0) is at the center of the Volume and the extent is
|
|
125
|
+
* from -0.5 to 0.5 in each axis.
|
|
127
126
|
*/
|
|
128
|
-
|
|
129
|
-
/** Returns whether a
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
*
|
|
133
|
-
* object's resources are not freed automatically (e.g. via `line.cleanup()`).
|
|
127
|
+
addDrawableObject(object: IDrawableObject): void;
|
|
128
|
+
/** Returns whether a drawable object exists as a child of the volume. */
|
|
129
|
+
hasDrawableObject(object: IDrawableObject): boolean;
|
|
130
|
+
/** Removes a drawable object from the Volume, if it exists. Note that the
|
|
131
|
+
* object's resources are not freed automatically (e.g. via `object.cleanup()`).
|
|
134
132
|
*/
|
|
135
|
-
|
|
133
|
+
removeDrawableObject(object: IDrawableObject): void;
|
|
136
134
|
setupGui(pane: Pane): void;
|
|
137
135
|
setZSlice(slice: number): boolean;
|
|
138
136
|
get showBoundingBox(): boolean;
|
package/es/types/index.d.ts
CHANGED
|
@@ -20,10 +20,11 @@ import { VolumeLoadError, VolumeLoadErrorType } from "./loaders/VolumeLoadError.
|
|
|
20
20
|
import { type CameraState } from "./ThreeJsPanel.js";
|
|
21
21
|
import { Light, AREA_LIGHT, SKY_LIGHT } from "./Light.js";
|
|
22
22
|
import Line3d from "./Line3d.js";
|
|
23
|
+
import VectorArrows3d from "./VectorArrows3d.js";
|
|
23
24
|
export type { ImageInfo } from "./ImageInfo.js";
|
|
24
25
|
export type { ControlPoint } from "./Lut.js";
|
|
25
26
|
export type { CreateLoaderOptions } from "./loaders/index.js";
|
|
26
27
|
export type { IVolumeLoader, PerChannelCallback, ThreadableVolumeLoader } from "./loaders/IVolumeLoader.js";
|
|
27
28
|
export type { ZarrLoaderFetchOptions } from "./loaders/OmeZarrLoader.js";
|
|
28
29
|
export type { WorkerLoader } from "./workers/VolumeLoaderContext.js";
|
|
29
|
-
export { Histogram, Lut, Line3d, remapControlPoints, View3d, Volume, VolumeDrawable, LoadSpec, VolumeMaker, VolumeCache, RequestQueue, SubscribableRequestQueue, PrefetchDirection, OMEZarrLoader, JsonImageInfoLoader, RawArrayLoader, type RawArrayData, type RawArrayInfo, type RawArrayLoaderOptions, TiffLoader, VolumeLoaderContext, VolumeLoadError, VolumeLoadErrorType, VolumeFileFormat, createVolumeLoader, Channel, Light, ViewportCorner, AREA_LIGHT, RENDERMODE_PATHTRACE, RENDERMODE_RAYMARCH, SKY_LIGHT, type CameraState, type ColorizeFeature, type NumberType, };
|
|
30
|
+
export { Histogram, Lut, Line3d, VectorArrows3d, remapControlPoints, View3d, Volume, VolumeDrawable, LoadSpec, VolumeMaker, VolumeCache, RequestQueue, SubscribableRequestQueue, PrefetchDirection, OMEZarrLoader, JsonImageInfoLoader, RawArrayLoader, type RawArrayData, type RawArrayInfo, type RawArrayLoaderOptions, TiffLoader, VolumeLoaderContext, VolumeLoadError, VolumeLoadErrorType, VolumeFileFormat, createVolumeLoader, Channel, Light, ViewportCorner, AREA_LIGHT, RENDERMODE_PATHTRACE, RENDERMODE_RAYMARCH, SKY_LIGHT, type CameraState, type ColorizeFeature, type NumberType, };
|
package/es/types/types.d.ts
CHANGED
|
@@ -80,6 +80,10 @@ export interface IDrawableObject {
|
|
|
80
80
|
get3dObject(): Group;
|
|
81
81
|
setTranslation(translation: Vector3): void;
|
|
82
82
|
setScale(scale: Vector3): void;
|
|
83
|
+
/**
|
|
84
|
+
* Optional. Should be called when parent transforms are updated.
|
|
85
|
+
*/
|
|
86
|
+
onParentTransformUpdated?(): void;
|
|
83
87
|
setRotation(eulerXYZ: Euler): void;
|
|
84
88
|
setFlipAxes(flipX: number, flipY: number, flipZ: number): void;
|
|
85
89
|
setOrthoThickness(thickness: number): void;
|
package/es/utils/num_utils.js
CHANGED
|
@@ -224,8 +224,12 @@ export function getDataRange(data) {
|
|
|
224
224
|
let min = data[0];
|
|
225
225
|
let max = data[0];
|
|
226
226
|
for (let i = 1; i < data.length; i++) {
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
const value = data[i];
|
|
228
|
+
if (value < min) {
|
|
229
|
+
min = value;
|
|
230
|
+
} else if (value > max) {
|
|
231
|
+
max = value;
|
|
232
|
+
}
|
|
229
233
|
}
|
|
230
234
|
return [min, max];
|
|
231
235
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { fromUrl } from "geotiff";
|
|
2
2
|
import { serializeError } from "serialize-error";
|
|
3
3
|
import { VolumeLoadError, VolumeLoadErrorType } from "../loaders/VolumeLoadError.js";
|
|
4
|
+
import { getDataRange } from "../utils/num_utils.js";
|
|
4
5
|
// from TIFF
|
|
5
6
|
const SAMPLEFORMAT_UINT = 1;
|
|
6
7
|
const SAMPLEFORMAT_INT = 2;
|
|
@@ -121,17 +122,7 @@ async function loadTiffChannel(e) {
|
|
|
121
122
|
// all slices collected, now resample to 8 bits full data range
|
|
122
123
|
const src = castToArray(buffer, bytesPerPixel, sampleFormat);
|
|
123
124
|
const dtype = getDtype(sampleFormat, bytesPerPixel);
|
|
124
|
-
|
|
125
|
-
let chmax = src[0];
|
|
126
|
-
for (let j = 0; j < src.length; ++j) {
|
|
127
|
-
const val = src[j];
|
|
128
|
-
if (val < chmin) {
|
|
129
|
-
chmin = val;
|
|
130
|
-
}
|
|
131
|
-
if (val > chmax) {
|
|
132
|
-
chmax = val;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
125
|
+
const [chmin, chmax] = getDataRange(src);
|
|
135
126
|
return {
|
|
136
127
|
data: src,
|
|
137
128
|
channel: channelIndex,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aics/vole-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.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",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"build": "npm run transpileES && npm run build-types",
|
|
18
18
|
"build-types": "tsc -p tsconfig.types.json",
|
|
19
19
|
"build-demo": "vite build public/ --config vite.config.ts --outDir ./demo",
|
|
20
|
+
"checks": "npm run lint & npm run typeCheck & npx vitest run",
|
|
20
21
|
"clean": "rimraf es/",
|
|
21
22
|
"format": "prettier --write src/**/*.ts",
|
|
22
23
|
"gh-build": "vite build public/ --config vite.config.ts --outDir ./vole-core",
|