Skip to content

Commit cd47693

Browse files
committed
fix: fix SSR issues and optimize DICOM viewer performance
- Convert Cornerstone3D static imports to dynamic imports to avoid SSR errors - Update DICOM server URL to working endpoint - Replace volume viewer with faster stack viewport for local files - Increase web worker threads for better decoding performance - Add local DICOM example with commented remote WADO-RS reference
1 parent df10c93 commit cd47693

5 files changed

Lines changed: 123 additions & 3440 deletions

File tree

app/page.tsx

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,7 @@
11
"use client"
22

33
import { useEffect, useRef } from "react"
4-
import createImageIdsAndCacheMetaData from "../lib/createImageIdsAndCacheMetaData"
5-
import {
6-
RenderingEngine,
7-
Enums,
8-
type Types,
9-
volumeLoader,
10-
} from "@cornerstonejs/core"
11-
import { init as csRenderInit } from "@cornerstonejs/core"
12-
import { init as csToolsInit } from "@cornerstonejs/tools"
13-
import { init as dicomImageLoaderInit } from "@cornerstonejs/dicom-image-loader"
14-
4+
import type { Types } from "@cornerstonejs/core"
155

166
function App() {
177
const elementRef = useRef<HTMLDivElement>(null)
@@ -24,26 +14,47 @@ function App() {
2414
}
2515
running.current = true
2616

17+
// Dynamically import all Cornerstone modules to avoid SSR issues
18+
const [
19+
{ RenderingEngine, Enums, init: csRenderInit },
20+
{ init: csToolsInit },
21+
dicomImageLoader,
22+
] = await Promise.all([
23+
import("@cornerstonejs/core"),
24+
import("@cornerstonejs/tools"),
25+
import("@cornerstonejs/dicom-image-loader"),
26+
])
27+
2728
await csRenderInit()
2829
await csToolsInit()
29-
dicomImageLoaderInit({ maxWebWorkers: 1 })
30-
31-
// Get Cornerstone imageIds and fetch metadata into RAM
30+
dicomImageLoader.init({ maxWebWorkers: navigator.hardwareConcurrency || 4 })
31+
32+
// ===== EXAMPLE 1: Local DICOM file =====
33+
// Use local DICOM file from public folder (fast, no network latency)
34+
const imageId = "wadouri:/dicom-examples/CT0012.fragmented_no_bot_jpeg_baseline.51.dcm"
35+
36+
// ===== EXAMPLE 2: Remote DICOM server (WADO-RS) =====
37+
// Uncomment the code below to fetch from a remote DICOM web server
38+
// Note: This requires more setup and loads a full volume (slower)
39+
/*
40+
const { default: createImageIdsAndCacheMetaData } = await import("../lib/createImageIdsAndCacheMetaData")
41+
const { volumeLoader } = await import("@cornerstonejs/core")
42+
3243
const imageIds = await createImageIdsAndCacheMetaData({
3344
StudyInstanceUID:
3445
"1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463",
3546
SeriesInstanceUID:
3647
"1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561",
37-
wadoRsRoot: "https://d3t6nz73ql33tx.cloudfront.net/dicomweb",
48+
wadoRsRoot: "https://d14fa38qiwhyfd.cloudfront.net/dicomweb",
3849
})
3950
40-
// Instantiate a rendering engine
41-
const renderingEngineId = "myRenderingEngine"
42-
const renderingEngine = new RenderingEngine(renderingEngineId)
43-
const viewportId = "CT"
51+
const volumeId = "streamingImageVolume"
52+
const volume = await volumeLoader.createAndCacheVolume(volumeId, {
53+
imageIds,
54+
})
4455
4556
const viewportInput = {
46-
viewportId,
57+
viewportId: "CT_VOLUME",
4758
type: Enums.ViewportType.ORTHOGRAPHIC,
4859
element: elementRef.current,
4960
defaultOptions: {
@@ -52,33 +63,39 @@ function App() {
5263
}
5364
5465
renderingEngine.enableElement(viewportInput)
66+
const viewport = renderingEngine.getViewport("CT_VOLUME") as Types.IVolumeViewport
67+
68+
volume.load()
69+
viewport.setVolumes([{ volumeId }])
70+
viewport.render()
71+
return
72+
*/
5573

56-
// Get the stack viewport that was created
57-
const viewport = renderingEngine.getViewport(
58-
viewportId
59-
) as Types.IVolumeViewport
74+
// Instantiate a rendering engine
75+
const renderingEngineId = "myRenderingEngine"
76+
const renderingEngine = new RenderingEngine(renderingEngineId)
77+
const viewportId = "CT_STACK"
6078

61-
// Define a volume in memory
62-
const volumeId = "streamingImageVolume"
63-
const volume = await volumeLoader.createAndCacheVolume(volumeId, {
64-
imageIds,
65-
})
79+
const viewportInput = {
80+
viewportId,
81+
type: Enums.ViewportType.STACK,
82+
element: elementRef.current,
83+
}
6684

67-
// Set the volume to load
68-
// @ts-ignore
69-
volume.load()
85+
renderingEngine.enableElement(viewportInput)
7086

71-
// Set the volume on the viewport and it's default properties
72-
viewport.setVolumes([{ volumeId }])
87+
// Get the stack viewport that was created
88+
const viewport = renderingEngine.getViewport(viewportId) as Types.IStackViewport
89+
90+
// Set the stack of images on the viewport
91+
await viewport.setStack([imageId])
7392

7493
// Render the image
7594
viewport.render()
7695
}
7796

7897
setup()
79-
80-
// Create a stack viewport
81-
}, [elementRef, running])
98+
}, [])
8299

83100
return (
84101
<div

app/polyseg/page.tsx

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,12 @@
11
"use client"
22

3-
import { useEffect, useRef } from "react"
4-
import createImageIdsAndCacheMetaData from "../../lib/createImageIdsAndCacheMetaData"
5-
import {
6-
RenderingEngine,
7-
Enums,
8-
CONSTANTS,
9-
type Types,
10-
volumeLoader,
11-
setVolumesForViewports,
12-
utilities,
13-
} from "@cornerstonejs/core"
14-
import { init as csRenderInit } from "@cornerstonejs/core"
15-
import {
16-
init as csToolsInit,
17-
addTool,
18-
BrushTool,
19-
ToolGroupManager,
20-
segmentation,
21-
Enums as csToolsEnums,
22-
} from "@cornerstonejs/tools"
23-
import { init as dicomImageLoaderInit } from "@cornerstonejs/dicom-image-loader"
24-
25-
const { ViewportType } = Enums
26-
const { MouseBindings } = csToolsEnums
3+
import { useEffect, useRef, useCallback, useState } from "react"
4+
import type { Types } from "@cornerstonejs/core"
275

286
const toolGroupId = "ToolGroup_MPR"
297
const toolGroupId2 = "ToolGroup_3D"
30-
let toolGroup1, toolGroup2
31-
let renderingEngine
8+
let toolGroup1: any, toolGroup2: any
9+
let renderingEngine: any
3210
// Create the viewports
3311
const viewportId1 = "CT_AXIAL"
3412
const viewportId2 = "CT_SAGITTAL"
@@ -42,6 +20,19 @@ function App() {
4220
const elementRef2 = useRef<HTMLDivElement>(null)
4321
const elementRef3 = useRef<HTMLDivElement>(null)
4422
const running = useRef(false)
23+
const [isReady, setIsReady] = useState(false)
24+
25+
const handleConvertTo3D = useCallback(async () => {
26+
const cornerstoneTools = await import("@cornerstonejs/tools")
27+
const { segmentation, Enums: csToolsEnums } = cornerstoneTools
28+
29+
segmentation.addSegmentationRepresentations(viewportId3, [
30+
{
31+
segmentationId,
32+
type: csToolsEnums.SegmentationRepresentations.Surface,
33+
},
34+
])
35+
}, [])
4536

4637
useEffect(() => {
4738
const setup = async () => {
@@ -50,6 +41,43 @@ function App() {
5041
}
5142
running.current = true
5243

44+
// Dynamically import all Cornerstone modules to avoid SSR issues
45+
const [
46+
cornerstoneCore,
47+
cornerstoneTools,
48+
dicomImageLoader,
49+
{ default: createImageIdsAndCacheMetaData },
50+
] = await Promise.all([
51+
import("@cornerstonejs/core"),
52+
import("@cornerstonejs/tools"),
53+
import("@cornerstonejs/dicom-image-loader"),
54+
import("../../lib/createImageIdsAndCacheMetaData"),
55+
])
56+
57+
const {
58+
RenderingEngine,
59+
Enums,
60+
CONSTANTS,
61+
volumeLoader,
62+
setVolumesForViewports,
63+
utilities,
64+
init: csRenderInit,
65+
} = cornerstoneCore
66+
67+
const {
68+
init: csToolsInit,
69+
addTool,
70+
BrushTool,
71+
ToolGroupManager,
72+
segmentation,
73+
Enums: csToolsEnums,
74+
} = cornerstoneTools
75+
76+
const { init: dicomImageLoaderInit } = dicomImageLoader
77+
78+
const { ViewportType } = Enums
79+
const { MouseBindings } = csToolsEnums
80+
5381
await csRenderInit()
5482
await csToolsInit()
5583
dicomImageLoaderInit({ maxWebWorkers: 1 })
@@ -182,6 +210,7 @@ function App() {
182210

183211
// Render the image
184212
renderingEngine.render()
213+
setIsReady(true)
185214
}
186215

187216
setup()
@@ -192,15 +221,8 @@ function App() {
192221
return (
193222
<div>
194223
<button
195-
onClick={() => {
196-
// add the 3d representation to the 3d toolgroup
197-
segmentation.addSegmentationRepresentations(viewportId3, [
198-
{
199-
segmentationId,
200-
type: csToolsEnums.SegmentationRepresentations.Surface,
201-
},
202-
])
203-
}}
224+
onClick={handleConvertTo3D}
225+
disabled={!isReady}
204226
>
205227
Convert to 3D
206228
</button>

bun.lockb

-254 KB
Binary file not shown.

lib/createImageIdsAndCacheMetaData.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { api } from "dicomweb-client"
2-
import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader"
32

43
/**
54
/**
@@ -31,7 +30,7 @@ export default async function createImageIdsAndCacheMetaData({
3130
client ||
3231
new api.DICOMwebClient({ url: wadoRsRoot as string, singlepart: true })
3332
const instances = await client.retrieveSeriesMetadata(studySearchOptions)
34-
const imageIds = instances.map((instanceMetaData) => {
33+
const imageIdsWithMetadata = instances.map((instanceMetaData) => {
3534
const SeriesInstanceUID = instanceMetaData[SERIES_INSTANCE_UID].Value[0]
3635
const SOPInstanceUIDToUse =
3736
SOPInstanceUID || instanceMetaData[SOP_INSTANCE_UID].Value[0]
@@ -49,12 +48,20 @@ export default async function createImageIdsAndCacheMetaData({
4948
SOPInstanceUIDToUse +
5049
"/frames/1"
5150

52-
cornerstoneDICOMImageLoader.wadors.metaDataManager.add(
51+
return { imageId, instanceMetaData }
52+
})
53+
54+
// Dynamically import the DICOM image loader to avoid SSR issues
55+
const cornerstoneDICOMImageLoader = await import("@cornerstonejs/dicom-image-loader")
56+
57+
for (const { imageId, instanceMetaData } of imageIdsWithMetadata) {
58+
cornerstoneDICOMImageLoader.default.wadors.metaDataManager.add(
5359
imageId,
5460
instanceMetaData
5561
)
56-
return imageId
57-
})
62+
}
63+
64+
const imageIds = imageIdsWithMetadata.map(({ imageId }) => imageId)
5865

5966
// we don't want to add non-pet
6067
// Note: for 99% of scanners SUV calculation is consistent bw slices

0 commit comments

Comments
 (0)