@aics/vole-core 4.4.0 → 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/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
- // calculate min and max of arr
242
- // TODO See convertChannel, which will also compute min and max!
243
- // We could save a whole extra loop over the data, or have convertChannel compute the whole histogram.
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
- let min = arr[0];
247
- let max = arr[0];
248
- for (let i = 1; i < arr.length; i++) {
249
- if (arr[i] < min) {
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/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, namePrefix) {
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") {
@@ -1,7 +1,6 @@
1
+ import BaseDrawableMeshObject from "./BaseDrawableMeshObject.js";
2
+ import { MESH_NO_PICK_OCCLUSION_LAYER } from "./ThreeJsPanel.js";
1
3
  import { InstancedMesh, CylinderGeometry, ConeGeometry, Object3D, Vector3, MeshBasicMaterial, Color, DynamicDrawUsage, Matrix4 } from "three";
2
- import BaseDrawableMeshObject from "./BaseDrawableMeshObject";
3
- import { MESH_NO_PICK_OCCLUSION_LAYER } from "./ThreeJsPanel";
4
-
5
4
  // Unscaled arrowhead dimensions.
6
5
  const SHAFT_BASE_RADIUS = 0.5;
7
6
  const HEAD_BASE_RADIUS = 1.5;
@@ -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
- // TODO FIXME Histogram will also compute min and max!
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 || "Volume",
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,
@@ -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") || "XYZCT";
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: "TEST",
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
  }],
@@ -1,5 +1,5 @@
1
1
  import { Euler, Group, Mesh, Vector3 } from "three";
2
- import { IDrawableObject } from "./types";
2
+ import { IDrawableObject } from "./types.js";
3
3
  /**
4
4
  * Abstract base class for drawable 3D mesh objects.
5
5
  *
@@ -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
@@ -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
@@ -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, namePrefix: string): void;
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[];
@@ -1,6 +1,6 @@
1
+ import BaseDrawableMeshObject from "./BaseDrawableMeshObject.js";
1
2
  import { Vector3, Color } from "three";
2
- import { IDrawableObject } from "./types";
3
- import BaseDrawableMeshObject from "./BaseDrawableMeshObject";
3
+ import { IDrawableObject } from "./types.js";
4
4
  /**
5
5
  * A drawable vector arrow field, which uses instanced meshes for performance.
6
6
  */
@@ -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[];
@@ -3,6 +3,7 @@ import { type ImageInfo } from "../ImageInfo.js";
3
3
  import type { VolumeDims } from "../VolumeDims.js";
4
4
  import { TypedArray, NumberType } from "../types.js";
5
5
  declare class OMEDims {
6
+ name: string | undefined;
6
7
  sizex: number;
7
8
  sizey: number;
8
9
  sizez: number;
@@ -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
- min = Math.min(min, data[i]);
228
- max = Math.max(max, data[i]);
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
- let chmin = src[0];
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.4.0",
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",