@aics/vole-core 3.12.4 → 3.13.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.
Files changed (35) hide show
  1. package/README.md +17 -12
  2. package/es/View3d.js +7 -2
  3. package/es/VolumeRenderSettings.js +11 -0
  4. package/es/loaders/TiffLoader.js +3 -1
  5. package/es/types/NaiveSurfaceNets.d.ts +1 -1
  6. package/es/types/RayMarchedAtlasVolume.d.ts +1 -1
  7. package/es/types/ThreeJsPanel.d.ts +2 -2
  8. package/es/types/TrackballControls.d.ts +1 -1
  9. package/es/types/VolumeDrawable.d.ts +1 -1
  10. package/es/types/VolumeRenderImpl.d.ts +1 -1
  11. package/es/types/index.d.ts +1 -1
  12. package/es/types/workers/VolumeLoaderContext.d.ts +9 -13
  13. package/es/types/workers/types.d.ts +25 -16
  14. package/es/workers/VolumeLoadWorker.js +54 -32
  15. package/es/workers/VolumeLoaderContext.js +52 -51
  16. package/es/workers/types.js +17 -7
  17. package/package.json +13 -13
  18. package/es/test/ChunkPrefetchIterator.test.js +0 -208
  19. package/es/test/RequestQueue.test.js +0 -442
  20. package/es/test/SubscribableRequestQueue.test.js +0 -244
  21. package/es/test/VolumeCache.test.js +0 -118
  22. package/es/test/VolumeRenderSettings.test.js +0 -71
  23. package/es/test/lut.test.js +0 -671
  24. package/es/test/num_utils.test.js +0 -140
  25. package/es/test/volume.test.js +0 -98
  26. package/es/test/zarr_utils.test.js +0 -358
  27. package/es/types/test/ChunkPrefetchIterator.test.d.ts +0 -1
  28. package/es/types/test/RequestQueue.test.d.ts +0 -1
  29. package/es/types/test/SubscribableRequestQueue.test.d.ts +0 -1
  30. package/es/types/test/VolumeCache.test.d.ts +0 -1
  31. package/es/types/test/VolumeRenderSettings.test.d.ts +0 -1
  32. package/es/types/test/lut.test.d.ts +0 -1
  33. package/es/types/test/num_utils.test.d.ts +0 -1
  34. package/es/types/test/volume.test.d.ts +0 -1
  35. package/es/types/test/zarr_utils.test.d.ts +0 -1
package/README.md CHANGED
@@ -16,7 +16,7 @@ There are several ways to deliver volume data to the viewer:
16
16
  - Load raw TypedArrays of 3d volume data ( see `RawArrayLoader` and `Volume.setChannelDataFromVolume` ).
17
17
  - (legacy) Load texture atlases as .png files or Uint8Arrays containing volume slices tiled across a 2d image ( see `JsonImageInfoLoader` and `Volume.setChannelDataFromAtlas` ).
18
18
 
19
- # Example
19
+ ## Example
20
20
 
21
21
  See [`public/index.ts`](./public/index.ts) for a working example. (`npm install; npm run dev` will run that code)
22
22
 
@@ -59,19 +59,20 @@ view3D.addVolume(volume);
59
59
  loader.loadVolumeData(volume);
60
60
  ```
61
61
 
62
- # React example
62
+ ## React example
63
63
 
64
- See [vole-app](https://github.com/allen-cell-animated/website-3d-cell-viewer) for a complete application that wraps View3D in a React component.
64
+ See [vole-app](https://github.com/allen-cell-animated/vole-app) for a complete application that wraps View3D in a React component.
65
65
 
66
- # Acknowledgements
66
+ ## Acknowledgements
67
67
 
68
68
  The ray marched volume shader is a heavily modified version of one that has its origins in [Bisque](http://bioimage.ucsb.edu/bisque).
69
69
  The core path tracing implementation was adapted from ExposureRender.
70
70
 
71
- ## BisQue license
71
+ ### BisQue license
72
72
 
73
73
  Center for Bio-Image Informatics, University of California at Santa Barbara
74
74
 
75
+ ```text
75
76
  Copyright (c) 2007-2017 by the Regents of the University of California
76
77
  All rights reserved
77
78
 
@@ -106,14 +107,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
106
107
  The views and conclusions contained in the software and documentation
107
108
  are those of the authors and should not be interpreted as representing
108
109
  official policies, either expressed or implied, of the Regents of the University of California.
110
+ ```
109
111
 
110
112
  ## Exposure Render license
111
113
 
112
- Copyright (c) 2011, T. Kroes <t.kroes@tudelft.nl>
113
- All rights reserved.
114
- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
115
- - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
116
- - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
117
- - Neither the name of the TU Delft nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
114
+ ```text
115
+ Copyright (c) 2011, T. Kroes <t.kroes@tudelft.nl>
116
+ All rights reserved.
117
+
118
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
119
+ - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
120
+ - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
121
+ - Neither the name of the TU Delft nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
118
122
 
119
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
123
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
124
+ ```
package/es/View3d.js CHANGED
@@ -826,16 +826,21 @@ export class View3d {
826
826
  const prefetch = pane.addFolder({
827
827
  title: "Prefetch"
828
828
  });
829
+ // Not all `IVolumeLoader`s implement `updateFetchOptions`. This cast makes it sound to try to call it, but we
830
+ // still have to be careful to null-check it!
831
+ // TODO depending on how the relationship between loaders and images pans out, it's not impossible that the loader
832
+ // for an image will be changeable and this variable will capture a stale reference to old loaders. Careful!
833
+ const loader = this.image?.volume.loader;
829
834
  // one number will be used for all axis directions
830
835
  prefetch.addInput(allGlobalLoadingOptions, "numChunksToPrefetchAhead").on("change", event => {
831
- this.loaderContext?.getActiveLoader()?.updateFetchOptions({
836
+ loader?.updateFetchOptions?.({
832
837
  maxPrefetchDistance: [event.value, event.value, event.value, event.value]
833
838
  });
834
839
  this.image?.volume.updateRequiredData({});
835
840
  });
836
841
  // should we try to prefetch along Z even if we are only playing along T?
837
842
  prefetch.addInput(allGlobalLoadingOptions, "prefetchAlongNonPlayingAxis").on("change", event => {
838
- this.loaderContext?.getActiveLoader()?.updateFetchOptions({
843
+ loader?.updateFetchOptions?.({
839
844
  onlyPriorityDirections: !event.value
840
845
  });
841
846
  });
@@ -3,14 +3,23 @@ import { Euler, Vector2, Vector3 } from "three";
3
3
  * Marks groups of related settings that may have changed.
4
4
  */
5
5
  export let SettingsFlags = /*#__PURE__*/function (SettingsFlags) {
6
+ /** parameters: translation, rotation, scale, flipAxes */
6
7
  SettingsFlags[SettingsFlags["TRANSFORM"] = 1] = "TRANSFORM";
8
+ /** parameters: gammaMin, gammaLevel, gammaMax, brightness*/
7
9
  SettingsFlags[SettingsFlags["CAMERA"] = 2] = "CAMERA";
10
+ /** parameters: showBoundingBox, boundingBoxColor */
8
11
  SettingsFlags[SettingsFlags["BOUNDING_BOX"] = 4] = "BOUNDING_BOX";
12
+ /** parameters: bounds, zSlice */
9
13
  SettingsFlags[SettingsFlags["ROI"] = 8] = "ROI";
14
+ /** parameters: maskAlpha */
10
15
  SettingsFlags[SettingsFlags["MASK_ALPHA"] = 16] = "MASK_ALPHA";
16
+ /** parameters: density, diffuse, specular, emissive, glossiness */
11
17
  SettingsFlags[SettingsFlags["MATERIAL"] = 32] = "MATERIAL";
18
+ /** parameters: resolution, useInterpolation, pixelSamplingRate, primaryRayStepSize, secondaryRayStepSize*/
12
19
  SettingsFlags[SettingsFlags["SAMPLING"] = 64] = "SAMPLING";
20
+ /** parameters: isOrtho, orthoScale, viewAxis, visible, maxProjectMode */
13
21
  SettingsFlags[SettingsFlags["VIEW"] = 128] = "VIEW";
22
+ /** parameters: maskChannelIndex */
14
23
  SettingsFlags[SettingsFlags["MASK_DATA"] = 256] = "MASK_DATA";
15
24
  SettingsFlags[SettingsFlags["ALL"] = 1023] = "ALL";
16
25
  return SettingsFlags;
@@ -19,7 +28,9 @@ export let Axis = /*#__PURE__*/function (Axis) {
19
28
  Axis["X"] = "x";
20
29
  Axis["Y"] = "y";
21
30
  Axis["Z"] = "z";
31
+ /** Alias for NONE, indicates 3D mode */
22
32
  Axis["XYZ"] = "";
33
+ /** No current axis, indicates 3D mode */
23
34
  Axis["NONE"] = "";
24
35
  return Axis;
25
36
  }({});
@@ -191,7 +191,9 @@ class TiffLoader extends ThreadableVolumeLoader {
191
191
  bytesPerSample: getBytesPerSample(dims.pixeltype),
192
192
  url: this.url
193
193
  };
194
- const worker = new Worker(new URL("../workers/FetchTiffWorker", import.meta.url));
194
+ const worker = new Worker(new URL("../workers/FetchTiffWorker", import.meta.url), {
195
+ type: "module"
196
+ });
195
197
  worker.onmessage = e => {
196
198
  if (e.data.isError) {
197
199
  reject(deserializeError(e.data.error));
@@ -7,5 +7,5 @@ declare function SurfaceNets(data: any, dims: any, isovalue: any): {
7
7
  vertices: number[][];
8
8
  faces: number[][];
9
9
  };
10
- declare function ConstructTHREEGeometry(surfaceNetResult: any): BufferGeometry[];
10
+ declare function ConstructTHREEGeometry(surfaceNetResult: any): BufferGeometry<import("three").NormalBufferAttributes>[];
11
11
  import { BufferGeometry } from "three/src/core/BufferGeometry";
@@ -33,7 +33,7 @@ export default class RayMarchedAtlasVolume implements VolumeRenderImpl {
33
33
  private createGeometry;
34
34
  private createTickMarks;
35
35
  cleanup(): void;
36
- doRender(renderer: WebGLRenderer, camera: PerspectiveCamera | OrthographicCamera, depthTexture?: DepthTexture | Texture): void;
36
+ doRender(renderer: WebGLRenderer, camera: PerspectiveCamera | OrthographicCamera, depthTexture?: DepthTexture | Texture | null): void;
37
37
  get3dObject(): Group;
38
38
  private setUniform;
39
39
  updateActiveChannels(channelcolors: FuseChannel[], channeldata: Channel[]): void;
@@ -18,7 +18,7 @@ export declare class ThreeJsPanel {
18
18
  scene: Scene;
19
19
  private meshRenderTarget;
20
20
  private meshRenderToBuffer;
21
- animateFuncs: ((renderer: WebGLRenderer, camera: PerspectiveCamera | OrthographicCamera, depthTexture?: DepthTexture) => void)[];
21
+ animateFuncs: ((renderer: WebGLRenderer, camera: PerspectiveCamera | OrthographicCamera, depthTexture?: DepthTexture | null) => void)[];
22
22
  private inRenderLoop;
23
23
  private requestedRender;
24
24
  hasWebGL2: boolean;
@@ -81,7 +81,7 @@ export declare class ThreeJsPanel {
81
81
  replaceCamera(newCam: PerspectiveCamera | OrthographicCamera): void;
82
82
  replaceControls(newControls: TrackballControls): void;
83
83
  switchViewMode(mode: string): void;
84
- getMeshDepthTexture(): DepthTexture;
84
+ getMeshDepthTexture(): DepthTexture | null;
85
85
  resize(comp: HTMLElement | null, w?: number, h?: number, _ow?: number, _oh?: number, _eOpts?: unknown): void;
86
86
  setClearColor(color: Color, alpha: number): void;
87
87
  getWidth(): number;
@@ -1,5 +1,5 @@
1
1
  export default TrackballControls;
2
- declare class TrackballControls extends EventDispatcher<import("three").Event> {
2
+ declare class TrackballControls extends EventDispatcher<any> {
3
3
  constructor(object: any, domElement: any);
4
4
  object: any;
5
5
  domElement: any;
@@ -56,7 +56,7 @@ export default class VolumeDrawable {
56
56
  setGamma(gmin: number, glevel: number, gmax: number): void;
57
57
  setFlipAxes(flipX: -1 | 1, flipY: -1 | 1, flipZ: -1 | 1): void;
58
58
  setMaxProjectMode(isMaxProject: boolean): void;
59
- onAnimate(renderer: WebGLRenderer, camera: PerspectiveCamera | OrthographicCamera, depthTexture?: DepthTexture | Texture): void;
59
+ onAnimate(renderer: WebGLRenderer, camera: PerspectiveCamera | OrthographicCamera, depthTexture?: DepthTexture | Texture | null): void;
60
60
  getViewMode(): Axis;
61
61
  getIsovalue(channel: number): number | undefined;
62
62
  hasIsosurface(channel: number): boolean;
@@ -13,7 +13,7 @@ export interface VolumeRenderImpl {
13
13
  */
14
14
  updateSettings: (settings: VolumeRenderSettings, dirtyFlags?: number | SettingsFlags) => void;
15
15
  get3dObject: () => Object3D;
16
- doRender: (renderer: WebGLRenderer, camera: PerspectiveCamera | OrthographicCamera, depthTexture?: DepthTexture | Texture) => void;
16
+ doRender: (renderer: WebGLRenderer, camera: PerspectiveCamera | OrthographicCamera, depthTexture?: DepthTexture | Texture | null) => void;
17
17
  updateVolumeDimensions: () => void;
18
18
  cleanup: () => void;
19
19
  viewpointMoved: () => void;
@@ -22,7 +22,7 @@ import { Light, AREA_LIGHT, SKY_LIGHT } from "./Light.js";
22
22
  export type { ImageInfo } from "./ImageInfo.js";
23
23
  export type { ControlPoint } from "./Lut.js";
24
24
  export type { CreateLoaderOptions } from "./loaders/index.js";
25
- export type { IVolumeLoader, PerChannelCallback } from "./loaders/IVolumeLoader.js";
25
+ export type { IVolumeLoader, PerChannelCallback, ThreadableVolumeLoader } from "./loaders/IVolumeLoader.js";
26
26
  export type { ZarrLoaderFetchOptions } from "./loaders/OmeZarrLoader.js";
27
27
  export type { WorkerLoader } from "./workers/VolumeLoaderContext.js";
28
28
  export { Histogram, Lut, 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, };
@@ -4,9 +4,8 @@ import { CreateLoaderOptions, PrefetchDirection } from "../loaders/index.js";
4
4
  import { ThreadableVolumeLoader, LoadSpec, RawChannelDataCallback, LoadedVolumeInfo } from "../loaders/IVolumeLoader.js";
5
5
  import { RawArrayLoader } from "../loaders/RawArrayLoader.js";
6
6
  import { TiffLoader } from "../loaders/TiffLoader.js";
7
- import type { WorkerRequestPayload, WorkerResponsePayload, ChannelLoadEvent, MetadataUpdateEvent } from "./types.js";
7
+ import type { ChannelLoadEvent, MetadataUpdateEvent, WorkerMsgTypeGlobal, WorkerMsgTypeWithLoader, WorkerRequestPayload, WorkerResponsePayload } from "./types.js";
8
8
  import type { ZarrLoaderFetchOptions } from "../loaders/OmeZarrLoader.js";
9
- import { WorkerMsgType } from "./types.js";
10
9
  /**
11
10
  * A handle that holds the worker and manages requests and messages to/from it.
12
11
  *
@@ -22,9 +21,7 @@ declare class SharedLoadWorkerHandle {
22
21
  private worker;
23
22
  private pendingRequests;
24
23
  private workerOpen;
25
- private throttleChannelData;
26
- onChannelData: ((e: ChannelLoadEvent) => void) | undefined;
27
- onUpdateMetadata: ((e: MetadataUpdateEvent) => void) | undefined;
24
+ onEvent: ((e: ChannelLoadEvent | MetadataUpdateEvent) => void) | undefined;
28
25
  constructor();
29
26
  /** Given a handle for settling a promise when a response is received from the worker, store it and return its ID */
30
27
  private registerMessagePromise;
@@ -35,16 +32,16 @@ declare class SharedLoadWorkerHandle {
35
32
  * Send a message of type `T` to the worker.
36
33
  * Returns a `Promise` that resolves with the worker's response, or rejects with an error message.
37
34
  */
38
- sendMessage<T extends WorkerMsgType>(type: T, payload: WorkerRequestPayload<T>): Promise<WorkerResponsePayload<T>>;
35
+ sendMessage<T extends WorkerMsgTypeGlobal>(type: T, payload: WorkerRequestPayload<T>): Promise<WorkerResponsePayload<T>>;
36
+ sendMessage<T extends WorkerMsgTypeWithLoader>(type: T, payload: WorkerRequestPayload<T>, loaderId: number): Promise<WorkerResponsePayload<T>>;
39
37
  /** Receive a message from the worker. If it's an event, call a callback; otherwise, resolve/reject a promise. */
40
38
  private receiveMessage;
41
- setThrottleChannelData(throttle: boolean): void;
42
39
  }
43
40
  /**
44
41
  * A context in which volume loaders can be run, which allows loading to run on a WebWorker (where it won't block
45
42
  * rendering or UI updates) and loaders to share a single `VolumeCache` and `RequestQueue`.
46
43
  *
47
- * ### To use:
44
+ * # To use:
48
45
  * 1. Create a `VolumeLoaderContext` with the desired cache and queue configuration.
49
46
  * 2. Before creating a loader, await `onOpen` to ensure the worker is ready.
50
47
  * 3. Create a loader with `createLoader`. This accepts nearly the same arguments as `createVolumeLoader`, but without
@@ -56,14 +53,15 @@ declare class SharedLoadWorkerHandle {
56
53
  */
57
54
  declare class VolumeLoaderContext {
58
55
  private workerHandle;
56
+ private loaders;
59
57
  private openPromise;
60
- private activeLoader;
61
- private activeLoaderId;
58
+ private throttleChannelData;
62
59
  constructor(maxCacheSize?: number, maxActiveRequests?: number, maxLowPriorityRequests?: number);
63
60
  /** Returns a `Promise` that resolves when the worker is ready. `await` it before trying to create a loader. */
64
61
  onOpen(): Promise<void>;
65
62
  /** Close this context, its worker, and any active loaders. */
66
63
  close(): void;
64
+ private handleEvent;
67
65
  /**
68
66
  * Create a new loader within this context. This loader will share the context's `VolumeCache` and `RequestQueue`.
69
67
  *
@@ -71,7 +69,6 @@ declare class VolumeLoaderContext {
71
69
  */
72
70
  createLoader(path: string | string[], options?: Omit<CreateLoaderOptions, "cache" | "queue">): Promise<WorkerLoader | TiffLoader | RawArrayLoader>;
73
71
  setThrottleChannelData(throttle: boolean): void;
74
- getActiveLoader(): WorkerLoader | undefined;
75
72
  }
76
73
  /**
77
74
  * A handle to an instance of `IVolumeLoader` (technically, a `ThreadableVolumeLoader`) running on a WebWorker.
@@ -81,12 +78,11 @@ declare class VolumeLoaderContext {
81
78
  declare class WorkerLoader extends ThreadableVolumeLoader {
82
79
  private loaderId;
83
80
  private workerHandle;
84
- private isOpen;
85
81
  private currentLoadId;
86
82
  private currentLoadCallback;
87
83
  private currentMetadataUpdateCallback;
88
84
  constructor(loaderId: number, workerHandle: SharedLoadWorkerHandle);
89
- private checkIsOpen;
85
+ private getLoaderId;
90
86
  /** Close and permanently invalidate this loader. */
91
87
  close(): void;
92
88
  /**
@@ -9,13 +9,18 @@ import type { ZarrLoaderFetchOptions } from "../loaders/OmeZarrLoader.js";
9
9
  export declare const enum WorkerMsgType {
10
10
  INIT = 0,
11
11
  CREATE_LOADER = 1,
12
- CREATE_VOLUME = 2,
13
- LOAD_DIMS = 3,
14
- LOAD_VOLUME_DATA = 4,
15
- SET_PREFETCH_PRIORITY_DIRECTIONS = 5,
16
- SYNCHRONIZE_MULTICHANNEL_LOADING = 6,
17
- UPDATE_FETCH_OPTIONS = 7
12
+ CLOSE_LOADER = 2,
13
+ CREATE_VOLUME = 3,
14
+ LOAD_DIMS = 4,
15
+ LOAD_VOLUME_DATA = 5,
16
+ SET_PREFETCH_PRIORITY_DIRECTIONS = 6,
17
+ SYNCHRONIZE_MULTICHANNEL_LOADING = 7,
18
+ UPDATE_FETCH_OPTIONS = 8
18
19
  }
20
+ /** The variants of `WorkerMessageType` which represent "global" actions that don't require a specific loader */
21
+ export type WorkerMsgTypeGlobal = WorkerMsgType.INIT | WorkerMsgType.CREATE_LOADER;
22
+ /** The variants of `WorkerMessageType` which represent actions on a specific loader */
23
+ export type WorkerMsgTypeWithLoader = Exclude<WorkerMsgType, WorkerMsgTypeGlobal>;
19
24
  /** The kind of response a worker can return - `SUCCESS`, `ERROR`, or `EVENT`. */
20
25
  export declare const enum WorkerResponseResult {
21
26
  SUCCESS = 0,
@@ -29,9 +34,13 @@ export declare const enum WorkerEventType {
29
34
  /** Fired when data for a channel (or batch of channels) is loaded */
30
35
  CHANNEL_LOAD = 1
31
36
  }
32
- /** All messages to/from a worker carry a `msgId`, a `type`, and a `payload` (whose type is determined by `type`). */
37
+ /**
38
+ * All messages to/from a worker carry a `msgId`, a `type`, and a `payload` (whose type is determined by `type`).
39
+ * Messages which operate on a specific loader also require a `loaderId`.
40
+ */
33
41
  type WorkerMsgBase<T extends WorkerMsgType, P> = {
34
42
  msgId: number;
43
+ loaderId: T extends WorkerMsgTypeWithLoader ? number : undefined;
35
44
  type: T;
36
45
  payload: P;
37
46
  };
@@ -46,12 +55,12 @@ export type WorkerRequestPayload<T extends WorkerMsgType> = {
46
55
  path: string | string[];
47
56
  options?: CreateLoaderOptions;
48
57
  };
58
+ [WorkerMsgType.CLOSE_LOADER]: void;
49
59
  [WorkerMsgType.CREATE_VOLUME]: LoadSpec;
50
60
  [WorkerMsgType.LOAD_DIMS]: LoadSpec;
51
61
  [WorkerMsgType.LOAD_VOLUME_DATA]: {
52
62
  imageInfo: ImageInfo;
53
63
  loadSpec: LoadSpec;
54
- loaderId: number;
55
64
  loadId: number;
56
65
  };
57
66
  [WorkerMsgType.SET_PREFETCH_PRIORITY_DIRECTIONS]: PrefetchDirection[];
@@ -61,7 +70,8 @@ export type WorkerRequestPayload<T extends WorkerMsgType> = {
61
70
  /** Maps each `WorkerMsgType` to the type of the payload of responses of that type. */
62
71
  export type WorkerResponsePayload<T extends WorkerMsgType> = {
63
72
  [WorkerMsgType.INIT]: void;
64
- [WorkerMsgType.CREATE_LOADER]: boolean;
73
+ [WorkerMsgType.CREATE_LOADER]: number | undefined;
74
+ [WorkerMsgType.CLOSE_LOADER]: void;
65
75
  [WorkerMsgType.CREATE_VOLUME]: LoadedVolumeInfo;
66
76
  [WorkerMsgType.LOAD_DIMS]: VolumeDims[];
67
77
  [WorkerMsgType.LOAD_VOLUME_DATA]: void;
@@ -69,11 +79,13 @@ export type WorkerResponsePayload<T extends WorkerMsgType> = {
69
79
  [WorkerMsgType.SYNCHRONIZE_MULTICHANNEL_LOADING]: void;
70
80
  [WorkerMsgType.UPDATE_FETCH_OPTIONS]: void;
71
81
  }[T];
72
- /** Event for when a batch of channel data loads. */
73
- export type ChannelLoadEvent = {
74
- eventType: WorkerEventType.CHANNEL_LOAD;
82
+ type WorkerEventBase<T extends WorkerEventType> = {
83
+ eventType: T;
75
84
  loaderId: number;
76
85
  loadId: number;
86
+ };
87
+ /** Event for when a batch of channel data loads. */
88
+ export type ChannelLoadEvent = WorkerEventBase<WorkerEventType.CHANNEL_LOAD> & {
77
89
  channelIndex: number[];
78
90
  dtype: NumberType[];
79
91
  data: TypedArray<NumberType>[];
@@ -81,10 +93,7 @@ export type ChannelLoadEvent = {
81
93
  atlasDims?: [number, number];
82
94
  };
83
95
  /** Event for when metadata updates. */
84
- export type MetadataUpdateEvent = {
85
- eventType: WorkerEventType.METADATA_UPDATE;
86
- loaderId: number;
87
- loadId: number;
96
+ export type MetadataUpdateEvent = WorkerEventBase<WorkerEventType.METADATA_UPDATE> & {
88
97
  imageInfo?: ImageInfo;
89
98
  loadSpec?: LoadSpec;
90
99
  };
@@ -9,9 +9,16 @@ import { rebuildLoadSpec } from "./util.js";
9
9
  let cache = undefined;
10
10
  let queue = undefined;
11
11
  let subscribableQueue = undefined;
12
- let loader = undefined;
12
+ let loaderCount = 0;
13
+ const loaders = new Map();
14
+ const getLoader = loaderId => {
15
+ const loader = loaders.get(loaderId);
16
+ if (loader === undefined) {
17
+ throw new VolumeLoadError(`Loader with ID ${loaderId} does not exist`);
18
+ }
19
+ return loader;
20
+ };
13
21
  let initialized = false;
14
- let copyOnLoad = false;
15
22
  const messageHandlers = {
16
23
  [WorkerMsgType.INIT]: ({
17
24
  maxCacheSize,
@@ -30,37 +37,50 @@ const messageHandlers = {
30
37
  path,
31
38
  options
32
39
  }) => {
33
- const pathString = Array.isArray(path) ? path[0] : path;
34
- const fileType = options?.fileType || pathToFileType(pathString);
35
- copyOnLoad = fileType === VolumeFileFormat.JSON;
36
- loader = await createVolumeLoader(path, {
40
+ const loader = await createVolumeLoader(path, {
37
41
  ...options,
38
42
  cache,
39
43
  queue: subscribableQueue
40
44
  });
41
- return loader !== undefined;
42
- },
43
- [WorkerMsgType.CREATE_VOLUME]: async loadSpec => {
44
45
  if (loader === undefined) {
45
- throw new VolumeLoadError("No loader created");
46
+ return undefined;
46
47
  }
48
+ const pathString = Array.isArray(path) ? path[0] : path;
49
+ const fileType = options?.fileType || pathToFileType(pathString);
50
+ const copyOnLoad = fileType === VolumeFileFormat.JSON;
51
+ const loaderId = loaderCount;
52
+ loaderCount += 1;
53
+ loaders.set(loaderId, {
54
+ loader,
55
+ copyOnLoad
56
+ });
57
+ return loaderId;
58
+ },
59
+ [WorkerMsgType.CLOSE_LOADER]: (_, loaderId) => {
60
+ loaders.delete(loaderId);
61
+ return Promise.resolve();
62
+ },
63
+ [WorkerMsgType.CREATE_VOLUME]: async (loadSpec, loaderId) => {
64
+ const {
65
+ loader
66
+ } = getLoader(loaderId);
47
67
  return await loader.createImageInfo(rebuildLoadSpec(loadSpec));
48
68
  },
49
- [WorkerMsgType.LOAD_DIMS]: async loadSpec => {
50
- if (loader === undefined) {
51
- throw new VolumeLoadError("No loader created");
52
- }
69
+ [WorkerMsgType.LOAD_DIMS]: async (loadSpec, loaderId) => {
70
+ const {
71
+ loader
72
+ } = getLoader(loaderId);
53
73
  return await loader.loadDims(rebuildLoadSpec(loadSpec));
54
74
  },
55
75
  [WorkerMsgType.LOAD_VOLUME_DATA]: ({
56
76
  imageInfo,
57
77
  loadSpec,
58
- loaderId,
59
78
  loadId
60
- }) => {
61
- if (loader === undefined) {
62
- throw new VolumeLoadError("No loader created");
63
- }
79
+ }, loaderId) => {
80
+ const {
81
+ loader,
82
+ copyOnLoad
83
+ } = getLoader(loaderId);
64
84
  return loader.loadRawChannelData(imageInfo, rebuildLoadSpec(loadSpec), (imageInfo, loadSpec) => {
65
85
  const message = {
66
86
  responseResult: WorkerResponseResult.EVENT,
@@ -86,16 +106,25 @@ const messageHandlers = {
86
106
  self.postMessage(message, copyOnLoad ? [] : data.map(d => d.buffer));
87
107
  });
88
108
  },
89
- [WorkerMsgType.SET_PREFETCH_PRIORITY_DIRECTIONS]: directions => {
109
+ [WorkerMsgType.SET_PREFETCH_PRIORITY_DIRECTIONS]: (directions, loaderId) => {
110
+ const {
111
+ loader
112
+ } = getLoader(loaderId);
90
113
  // Silently does nothing if the loader isn't an `OMEZarrLoader`
91
114
  loader?.setPrefetchPriority(directions);
92
115
  return Promise.resolve();
93
116
  },
94
- [WorkerMsgType.SYNCHRONIZE_MULTICHANNEL_LOADING]: syncChannels => {
117
+ [WorkerMsgType.SYNCHRONIZE_MULTICHANNEL_LOADING]: (syncChannels, loaderId) => {
118
+ const {
119
+ loader
120
+ } = getLoader(loaderId);
95
121
  loader?.syncMultichannelLoading(syncChannels);
96
122
  return Promise.resolve();
97
123
  },
98
- [WorkerMsgType.UPDATE_FETCH_OPTIONS]: fetchOptions => {
124
+ [WorkerMsgType.UPDATE_FETCH_OPTIONS]: (fetchOptions, loaderId) => {
125
+ const {
126
+ loader
127
+ } = getLoader(loaderId);
99
128
  loader?.updateFetchOptions(fetchOptions);
100
129
  return Promise.resolve();
101
130
  }
@@ -103,25 +132,18 @@ const messageHandlers = {
103
132
  self.onmessage = async ({
104
133
  data
105
134
  }) => {
106
- const {
107
- msgId,
108
- type,
109
- payload
110
- } = data;
111
135
  let message;
112
136
  try {
113
- const response = await messageHandlers[type](payload);
137
+ const response = await messageHandlers[data.type](data.payload, data.loaderId);
114
138
  message = {
139
+ ...data,
115
140
  responseResult: WorkerResponseResult.SUCCESS,
116
- msgId,
117
- type,
118
141
  payload: response
119
142
  };
120
143
  } catch (e) {
121
144
  message = {
145
+ ...data,
122
146
  responseResult: WorkerResponseResult.ERROR,
123
- msgId,
124
- type,
125
147
  payload: serializeError(e)
126
148
  };
127
149
  }