@knopkem/dicomview 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { DICOMwebLoader } from "./loader.js";
2
- export { Presets } from "./presets.js";
3
- export { ensureDicomviewWasm, Viewer } from "./viewer.js";
4
- export type { BlendMode, DICOMwebLoaderOptions, ProgressCallback, ProjectionMode, SeriesParams, ThickSlabOptions, ViewportId, ViewerOptions, VolumeGeometry, VolumePreset, WasmSource, } from "./types.js";
2
+ export { CornerstonePresets, Presets } from "./presets.js";
3
+ export { ensureDicomviewWasm, StackViewer, Viewer } from "./viewer.js";
4
+ export { InputHandler, VolumeInputHandler } from "./input-handler.js";
5
+ export type { BlendMode, DICOMwebLoaderOptions, InputTool, ProgressCallback, ProjectionMode, SeriesParams, StackViewerOptions, ThickSlabOptions, ViewportId, ViewerOptions, VolumeGeometry, VolumePreset, WasmSource, } from "./types.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { DICOMwebLoader } from "./loader.js";
2
- export { Presets } from "./presets.js";
3
- export { ensureDicomviewWasm, Viewer } from "./viewer.js";
2
+ export { CornerstonePresets, Presets } from "./presets.js";
3
+ export { ensureDicomviewWasm, StackViewer, Viewer } from "./viewer.js";
4
+ export { InputHandler, VolumeInputHandler } from "./input-handler.js";
@@ -0,0 +1,35 @@
1
+ import type { InputTool, ViewportId } from "./types.js";
2
+ import type { StackViewer, Viewer } from "./viewer.js";
3
+ type ViewerLike = Viewer | StackViewer;
4
+ /**
5
+ * Optional built-in input handler for slice viewports.
6
+ *
7
+ * Binds pointer and wheel events to a canvas and translates them into
8
+ * Viewer API calls based on the active tool. Supports left/middle/right
9
+ * mouse button differentiation:
10
+ * - **Left button**: active tool (windowLevel, pan, zoom, crosshair, scroll)
11
+ * - **Middle button**: pan
12
+ * - **Right button**: zoom
13
+ * - **Wheel**: scroll (shift+wheel → zoom)
14
+ */
15
+ export declare class InputHandler {
16
+ #private;
17
+ constructor(canvas: HTMLCanvasElement, viewer: ViewerLike, viewport?: ViewportId);
18
+ get activeTool(): InputTool;
19
+ setActiveTool(tool: InputTool): void;
20
+ destroy(): void;
21
+ }
22
+ /**
23
+ * Input handler specialized for the 3D volume viewport.
24
+ *
25
+ * - **Left drag**: orbit (rotate camera)
26
+ * - **Middle drag**: pan
27
+ * - **Right drag**: zoom
28
+ * - **Wheel**: zoom
29
+ */
30
+ export declare class VolumeInputHandler {
31
+ #private;
32
+ constructor(canvas: HTMLCanvasElement, viewer: Viewer);
33
+ destroy(): void;
34
+ }
35
+ export {};
@@ -0,0 +1,227 @@
1
+ const WL_SENSITIVITY = 1.0;
2
+ const PAN_SENSITIVITY = 1.0;
3
+ const ZOOM_SENSITIVITY = 0.005;
4
+ const SCROLL_SENSITIVITY = 1.0;
5
+ function hasMethod(viewer, name) {
6
+ return typeof viewer[name] === "function";
7
+ }
8
+ /**
9
+ * Optional built-in input handler for slice viewports.
10
+ *
11
+ * Binds pointer and wheel events to a canvas and translates them into
12
+ * Viewer API calls based on the active tool. Supports left/middle/right
13
+ * mouse button differentiation:
14
+ * - **Left button**: active tool (windowLevel, pan, zoom, crosshair, scroll)
15
+ * - **Middle button**: pan
16
+ * - **Right button**: zoom
17
+ * - **Wheel**: scroll (shift+wheel → zoom)
18
+ */
19
+ export class InputHandler {
20
+ #canvas;
21
+ #viewer;
22
+ #viewport;
23
+ #activeTool = "windowLevel";
24
+ #dragging = null;
25
+ #abortController = new AbortController();
26
+ #renderScheduled = false;
27
+ constructor(canvas, viewer, viewport = "axial") {
28
+ this.#canvas = canvas;
29
+ this.#viewer = viewer;
30
+ this.#viewport = viewport;
31
+ this.#bind();
32
+ }
33
+ get activeTool() {
34
+ return this.#activeTool;
35
+ }
36
+ setActiveTool(tool) {
37
+ this.#activeTool = tool;
38
+ }
39
+ destroy() {
40
+ this.#abortController.abort();
41
+ this.#dragging = null;
42
+ }
43
+ #bind() {
44
+ const signal = this.#abortController.signal;
45
+ const canvas = this.#canvas;
46
+ canvas.addEventListener("pointerdown", (e) => {
47
+ e.preventDefault();
48
+ canvas.setPointerCapture(e.pointerId);
49
+ this.#dragging = { startX: e.clientX, startY: e.clientY, button: e.button };
50
+ }, { signal });
51
+ canvas.addEventListener("pointermove", (e) => {
52
+ if (!this.#dragging)
53
+ return;
54
+ const dx = e.clientX - this.#dragging.startX;
55
+ const dy = e.clientY - this.#dragging.startY;
56
+ this.#dragging.startX = e.clientX;
57
+ this.#dragging.startY = e.clientY;
58
+ this.#handleDrag(dx, dy, this.#dragging.button);
59
+ }, { signal });
60
+ canvas.addEventListener("pointerup", (e) => {
61
+ canvas.releasePointerCapture(e.pointerId);
62
+ this.#dragging = null;
63
+ }, { signal });
64
+ canvas.addEventListener("pointercancel", (e) => {
65
+ canvas.releasePointerCapture(e.pointerId);
66
+ this.#dragging = null;
67
+ }, { signal });
68
+ canvas.addEventListener("wheel", (e) => {
69
+ e.preventDefault();
70
+ if (e.shiftKey) {
71
+ const factor = 1.0 - e.deltaY * ZOOM_SENSITIVITY;
72
+ if (hasMethod(this.#viewer, "zoom")) {
73
+ this.#viewer.zoom(factor);
74
+ }
75
+ }
76
+ else {
77
+ const delta = (e.deltaY > 0 ? 1 : -1) * SCROLL_SENSITIVITY;
78
+ this.#scroll(delta);
79
+ }
80
+ this.#scheduleRender();
81
+ }, { signal, passive: false });
82
+ canvas.addEventListener("contextmenu", (e) => e.preventDefault(), { signal });
83
+ }
84
+ #handleDrag(dx, dy, button) {
85
+ if (button === 1) {
86
+ this.#pan(dx, dy);
87
+ }
88
+ else if (button === 2) {
89
+ const factor = 1.0 - dy * ZOOM_SENSITIVITY;
90
+ if (hasMethod(this.#viewer, "zoom")) {
91
+ this.#viewer.zoom(factor);
92
+ }
93
+ }
94
+ else {
95
+ this.#handleToolDrag(dx, dy);
96
+ }
97
+ this.#scheduleRender();
98
+ }
99
+ #handleToolDrag(dx, dy) {
100
+ switch (this.#activeTool) {
101
+ case "windowLevel": {
102
+ const viewer = this.#viewer;
103
+ // Approximate W/L adjustment — caller should maintain center/width state
104
+ // for proper behavior; this provides a reasonable default interaction.
105
+ viewer.setWindowLevel(dx * WL_SENSITIVITY, dy * WL_SENSITIVITY);
106
+ break;
107
+ }
108
+ case "pan":
109
+ this.#pan(dx, dy);
110
+ break;
111
+ case "zoom": {
112
+ const factor = 1.0 - dy * ZOOM_SENSITIVITY;
113
+ if (hasMethod(this.#viewer, "zoom")) {
114
+ this.#viewer.zoom(factor);
115
+ }
116
+ break;
117
+ }
118
+ case "crosshair":
119
+ // Crosshair requires world-space conversion — viewer-specific, not handled here
120
+ break;
121
+ case "scroll":
122
+ this.#scroll(dy > 0 ? 1 : dy < 0 ? -1 : 0);
123
+ break;
124
+ }
125
+ }
126
+ #pan(dx, dy) {
127
+ if (hasMethod(this.#viewer, "pan")) {
128
+ this.#viewer.pan(dx * PAN_SENSITIVITY, dy * PAN_SENSITIVITY);
129
+ }
130
+ }
131
+ #scroll(delta) {
132
+ const viewer = this.#viewer;
133
+ if (hasMethod(viewer, "scrollSlice") && !hasMethod(viewer, "orbit")) {
134
+ // StackViewer: scrollSlice(delta)
135
+ viewer.scrollSlice(delta);
136
+ }
137
+ else if (hasMethod(viewer, "scrollSlice")) {
138
+ // Viewer: scrollSlice(viewport, delta)
139
+ viewer.scrollSlice(this.#viewport, delta);
140
+ }
141
+ }
142
+ #scheduleRender() {
143
+ if (this.#renderScheduled)
144
+ return;
145
+ this.#renderScheduled = true;
146
+ requestAnimationFrame(() => {
147
+ this.#renderScheduled = false;
148
+ this.#viewer.render();
149
+ });
150
+ }
151
+ }
152
+ /**
153
+ * Input handler specialized for the 3D volume viewport.
154
+ *
155
+ * - **Left drag**: orbit (rotate camera)
156
+ * - **Middle drag**: pan
157
+ * - **Right drag**: zoom
158
+ * - **Wheel**: zoom
159
+ */
160
+ export class VolumeInputHandler {
161
+ #canvas;
162
+ #viewer;
163
+ #dragging = null;
164
+ #abortController = new AbortController();
165
+ #renderScheduled = false;
166
+ constructor(canvas, viewer) {
167
+ this.#canvas = canvas;
168
+ this.#viewer = viewer;
169
+ this.#bind();
170
+ }
171
+ destroy() {
172
+ this.#abortController.abort();
173
+ this.#dragging = null;
174
+ }
175
+ #bind() {
176
+ const signal = this.#abortController.signal;
177
+ const canvas = this.#canvas;
178
+ canvas.addEventListener("pointerdown", (e) => {
179
+ e.preventDefault();
180
+ canvas.setPointerCapture(e.pointerId);
181
+ this.#dragging = { startX: e.clientX, startY: e.clientY, button: e.button };
182
+ }, { signal });
183
+ canvas.addEventListener("pointermove", (e) => {
184
+ if (!this.#dragging)
185
+ return;
186
+ const dx = e.clientX - this.#dragging.startX;
187
+ const dy = e.clientY - this.#dragging.startY;
188
+ this.#dragging.startX = e.clientX;
189
+ this.#dragging.startY = e.clientY;
190
+ if (this.#dragging.button === 0) {
191
+ this.#viewer.orbit(dx, dy);
192
+ }
193
+ else if (this.#dragging.button === 1) {
194
+ this.#viewer.pan(dx * PAN_SENSITIVITY, dy * PAN_SENSITIVITY);
195
+ }
196
+ else if (this.#dragging.button === 2) {
197
+ const factor = 1.0 - dy * ZOOM_SENSITIVITY;
198
+ this.#viewer.zoom(factor);
199
+ }
200
+ this.#scheduleRender();
201
+ }, { signal });
202
+ canvas.addEventListener("pointerup", (e) => {
203
+ canvas.releasePointerCapture(e.pointerId);
204
+ this.#dragging = null;
205
+ }, { signal });
206
+ canvas.addEventListener("pointercancel", (e) => {
207
+ canvas.releasePointerCapture(e.pointerId);
208
+ this.#dragging = null;
209
+ }, { signal });
210
+ canvas.addEventListener("wheel", (e) => {
211
+ e.preventDefault();
212
+ const factor = 1.0 - e.deltaY * ZOOM_SENSITIVITY;
213
+ this.#viewer.zoom(factor);
214
+ this.#scheduleRender();
215
+ }, { signal, passive: false });
216
+ canvas.addEventListener("contextmenu", (e) => e.preventDefault(), { signal });
217
+ }
218
+ #scheduleRender() {
219
+ if (this.#renderScheduled)
220
+ return;
221
+ this.#renderScheduled = true;
222
+ requestAnimationFrame(() => {
223
+ this.#renderScheduled = false;
224
+ this.#viewer.render();
225
+ });
226
+ }
227
+ }
package/dist/loader.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { Viewer } from "./viewer.js";
1
+ import { StackViewer, Viewer } from "./viewer.js";
2
2
  import type { DICOMwebLoaderOptions, ProgressCallback, SeriesParams } from "./types.js";
3
3
  export declare class DICOMwebLoader {
4
4
  #private;
5
5
  constructor(options: DICOMwebLoaderOptions);
6
6
  onProgress(callback: ProgressCallback): void;
7
7
  abort(): void;
8
- loadSeries(viewer: Viewer, params: SeriesParams): Promise<void>;
8
+ loadSeries(viewer: Viewer | StackViewer, params: SeriesParams): Promise<void>;
9
9
  }
package/dist/loader.js CHANGED
@@ -17,6 +17,7 @@ export class DICOMwebLoader {
17
17
  this.#abortController = null;
18
18
  }
19
19
  async loadSeries(viewer, params) {
20
+ const renderDuringLoad = this.#options.renderDuringLoad !== false;
20
21
  const controller = new AbortController();
21
22
  this.#abortController = controller;
22
23
  const metadataUrl = [
@@ -37,7 +38,7 @@ export class DICOMwebLoader {
37
38
  let nextIndex = 0;
38
39
  let renderScheduled = false;
39
40
  const scheduleRender = () => {
40
- if (renderScheduled) {
41
+ if (!renderDuringLoad || renderScheduled) {
41
42
  return;
42
43
  }
43
44
  renderScheduled = true;
package/dist/presets.d.ts CHANGED
@@ -7,3 +7,13 @@ export declare const Presets: {
7
7
  readonly MR_ANGIO: "mr-angio";
8
8
  readonly MR_T2_BRAIN: "mr-t2-brain";
9
9
  };
10
+ /** Cornerstone3D-style preset name aliases. */
11
+ export declare const CornerstonePresets: {
12
+ readonly "CT-Bone": "ct-bone";
13
+ readonly "CT-Soft-Tissue": "ct-soft-tissue";
14
+ readonly "CT-Lung": "ct-lung";
15
+ readonly "CT-MIP": "ct-mip";
16
+ readonly "MR-Default": "mr-default";
17
+ readonly "MR-Angio": "mr-angio";
18
+ readonly "MR-T2-Brain": "mr-t2-brain";
19
+ };
package/dist/presets.js CHANGED
@@ -7,3 +7,13 @@ export const Presets = {
7
7
  MR_ANGIO: "mr-angio",
8
8
  MR_T2_BRAIN: "mr-t2-brain",
9
9
  };
10
+ /** Cornerstone3D-style preset name aliases. */
11
+ export const CornerstonePresets = {
12
+ "CT-Bone": "ct-bone",
13
+ "CT-Soft-Tissue": "ct-soft-tissue",
14
+ "CT-Lung": "ct-lung",
15
+ "CT-MIP": "ct-mip",
16
+ "MR-Default": "mr-default",
17
+ "MR-Angio": "mr-angio",
18
+ "MR-T2-Brain": "mr-t2-brain",
19
+ };
package/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type ViewportId = "axial" | "coronal" | "sagittal";
2
2
  export type BlendMode = "composite" | "mip" | "minip" | "average";
3
3
  export type ProjectionMode = "thin" | "mip" | "minip" | "average";
4
- export type VolumePreset = "ct-bone" | "ct-soft-tissue" | "ct-lung" | "ct-mip" | "mr-default" | "mr-angio" | "mr-t2-brain";
4
+ export type VolumePreset = "ct-bone" | "ct-soft-tissue" | "ct-lung" | "ct-mip" | "mr-default" | "mr-angio" | "mr-t2-brain" | "CT-Bone" | "CT-Soft-Tissue" | "CT-Lung" | "CT-MIP" | "MR-Default" | "MR-Angio" | "MR-T2-Brain";
5
5
  export type WasmSource = string | URL | Request | Response;
6
6
  export interface VolumeGeometry {
7
7
  dimensions: [number, number, number];
@@ -37,6 +37,11 @@ export interface ThickSlabOptions {
37
37
  thickness: number;
38
38
  projection: ProjectionMode;
39
39
  }
40
+ export interface StackViewerOptions {
41
+ canvas: HTMLCanvasElement;
42
+ wasmUrl?: WasmSource;
43
+ }
44
+ export type InputTool = "windowLevel" | "pan" | "zoom" | "crosshair" | "scroll";
40
45
  export interface SeriesParams {
41
46
  studyUid: string;
42
47
  seriesUid: string;
@@ -49,4 +54,6 @@ export interface DICOMwebLoaderOptions {
49
54
  concurrency?: number;
50
55
  decodeWorkers?: number;
51
56
  wasmUrl?: WasmSource;
57
+ /** When true (default), renders after each slice during loading. */
58
+ renderDuringLoad?: boolean;
52
59
  }
package/dist/viewer.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { BlendMode, ThickSlabOptions, ViewerOptions, ViewportId, VolumeGeometry, VolumePreset, WasmSource } from "./types.js";
1
+ import type { BlendMode, StackViewerOptions, ThickSlabOptions, ViewerOptions, ViewportId, VolumeGeometry, VolumePreset, WasmSource } from "./types.js";
2
2
  export declare function ensureDicomviewWasm(wasmUrl?: WasmSource): Promise<void>;
3
3
  export declare class Viewer {
4
4
  #private;
@@ -21,3 +21,25 @@ export declare class Viewer {
21
21
  reset(): void;
22
22
  destroy(): void;
23
23
  }
24
+ /**
25
+ * Single-canvas viewer for 2D stack browsing.
26
+ *
27
+ * Unlike {@link Viewer} which requires 4 canvases (axial, coronal, sagittal,
28
+ * volume), this only needs one canvas element. It renders a single orthogonal
29
+ * slice that users can scroll through.
30
+ */
31
+ export declare class StackViewer {
32
+ #private;
33
+ private constructor();
34
+ static create(options: StackViewerOptions): Promise<StackViewer>;
35
+ get loadingProgress(): number;
36
+ prepareVolume(geometry: VolumeGeometry): void;
37
+ feedDicomSlice(zIndex: number, bytes: ArrayBuffer | ArrayBufferView): void;
38
+ feedPixelSlice(zIndex: number, pixels: Int16Array | ArrayBuffer): void;
39
+ render(): void;
40
+ scrollSlice(delta: number): void;
41
+ setWindowLevel(center: number, width: number): void;
42
+ setSliceMode(viewport: ViewportId): void;
43
+ reset(): void;
44
+ destroy(): void;
45
+ }
package/dist/viewer.js CHANGED
@@ -1,4 +1,4 @@
1
- import initWasm, { Viewer as WasmViewer } from "../wasm/dicomview_wasm.js";
1
+ import initWasm, { Viewer as WasmViewer, StackViewer as WasmStackViewer, } from "../wasm/dicomview_wasm.js";
2
2
  const VIEWPORT_CODE = {
3
3
  axial: 0,
4
4
  coronal: 1,
@@ -106,3 +106,61 @@ function toUint8Array(value) {
106
106
  }
107
107
  return new Uint8Array(value);
108
108
  }
109
+ /**
110
+ * Single-canvas viewer for 2D stack browsing.
111
+ *
112
+ * Unlike {@link Viewer} which requires 4 canvases (axial, coronal, sagittal,
113
+ * volume), this only needs one canvas element. It renders a single orthogonal
114
+ * slice that users can scroll through.
115
+ */
116
+ export class StackViewer {
117
+ #inner;
118
+ constructor(inner) {
119
+ this.#inner = inner;
120
+ }
121
+ static async create(options) {
122
+ await ensureDicomviewWasm(options.wasmUrl);
123
+ const inner = await WasmStackViewer.create({ canvas: options.canvas });
124
+ return new StackViewer(inner);
125
+ }
126
+ get loadingProgress() {
127
+ return this.#requireInner().loading_progress();
128
+ }
129
+ prepareVolume(geometry) {
130
+ this.#requireInner().prepare_volume(geometry);
131
+ }
132
+ feedDicomSlice(zIndex, bytes) {
133
+ this.#requireInner().feed_dicom_slice(zIndex, toUint8Array(bytes));
134
+ }
135
+ feedPixelSlice(zIndex, pixels) {
136
+ const data = pixels instanceof Int16Array ? pixels : new Int16Array(pixels);
137
+ this.#requireInner().feed_pixel_slice(zIndex, data);
138
+ }
139
+ render() {
140
+ this.#requireInner().render();
141
+ }
142
+ scrollSlice(delta) {
143
+ this.#requireInner().scroll_slice(delta);
144
+ }
145
+ setWindowLevel(center, width) {
146
+ this.#requireInner().set_window_level(center, width);
147
+ }
148
+ setSliceMode(viewport) {
149
+ this.#requireInner().set_slice_mode(VIEWPORT_CODE[viewport]);
150
+ }
151
+ reset() {
152
+ this.#requireInner().reset();
153
+ }
154
+ destroy() {
155
+ if (this.#inner) {
156
+ this.#inner.destroy();
157
+ this.#inner = null;
158
+ }
159
+ }
160
+ #requireInner() {
161
+ if (!this.#inner) {
162
+ throw new Error("StackViewer has already been destroyed");
163
+ }
164
+ return this.#inner;
165
+ }
166
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knopkem/dicomview",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Rust/WASM medical imaging primitives for the web",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -1,6 +1,61 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
 
4
+ /**
5
+ * A single-canvas viewer for 2D stack browsing (like cornerstone's `StackViewport`).
6
+ *
7
+ * Unlike [`Viewer`] which requires 4 canvases, this only needs one.
8
+ */
9
+ export class StackViewer {
10
+ private constructor();
11
+ free(): void;
12
+ [Symbol.dispose](): void;
13
+ /**
14
+ * Creates a new stack viewer bound to a single canvas element.
15
+ */
16
+ static create(config: any): Promise<StackViewer>;
17
+ /**
18
+ * Explicitly destroys the stack viewer and releases its resources.
19
+ */
20
+ destroy(): void;
21
+ /**
22
+ * Decodes one DICOM Part 10 payload and uploads its frame data.
23
+ */
24
+ feed_dicom_slice(z_index: number, bytes: Uint8Array): void;
25
+ /**
26
+ * Uploads one already-decoded signed 16-bit slice.
27
+ */
28
+ feed_pixel_slice(z_index: number, pixels: Int16Array): void;
29
+ /**
30
+ * Returns the current loading progress in `[0, 1]`.
31
+ */
32
+ loading_progress(): number;
33
+ /**
34
+ * Prepares an empty volume with the provided geometry object.
35
+ */
36
+ prepare_volume(geometry: any): void;
37
+ /**
38
+ * Renders the single slice canvas.
39
+ */
40
+ render(): void;
41
+ /**
42
+ * Resets all viewport state back to defaults.
43
+ */
44
+ reset(): void;
45
+ /**
46
+ * Scrolls the slice along its normal.
47
+ */
48
+ scroll_slice(delta: number): void;
49
+ /**
50
+ * Switches which orthogonal plane is displayed.
51
+ */
52
+ set_slice_mode(viewport: number): void;
53
+ /**
54
+ * Applies one window/level setting to the viewport.
55
+ */
56
+ set_window_level(center: number, width: number): void;
57
+ }
58
+
4
59
  /**
5
60
  * One JS-visible viewer instance managing four canvases.
6
61
  */
@@ -120,7 +175,19 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
120
175
 
121
176
  export interface InitOutput {
122
177
  readonly memory: WebAssembly.Memory;
178
+ readonly __wbg_stackviewer_free: (a: number, b: number) => void;
123
179
  readonly __wbg_viewer_free: (a: number, b: number) => void;
180
+ readonly stackviewer_create: (a: any) => any;
181
+ readonly stackviewer_destroy: (a: number) => void;
182
+ readonly stackviewer_feed_dicom_slice: (a: number, b: number, c: number, d: number) => [number, number];
183
+ readonly stackviewer_feed_pixel_slice: (a: number, b: number, c: number, d: number) => [number, number];
184
+ readonly stackviewer_loading_progress: (a: number) => number;
185
+ readonly stackviewer_prepare_volume: (a: number, b: any) => [number, number];
186
+ readonly stackviewer_render: (a: number) => [number, number];
187
+ readonly stackviewer_reset: (a: number) => [number, number];
188
+ readonly stackviewer_scroll_slice: (a: number, b: number) => [number, number];
189
+ readonly stackviewer_set_slice_mode: (a: number, b: number) => [number, number];
190
+ readonly stackviewer_set_window_level: (a: number, b: number, c: number) => [number, number];
124
191
  readonly viewer_create: (a: any) => any;
125
192
  readonly viewer_destroy: (a: number) => void;
126
193
  readonly viewer_feed_dicom_slice: (a: number, b: number, c: number, d: number) => [number, number];
@@ -1,5 +1,140 @@
1
1
  /* @ts-self-types="./dicomview_wasm.d.ts" */
2
2
 
3
+ /**
4
+ * A single-canvas viewer for 2D stack browsing (like cornerstone's `StackViewport`).
5
+ *
6
+ * Unlike [`Viewer`] which requires 4 canvases, this only needs one.
7
+ */
8
+ export class StackViewer {
9
+ static __wrap(ptr) {
10
+ ptr = ptr >>> 0;
11
+ const obj = Object.create(StackViewer.prototype);
12
+ obj.__wbg_ptr = ptr;
13
+ StackViewerFinalization.register(obj, obj.__wbg_ptr, obj);
14
+ return obj;
15
+ }
16
+ __destroy_into_raw() {
17
+ const ptr = this.__wbg_ptr;
18
+ this.__wbg_ptr = 0;
19
+ StackViewerFinalization.unregister(this);
20
+ return ptr;
21
+ }
22
+ free() {
23
+ const ptr = this.__destroy_into_raw();
24
+ wasm.__wbg_stackviewer_free(ptr, 0);
25
+ }
26
+ /**
27
+ * Creates a new stack viewer bound to a single canvas element.
28
+ * @param {any} config
29
+ * @returns {Promise<StackViewer>}
30
+ */
31
+ static create(config) {
32
+ const ret = wasm.stackviewer_create(config);
33
+ return ret;
34
+ }
35
+ /**
36
+ * Explicitly destroys the stack viewer and releases its resources.
37
+ */
38
+ destroy() {
39
+ const ptr = this.__destroy_into_raw();
40
+ wasm.stackviewer_destroy(ptr);
41
+ }
42
+ /**
43
+ * Decodes one DICOM Part 10 payload and uploads its frame data.
44
+ * @param {number} z_index
45
+ * @param {Uint8Array} bytes
46
+ */
47
+ feed_dicom_slice(z_index, bytes) {
48
+ const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
49
+ const len0 = WASM_VECTOR_LEN;
50
+ const ret = wasm.stackviewer_feed_dicom_slice(this.__wbg_ptr, z_index, ptr0, len0);
51
+ if (ret[1]) {
52
+ throw takeFromExternrefTable0(ret[0]);
53
+ }
54
+ }
55
+ /**
56
+ * Uploads one already-decoded signed 16-bit slice.
57
+ * @param {number} z_index
58
+ * @param {Int16Array} pixels
59
+ */
60
+ feed_pixel_slice(z_index, pixels) {
61
+ const ptr0 = passArray16ToWasm0(pixels, wasm.__wbindgen_malloc);
62
+ const len0 = WASM_VECTOR_LEN;
63
+ const ret = wasm.stackviewer_feed_pixel_slice(this.__wbg_ptr, z_index, ptr0, len0);
64
+ if (ret[1]) {
65
+ throw takeFromExternrefTable0(ret[0]);
66
+ }
67
+ }
68
+ /**
69
+ * Returns the current loading progress in `[0, 1]`.
70
+ * @returns {number}
71
+ */
72
+ loading_progress() {
73
+ const ret = wasm.stackviewer_loading_progress(this.__wbg_ptr);
74
+ return ret;
75
+ }
76
+ /**
77
+ * Prepares an empty volume with the provided geometry object.
78
+ * @param {any} geometry
79
+ */
80
+ prepare_volume(geometry) {
81
+ const ret = wasm.stackviewer_prepare_volume(this.__wbg_ptr, geometry);
82
+ if (ret[1]) {
83
+ throw takeFromExternrefTable0(ret[0]);
84
+ }
85
+ }
86
+ /**
87
+ * Renders the single slice canvas.
88
+ */
89
+ render() {
90
+ const ret = wasm.stackviewer_render(this.__wbg_ptr);
91
+ if (ret[1]) {
92
+ throw takeFromExternrefTable0(ret[0]);
93
+ }
94
+ }
95
+ /**
96
+ * Resets all viewport state back to defaults.
97
+ */
98
+ reset() {
99
+ const ret = wasm.stackviewer_reset(this.__wbg_ptr);
100
+ if (ret[1]) {
101
+ throw takeFromExternrefTable0(ret[0]);
102
+ }
103
+ }
104
+ /**
105
+ * Scrolls the slice along its normal.
106
+ * @param {number} delta
107
+ */
108
+ scroll_slice(delta) {
109
+ const ret = wasm.stackviewer_scroll_slice(this.__wbg_ptr, delta);
110
+ if (ret[1]) {
111
+ throw takeFromExternrefTable0(ret[0]);
112
+ }
113
+ }
114
+ /**
115
+ * Switches which orthogonal plane is displayed.
116
+ * @param {number} viewport
117
+ */
118
+ set_slice_mode(viewport) {
119
+ const ret = wasm.stackviewer_set_slice_mode(this.__wbg_ptr, viewport);
120
+ if (ret[1]) {
121
+ throw takeFromExternrefTable0(ret[0]);
122
+ }
123
+ }
124
+ /**
125
+ * Applies one window/level setting to the viewport.
126
+ * @param {number} center
127
+ * @param {number} width
128
+ */
129
+ set_window_level(center, width) {
130
+ const ret = wasm.stackviewer_set_window_level(this.__wbg_ptr, center, width);
131
+ if (ret[1]) {
132
+ throw takeFromExternrefTable0(ret[0]);
133
+ }
134
+ }
135
+ }
136
+ if (Symbol.dispose) StackViewer.prototype[Symbol.dispose] = StackViewer.prototype.free;
137
+
3
138
  /**
4
139
  * One JS-visible viewer instance managing four canvases.
5
140
  */
@@ -1185,6 +1320,10 @@ function __wbg_get_imports() {
1185
1320
  getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
1186
1321
  getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
1187
1322
  },
1323
+ __wbg_stackviewer_new: function(arg0) {
1324
+ const ret = StackViewer.__wrap(arg0);
1325
+ return ret;
1326
+ },
1188
1327
  __wbg_static_accessor_GLOBAL_THIS_a1248013d790bf5f: function() {
1189
1328
  const ret = typeof globalThis === 'undefined' ? null : globalThis;
1190
1329
  return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
@@ -1235,12 +1374,12 @@ function __wbg_get_imports() {
1235
1374
  arg0.writeTexture(arg1, getArrayU8FromWasm0(arg2, arg3), arg4, arg5);
1236
1375
  }, arguments); },
1237
1376
  __wbindgen_cast_0000000000000001: function(arg0, arg1) {
1238
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 172, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
1377
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 177, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
1239
1378
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h4c19ef90e3a2449e);
1240
1379
  return ret;
1241
1380
  },
1242
1381
  __wbindgen_cast_0000000000000002: function(arg0, arg1) {
1243
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 690, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
1382
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 695, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
1244
1383
  const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h77f088fde8c66f5c);
1245
1384
  return ret;
1246
1385
  },
@@ -1362,6 +1501,9 @@ const __wbindgen_enum_GpuVertexStepMode = ["vertex", "instance"];
1362
1501
 
1363
1502
 
1364
1503
  const __wbindgen_enum_RequestMode = ["same-origin", "no-cors", "cors", "navigate"];
1504
+ const StackViewerFinalization = (typeof FinalizationRegistry === 'undefined')
1505
+ ? { register: () => {}, unregister: () => {} }
1506
+ : new FinalizationRegistry(ptr => wasm.__wbg_stackviewer_free(ptr >>> 0, 1));
1365
1507
  const ViewerFinalization = (typeof FinalizationRegistry === 'undefined')
1366
1508
  ? { register: () => {}, unregister: () => {} }
1367
1509
  : new FinalizationRegistry(ptr => wasm.__wbg_viewer_free(ptr >>> 0, 1));
Binary file
@@ -1,7 +1,19 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
  export const memory: WebAssembly.Memory;
4
+ export const __wbg_stackviewer_free: (a: number, b: number) => void;
4
5
  export const __wbg_viewer_free: (a: number, b: number) => void;
6
+ export const stackviewer_create: (a: any) => any;
7
+ export const stackviewer_destroy: (a: number) => void;
8
+ export const stackviewer_feed_dicom_slice: (a: number, b: number, c: number, d: number) => [number, number];
9
+ export const stackviewer_feed_pixel_slice: (a: number, b: number, c: number, d: number) => [number, number];
10
+ export const stackviewer_loading_progress: (a: number) => number;
11
+ export const stackviewer_prepare_volume: (a: number, b: any) => [number, number];
12
+ export const stackviewer_render: (a: number) => [number, number];
13
+ export const stackviewer_reset: (a: number) => [number, number];
14
+ export const stackviewer_scroll_slice: (a: number, b: number) => [number, number];
15
+ export const stackviewer_set_slice_mode: (a: number, b: number) => [number, number];
16
+ export const stackviewer_set_window_level: (a: number, b: number, c: number) => [number, number];
5
17
  export const viewer_create: (a: any) => any;
6
18
  export const viewer_destroy: (a: number) => void;
7
19
  export const viewer_feed_dicom_slice: (a: number, b: number, c: number, d: number) => [number, number];