NextJsWithCornerstone3D4Version#4
Conversation
📝 WalkthroughWalkthroughThis PR upgrades the project to Next.js 16, React 19, and Cornerstone3D 4.22.13, migrates to Tailwind v4 CSS imports and ESLint flat config, adds Turbopack configuration, and refactors app pages to extract Cornerstone3D initialization logic into separate dynamically-loaded Viewer components. ChangesUpgrade and Refactoring
🎯 4 (Complex) | ⏱️ ~75 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed: one or more packages not found in the registry. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/polyseg/Viewer.tsx`:
- Around line 194-203: The "Convert to 3D" button can be clicked before async
initialization completes causing
segmentation.addSegmentationRepresentations(viewportId3, ...) to run on
uninitialized state; add a boolean ready flag (e.g., is3DReady or
segmentationReady) that is set true only after your async effect successfully
registers segmentation and the 3D viewport, use that flag to disable the button
(disabled={!is3DReady}) and also guard the onClick handler to no-op if not
ready; set the flag to false on init start and only true on success (and keep
false on failure) so calls to segmentation.addSegmentationRepresentations,
viewportId3, and csToolsEnums.SegmentationRepresentations.Surface are only
invoked when ready.
- Around line 46-190: The setup() async block should be made resilient: surround
its body with try/catch/finally, ensuring you reset running.current = false in
the finally block so a failed init can be retried; catch should log or surface
the error. Add a useEffect cleanup function that runs on unmount to tear down
Cornerstone resources created during setup: call renderingEngine.destroy() (or
equivalent) if renderingEngine was created, and remove/destroy toolGroup1 and
toolGroup2 from ToolGroupManager (or call any ToolGroupManager.destroy/remove
methods) to avoid leaking tool groups; store renderingEngine, toolGroup1,
toolGroup2 in refs so the cleanup can access them. Also ensure any UI actions
that operate on viewports/segmentations check running.current or a "ready" flag
before executing.
In `@app/Viewer.tsx`:
- Around line 20-81: Wrap the async init in a try/finally so running.current is
reset on any error (set running.current = true before try, set to false in
finally) and add a cleanup function returned from the useEffect that tears down
the created resources: call renderingEngine.destroy() (and undo enableElement if
needed) and stop/cleanup the volume/loader if applicable; locate the async setup
in Viewer.tsx (the setup function inside useEffect), the running ref
(running.current), the RenderingEngine instance (new
RenderingEngine(renderingEngineId) / renderingEngine.enableElement), and the
created volume (volume.load / volumeId) and ensure those are cleaned up in the
returned cleanup callback and on init failure.
In `@package.json`:
- Line 9: Update the package.json "lint" script to call ESLint directly instead
of the removed "next lint"; replace the value of the "lint" script key so it
invokes the eslint CLI against the project source (include the relevant file
globs/extensions like .js, .jsx, .ts, .tsx and any desired flags such as --ext
and optionally --fix or --max-warnings=0) so running npm run lint works under
Next.js 16.
In `@patches/`@cornerstonejs+dicom-image-loader+4.22.13.patch:
- Around line 126-135: The patch unconditionally disables HTJ2K by making
initialize() a no-op and making decodeAsync() always throw, which will cause
runtime failures for HTJ2K-encoded studies; update the module so it either
proactively rejects unsupported transfer syntaxes with a clear error or restores
codec support. Modify decodeAsync (and/or the codec selection path that calls
initialize) to inspect the incoming transferSyntaxUID or codec identifier and:
if it is HTJ2K (e.g., the HTJ2K/JP2K transferSyntax UID), return a rejected
Promise with a descriptive Error stating that HTJ2K is unsupported in this build
(including the UID); otherwise, preserve existing behavior for other codecs (or
re-wire to call the real `@cornerstonejs/codec-openjph` decoder if you choose to
restore support). Ensure initialize still advertises availability state
consistent with decodeAsync (so callers can preflight availability).
- Around line 8-10: The worker is being instantiated without the ESM flag which
breaks module-based workers; restore the module worker contract by adding back
the `{ type: 'module' }` option when constructing the Worker in the workerFn
that loads `decodeImageFrameWorker.js` (i.e., ensure new Worker(new
URL('./decodeImageFrameWorker.js', import.meta.url), { type: 'module' }) is
used), and instead address bundler/asset resolution (or produce a non-ESM build)
rather than changing the runtime worker invocation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4022501a-aeaa-4895-bb25-9a519959c4e6
⛔ Files ignored due to path filters (2)
package-lock.jsonis excluded by!**/package-lock.jsonyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (13)
.eslintrc.jsonapp/Viewer.tsxapp/globals.cssapp/page.tsxapp/polyseg/Viewer.tsxapp/polyseg/page.tsxeslint.config.mjspackage.jsonpatches/@cornerstonejs+dicom-image-loader+4.22.13.patchpatches/@cornerstonejs+tools+4.22.13.patchpostcss.config.mjstailwind.config.tstsconfig.json
💤 Files with no reviewable changes (2)
- .eslintrc.json
- tailwind.config.ts
| useEffect(() => { | ||
| const setup = async () => { | ||
| if (running.current) { | ||
| return | ||
| } | ||
| running.current = true | ||
|
|
||
| await csRenderInit() | ||
| await csToolsInit() | ||
| dicomImageLoaderInit({ maxWebWorkers: 1 }) | ||
| // Add tools to Cornerstone3D | ||
| addTool(BrushTool) | ||
|
|
||
| // Define tool groups to add the segmentation display tool to | ||
| toolGroup1 = ToolGroupManager.createToolGroup(toolGroupId) | ||
| toolGroup2 = ToolGroupManager.createToolGroup(toolGroupId2) | ||
|
|
||
| // Segmentation Tools | ||
| toolGroup1.addToolInstance("SphereBrush", BrushTool.toolName, { | ||
| activeStrategy: "FILL_INSIDE_SPHERE", | ||
| }) | ||
| toolGroup1.addToolInstance("EraserBrush", BrushTool.toolName, { | ||
| activeStrategy: "ERASE_INSIDE_SPHERE", | ||
| }) | ||
|
|
||
| toolGroup1.setToolActive("SphereBrush", { | ||
| bindings: [ | ||
| { | ||
| mouseButton: MouseBindings.Primary, // Middle Click | ||
| }, | ||
| ], | ||
| }) | ||
|
|
||
| // Get Cornerstone imageIds for the source data and fetch metadata into RAM | ||
| const imageIds = await createImageIdsAndCacheMetaData({ | ||
| StudyInstanceUID: | ||
| "1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463", | ||
| SeriesInstanceUID: | ||
| "1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561", | ||
| wadoRsRoot: "https://d14fa38qiwhyfd.cloudfront.net/dicomweb", | ||
| }) | ||
|
|
||
| // Define a volume in memory | ||
| const volume = await volumeLoader.createAndCacheVolume(volumeId, { | ||
| imageIds, | ||
| }) | ||
|
|
||
| // Instantiate a rendering engine | ||
| const renderingEngineId = "myRenderingEngine" | ||
| renderingEngine = new RenderingEngine(renderingEngineId) | ||
|
|
||
| const viewportInputArray = [ | ||
| { | ||
| viewportId: viewportId1, | ||
| type: ViewportType.ORTHOGRAPHIC, | ||
| element: elementRef1.current, | ||
| defaultOptions: { | ||
| orientation: Enums.OrientationAxis.AXIAL, | ||
| }, | ||
| }, | ||
| { | ||
| viewportId: viewportId2, | ||
| type: ViewportType.ORTHOGRAPHIC, | ||
| element: elementRef2.current, | ||
| defaultOptions: { | ||
| orientation: Enums.OrientationAxis.SAGITTAL, | ||
| }, | ||
| }, | ||
| { | ||
| viewportId: viewportId3, | ||
| type: ViewportType.VOLUME_3D, | ||
| element: elementRef3.current, | ||
| defaultOptions: { | ||
| background: CONSTANTS.BACKGROUND_COLORS.slicer3D, | ||
| }, | ||
| }, | ||
| ] | ||
|
|
||
| renderingEngine.setViewports(viewportInputArray) | ||
|
|
||
| toolGroup1.addViewport(viewportId1, renderingEngineId) | ||
| toolGroup1.addViewport(viewportId2, renderingEngineId) | ||
| toolGroup2.addViewport(viewportId3, renderingEngineId) | ||
|
|
||
| // Set the volume to load | ||
| await volume.load() | ||
|
|
||
| // Set volumes on the viewports | ||
| await setVolumesForViewports( | ||
| renderingEngine, | ||
| [{ volumeId }], | ||
| [viewportId1, viewportId2, viewportId3] | ||
| ) | ||
|
|
||
| // set the anatomy at first invisible | ||
| const volumeActor = renderingEngine | ||
| .getViewport(viewportId3) | ||
| .getDefaultActor().actor as Types.VolumeActor | ||
| utilities.applyPreset( | ||
| volumeActor, | ||
| CONSTANTS.VIEWPORT_PRESETS.find((preset) => preset.name === "CT-Bone") | ||
| ) | ||
| volumeActor.setVisibility(false) | ||
|
|
||
| // Add some segmentations based on the source data volume | ||
| // Create a segmentation of the same resolution as the source data | ||
| await volumeLoader.createAndCacheDerivedLabelmapVolume(volumeId, { | ||
| volumeId: segmentationId, | ||
| }) | ||
|
|
||
| // Add the segmentations to state | ||
| await segmentation.addSegmentations([ | ||
| { | ||
| segmentationId, | ||
| representation: { | ||
| // The type of segmentation | ||
| type: csToolsEnums.SegmentationRepresentations.Labelmap, | ||
| // The actual segmentation data, in the case of labelmap this is a | ||
| // reference to the source volume of the segmentation. | ||
| data: { | ||
| volumeId: segmentationId, | ||
| }, | ||
| }, | ||
| }, | ||
| ]) | ||
|
|
||
| // Add the segmentation representation to the viewports | ||
| const segmentationRepresentation = { | ||
| segmentationId, | ||
| type: csToolsEnums.SegmentationRepresentations.Labelmap, | ||
| } | ||
|
|
||
| await segmentation.addLabelmapRepresentationToViewportMap({ | ||
| [viewportId1]: [segmentationRepresentation], | ||
| [viewportId2]: [segmentationRepresentation], | ||
| }) | ||
|
|
||
| // Render the image | ||
| renderingEngine.render() | ||
| } | ||
|
|
||
| setup() | ||
|
|
||
| // Create a stack viewport | ||
| }, [elementRef1, elementRef2, elementRef3, running]) |
There was a problem hiding this comment.
Add init error recovery and Cornerstone teardown to prevent stale/non-retryable viewer state
In app/polyseg/Viewer.tsx the useEffect async setup sets running.current = true, performs multiple awaited Cornerstone init/cache/render steps, but provides no try/finally to clear running.current on failure and no effect cleanup to destroy the created RenderingEngine/tool groups. This can leak resources and leave the viewer stuck after transient init errors or unmount/remount (Fast Refresh/navigation).
Wrap setup in try/catch/finally (reset running.current in finally) and add useEffect cleanup that destroys the RenderingEngine and any created ToolGroups (or avoid long-lived module singletons by keeping them in component refs). Also gate any UI actions that depend on the viewports/segmentations so they can’t run until setup finishes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/polyseg/Viewer.tsx` around lines 46 - 190, The setup() async block should
be made resilient: surround its body with try/catch/finally, ensuring you reset
running.current = false in the finally block so a failed init can be retried;
catch should log or surface the error. Add a useEffect cleanup function that
runs on unmount to tear down Cornerstone resources created during setup: call
renderingEngine.destroy() (or equivalent) if renderingEngine was created, and
remove/destroy toolGroup1 and toolGroup2 from ToolGroupManager (or call any
ToolGroupManager.destroy/remove methods) to avoid leaking tool groups; store
renderingEngine, toolGroup1, toolGroup2 in refs so the cleanup can access them.
Also ensure any UI actions that operate on viewports/segmentations check
running.current or a "ready" flag before executing.
| <button | ||
| onClick={() => { | ||
| // add the 3d representation to the 3d toolgroup | ||
| segmentation.addSegmentationRepresentations(viewportId3, [ | ||
| { | ||
| segmentationId, | ||
| type: csToolsEnums.SegmentationRepresentations.Surface, | ||
| }, | ||
| ]) | ||
| }} |
There was a problem hiding this comment.
Disable “Convert to 3D” until initialization finishes.
The button is active on first paint, but the segmentation state and 3D viewport are only registered later in the async effect. A quick click can call addSegmentationRepresentations against uninitialized state and break the main demo flow. Track a ready flag and disable the button until setup succeeds.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/polyseg/Viewer.tsx` around lines 194 - 203, The "Convert to 3D" button
can be clicked before async initialization completes causing
segmentation.addSegmentationRepresentations(viewportId3, ...) to run on
uninitialized state; add a boolean ready flag (e.g., is3DReady or
segmentationReady) that is set true only after your async effect successfully
registers segmentation and the 3D viewport, use that flag to disable the button
(disabled={!is3DReady}) and also guard the onClick handler to no-op if not
ready; set the flag to false on init start and only true on success (and keep
false on failure) so calls to segmentation.addSegmentationRepresentations,
viewportId3, and csToolsEnums.SegmentationRepresentations.Surface are only
invoked when ready.
| useEffect(() => { | ||
| const setup = async () => { | ||
| if (running.current) { | ||
| return | ||
| } | ||
| running.current = true | ||
|
|
||
| await csRenderInit() | ||
| await csToolsInit() | ||
| dicomImageLoaderInit({ maxWebWorkers: 1 }) | ||
|
|
||
| // Get Cornerstone imageIds and fetch metadata into RAM | ||
| const imageIds = await createImageIdsAndCacheMetaData({ | ||
| StudyInstanceUID: | ||
| "1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463", | ||
| SeriesInstanceUID: | ||
| "1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561", | ||
| wadoRsRoot: "https://d14fa38qiwhyfd.cloudfront.net/dicomweb", | ||
| }) | ||
|
|
||
| // Instantiate a rendering engine | ||
| const renderingEngineId = "myRenderingEngine" | ||
| const renderingEngine = new RenderingEngine(renderingEngineId) | ||
| const viewportId = "CT" | ||
|
|
||
| const viewportInput = { | ||
| viewportId, | ||
| type: Enums.ViewportType.ORTHOGRAPHIC, | ||
| element: elementRef.current, | ||
| defaultOptions: { | ||
| orientation: Enums.OrientationAxis.SAGITTAL, | ||
| }, | ||
| } | ||
|
|
||
| renderingEngine.enableElement(viewportInput) | ||
|
|
||
| // Get the stack viewport that was created | ||
| const viewport = renderingEngine.getViewport( | ||
| viewportId | ||
| ) as Types.IVolumeViewport | ||
|
|
||
| // Define a volume in memory | ||
| const volumeId = "streamingImageVolume" | ||
| const volume = await volumeLoader.createAndCacheVolume(volumeId, { | ||
| imageIds, | ||
| }) | ||
|
|
||
| // Set the volume to load | ||
| // @ts-ignore | ||
| volume.load() | ||
|
|
||
| // Set the volume on the viewport and it's default properties | ||
| await viewport.setVolumes([{ volumeId }]) | ||
|
|
||
| // Render the image | ||
| viewport.render() | ||
| } | ||
|
|
||
| setup() | ||
|
|
||
| // Create a stack viewport | ||
| }, [elementRef, running]) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and inspect app/Viewer.tsx around the referenced lines
ls -la
sed -n '1,200p' app/Viewer.tsxRepository: cornerstonejs/nextjs-cornerstone3d
Length of output: 3937
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for any existing cleanup/destroy patterns for RenderingEngine
rg -n "RenderingEngine|destroy\(\)|enableElement\(" -S app libRepository: cornerstonejs/nextjs-cornerstone3d
Length of output: 551
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,200p' app/polyseg/Viewer.tsx
echo "---- destroy() usages ----"
rg -n "destroy\(" -S app libRepository: cornerstonejs/nextjs-cornerstone3d
Length of output: 6148
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- app/Viewer.tsx: useEffect cleanup / return () ----"
rg -n "useEffect\\(|return \\(\\) =>|return \\(\\s*\\)" -S app/Viewer.tsx
echo "---- app/polyseg/Viewer.tsx: effect cleanup patterns ----"
rg -n "useEffect\\(|return \\(\\) =>|return \\(\\s*\\)|destroy\\(" -S app/polyseg/Viewer.tsx
echo "---- repo-wide destroy() search (should confirm none) ----"
rg -n "destroy\\(" -S app libRepository: cornerstonejs/nextjs-cornerstone3d
Length of output: 297
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "myRenderingEngine" -S app libRepository: cornerstonejs/nextjs-cornerstone3d
Length of output: 226
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the RenderingEngine implementation in `@cornerstonejs/core`
rg -n "class RenderingEngine" node_modules/@cornerstonejs/core -S || true
rg -n "RenderingEngine.*destroy\\(|destroy\\(\\).*RenderingEngine|destroy\\(" node_modules/@cornerstonejs/core -S | head -n 50Repository: cornerstonejs/nextjs-cornerstone3d
Length of output: 2532
Return cleanup from the effect + reset running.current on init failure to avoid leaks and “stuck” init.
In app/Viewer.tsx, running.current is set to true and never cleared if any awaited init step throws (no try/catch), so the viewer can get stuck in the “already running” state. Also, the effect doesn’t return a cleanup, so the created RenderingEngine is never torn down on unmount—yet RenderingEngine in @cornerstonejs/core provides a destroy() API and the repo currently has no destroy() calls.
♻️ Proposed fix
useEffect(() => {
+ let cancelled = false
+ let renderingEngine: RenderingEngine | undefined
+
const setup = async () => {
- if (running.current) {
+ if (running.current || !elementRef.current) {
return
}
+
running.current = true
-
- await csRenderInit()
- await csToolsInit()
- dicomImageLoaderInit({ maxWebWorkers: 1 })
-
- // Get Cornerstone imageIds and fetch metadata into RAM
- const imageIds = await createImageIdsAndCacheMetaData({
- StudyInstanceUID:
- "1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463",
- SeriesInstanceUID:
- "1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561",
- wadoRsRoot: "https://d14fa38qiwhyfd.cloudfront.net/dicomweb",
- })
-
- // Instantiate a rendering engine
- const renderingEngineId = "myRenderingEngine"
- const renderingEngine = new RenderingEngine(renderingEngineId)
- const viewportId = "CT"
-
- const viewportInput = {
- viewportId,
- type: Enums.ViewportType.ORTHOGRAPHIC,
- element: elementRef.current,
- defaultOptions: {
- orientation: Enums.OrientationAxis.SAGITTAL,
- },
- }
-
- renderingEngine.enableElement(viewportInput)
-
- // Get the stack viewport that was created
- const viewport = renderingEngine.getViewport(
- viewportId
- ) as Types.IVolumeViewport
-
- // Define a volume in memory
- const volumeId = "streamingImageVolume"
- const volume = await volumeLoader.createAndCacheVolume(volumeId, {
- imageIds,
- })
-
- // Set the volume to load
- // `@ts-ignore`
- volume.load()
-
- // Set the volume on the viewport and it's default properties
- await viewport.setVolumes([{ volumeId }])
-
- // Render the image
- viewport.render()
+
+ try {
+ await csRenderInit()
+ await csToolsInit()
+ dicomImageLoaderInit({ maxWebWorkers: 1 })
+
+ const imageIds = await createImageIdsAndCacheMetaData({
+ StudyInstanceUID:
+ "1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463",
+ SeriesInstanceUID:
+ "1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561",
+ wadoRsRoot: "https://d14fa38qiwhyfd.cloudfront.net/dicomweb",
+ })
+
+ const renderingEngineId = "myRenderingEngine"
+ renderingEngine = new RenderingEngine(renderingEngineId)
+ const viewportId = "CT"
+
+ renderingEngine.enableElement({
+ viewportId,
+ type: Enums.ViewportType.ORTHOGRAPHIC,
+ element: elementRef.current,
+ defaultOptions: {
+ orientation: Enums.OrientationAxis.SAGITTAL,
+ },
+ })
+
+ const viewport = renderingEngine.getViewport(
+ viewportId
+ ) as Types.IVolumeViewport
+
+ const volumeId = "streamingImageVolume"
+ const volume = await volumeLoader.createAndCacheVolume(volumeId, {
+ imageIds,
+ })
+
+ // `@ts-ignore`
+ volume.load()
+
+ if (cancelled) {
+ return
+ }
+
+ await viewport.setVolumes([{ volumeId }])
+
+ if (!cancelled) {
+ viewport.render()
+ }
+ } catch (error) {
+ running.current = false
+ throw error
+ }
}
- setup()
+ void setup()
- // Create a stack viewport
- }, [elementRef, running])
+ return () => {
+ cancelled = true
+ running.current = false
+ renderingEngine?.destroy()
+ }
+ }, [])🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/Viewer.tsx` around lines 20 - 81, Wrap the async init in a try/finally so
running.current is reset on any error (set running.current = true before try,
set to false in finally) and add a cleanup function returned from the useEffect
that tears down the created resources: call renderingEngine.destroy() (and undo
enableElement if needed) and stop/cleanup the volume/loader if applicable;
locate the async setup in Viewer.tsx (the setup function inside useEffect), the
running ref (running.current), the RenderingEngine instance (new
RenderingEngine(renderingEngineId) / renderingEngine.enableElement), and the
created volume (volume.load / volumeId) and ensure those are cleaned up in the
returned cleanup callback and on init failure.
| const workerFn = () => { | ||
| - const instance = new Worker(new URL('./decodeImageFrameWorker.js', import.meta.url), { type: 'module' }); | ||
| + const instance = new Worker(new URL('./decodeImageFrameWorker.js', import.meta.url)); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Inspect the patch content
sed -n '1,160p' "patches/@cornerstonejs+dicom-image-loader+4.22.13.patch"
# 2) Locate any references to decodeImageFrameWorker and worker creation
rg -n "decodeImageFrameWorker\.js|new Worker\(" -S . || true
# 3) If the patched output exists in-repo, inspect the worker file content
# (common locations: dist/esm, dist/cjs, or similar)
fd -a "decodeImageFrameWorker\.js" . || true
# 4) If present, check whether decodeImageFrameWorker.js contains ESM imports/exports
# and whether any other build keeps { type: 'module' }
worker_file="$(fd -a "decodeImageFrameWorker\.js" . | head -n 1 || true)"
if [[ -n "${worker_file}" ]]; then
echo "---- inspecting: ${worker_file} ----"
# Print first ~120 lines to spot imports/exports and module usage
sed -n '1,140p' "$worker_file"
fi
# 5) Search for HTJ2K removal/stub throw in the codebase
rg -n "HTJ2K|tj2k|htj2k|throw new Error|stub" -S . || trueRepository: cornerstonejs/nextjs-cornerstone3d
Length of output: 1990
🌐 Web query:
@cornerstonejs/dicom-image-loader@4.22.13 decodeImageFrameWorker.js new Worker type: 'module'
💡 Result:
The code snippet new Worker(new URL('./decodeImageFrameWorker.js', import.meta.url), { type: 'module' }) is used within @cornerstonejs/dicom-image-loader (specifically in the init function) to instantiate a Web Worker as an ES module [1][2][3]. This implementation relies on the host environment (such as Vite or Webpack) to resolve the URL and bundle the worker file correctly [2][3]. Common technical issues and solutions related to this implementation include: 1. Build Tool Integration: In many modern frameworks (like Vite), this pattern can cause issues where the worker file is not correctly located, served, or bundled, leading to 404 errors or module resolution failures [2][4][3]. 2. Dependency Optimization: Users often resolve these errors by configuring their build tool (e.g., vite.config.ts) to: - Exclude @cornerstonejs/dicom-image-loader from optimizeDeps [2][4][3]. - Include dicom-parser in optimizeDeps [2][3]. - Use plugins like @originjs/vite-plugin-commonjs to handle interoperability issues between CommonJS dependencies and ESM [2][4][3]. 3. Customization: Recent updates have introduced the ability to customize the webWorkerUrl via the init configuration, allowing developers to manually define how the worker is instantiated if the default behavior fails in their specific build environment [5]. If you are encountering errors related to this worker, ensure your build configuration aligns with the recommended settings for Cornerstone3D, as the library expects the build environment to handle the import.meta.url resolution for the worker script [1][3].
Citations:
- 1: https://github.com/cornerstonejs/cornerstone3D/blob/main/packages/dicomImageLoader/src/init.ts
- 2: [Bug] decodeImageFrameWorker.js file does not exist, volumeLoader.loadVolume promise not resolving cornerstone3D#1538
- 3: [Bug] 2.0: while initializing DICOM image loader: invalid URL at workerFn cornerstone3D#1532
- 4: [Bug] decodeImageFrameWorker fails to load Vue+Vite+Nuxt cornerstone3D#1876
- 5: feat(dicom-image-loader): Add ability to customise path to web worker and codec wasm files cornerstone3D#1982
🌐 Web query:
cornerstonejs dicom-image-loader decodeImageFrameWorker type module import.meta.url
💡 Result:
In Cornerstone3D (@cornerstonejs/dicom-image-loader), the use of new URL('./decodeImageFrameWorker.js', import.meta.url) is part of the library's internal mechanism for initializing web workers as ES modules [1]. This specific syntax relies on the bundler (e.g., Vite, Webpack) to resolve the worker file path relative to the module's location at build time [2][3]. Common issues and resolutions related to this implementation include: 1. Bundler Configuration: Developers frequently encounter issues where the bundler fails to correctly resolve the worker path or misinterprets the module type [4][5]. Official documentation recommends specific configurations for popular frameworks [2]. For example, when using Vite, you must often exclude @cornerstonejs/dicom-image-loader from dependency optimization (optimizeDeps) to ensure the web worker is handled correctly [5][2]. 2. Customization: Due to the challenges of static path resolution in different project environments, newer versions of the library (introduced in PR #1982) provide the ability to customize the path to the web worker [6]. You can override the default worker loading behavior by providing a custom webWorkerUrl function in the init configuration [6]: init({ webWorkerUrl: => { return new Worker( new URL('../path-to-your-worker.ts', import.meta.url), { type: 'module' }); }, //... other configuration }); 3. Environment Awareness: The use of import.meta.url can behave unexpectedly in certain environments (e.g., some SSR frameworks or specific dev-server configurations), sometimes resulting in undefined values [4]. If you are experiencing runtime errors where the worker fails to load, ensure your bundler is configured to support ES module workers and that any necessary WASM assets are being correctly copied to your public/assets directory [6][2].
Citations:
- 1: https://github.com/cornerstonejs/cornerstone3D/blob/main/packages/dicomImageLoader/src/init.ts
- 2: https://cornerstonejs.org/docs/getting-started/vue-angular-react-etc
- 3: https://cornerstonejs.org/docs/migration-guides/2x/dicom-image-loader/
- 4: [Bug] 2.0: while initializing DICOM image loader: invalid URL at workerFn cornerstone3D#1532
- 5: [Bug] decodeImageFrameWorker fails to load Vue+Vite+Nuxt cornerstone3D#1876
- 6: feat(dicom-image-loader): Add ability to customise path to web worker and codec wasm files cornerstone3D#1982
🌐 Web query:
site:github.com cornerstonjs dicom-image-loader decodeImageFrameWorker.js
💡 Result:
The decodeImageFrameWorker.js file is a critical component of the @cornerstonejs/dicom-image-loader library in the Cornerstone3D ecosystem [1]. It serves as the entry point for a dedicated Web Worker that performs CPU-intensive tasks—specifically, the decompression and decoding of DICOM image frames—off the main browser thread [2]. By utilizing this worker, Cornerstone3D ensures that high-latency operations, such as decompressing complex formats (e.g., JPEG 2000, JPEG-LS), do not block the UI thread, thereby maintaining smooth application performance [2]. Key aspects of decodeImageFrameWorker.js include: Functionality: It executes the 'decodeTask', which takes raw DICOM pixel data and applies the necessary codec logic to generate displayable image frames [3][2]. Implementation: The loader initializes this worker via the library's WebWorkerManager [1]. In recent versions, this is often handled by creating a new Worker instance using a dynamic import: const workerFn = => { const instance = new Worker( new URL('./decodeImageFrameWorker.js', import.meta.url), { type: 'module' }); return instance; }; Common Configuration & Build Issues: Because this file is a separate module, developers frequently encounter build-related issues when using modern bundlers like Vite, Webpack, or Angular's build system [4][5]. If the build tool does not correctly resolve or include the worker file in the output assets, the application will fail to load the worker, resulting in pending promises or runtime errors [4][5]. To resolve these issues, developers often need to: 1. Exclude the dicom-image-loader package from the bundler's dependency optimizer (e.g., in Vite) [4]. 2. Manually configure the path to the worker or override the webWorkerFn to ensure the file is served correctly by the application's dev server or build pipeline [6]. If you are experiencing issues with the worker failing to load, ensure that your build configuration specifically accounts for the worker module and that any required WebAssembly (.wasm) codec files are accessible at the specified paths [6][4].
Citations:
- 1: https://github.com/cornerstonejs/cornerstone3D/blob/main/packages/dicomImageLoader/src/init.ts
- 2: https://github.com/cornerstonejs/cornerstoneWADOImageLoader/blob/HEAD/docs/WebWorkers.md
- 3: cornerstonejs/cornerstone3D@e4e2be3
- 4: [Bug] decodeImageFrameWorker fails to load Vue+Vite+Nuxt cornerstone3D#1876
- 5: [Bug] decodeImageFrameWorker.js file does not exist, volumeLoader.loadVolume promise not resolving cornerstone3D#1538
- 6: feat(dicom-image-loader): Add ability to customise path to web worker and codec wasm files cornerstone3D#1982
🌐 Web query:
@cornerstonejs/dicom-image-loader 4.22.13 patches module worker type module
💡 Result:
In @cornerstonejs/dicom-image-loader, the use of Web Workers with the { type: 'module' } option is a core part of its architecture for offloading CPU-intensive image decompression [1][2][3]. The library utilizes a WebWorkerManager, which internally initializes workers using the ES module syntax [1][2]. Specifically, the initialization logic often employs the following pattern to create a worker as a module: const workerFn = => { const instance = new Worker( new URL('./decodeImageFrameWorker.js', import.meta.url), { type: 'module' }); return instance; }; This implementation allows the worker to leverage ESM features, which is essential for the library's integration with modern bundlers like Vite [2][4]. Regarding version 4.22.13: - Version 4.22.13 is a specific release within the 4.x series of @cornerstonejs/dicom-image-loader [5]. - The library does not typically list "patches for module worker type module" as a standalone feature in changelogs for minor version bumps, as this architecture has been the established standard since the transition to the 2.x API [6][2]. - If you are encountering issues related to the worker's type: 'module' configuration (such as 404 errors during asset loading or bundler resolution issues), these are often related to path resolution—specifically how the bundler resolves the location of the worker file relative to the entry point [7][4]. - Users can override the default worker loading behavior by providing a custom webWorkerUrl factory function in the init configuration, which allows for explicit path handling for different build environments (e.g., Angular or Vite) [8]. For most production use cases, ensuring your bundler is configured to handle.worker.js files and correctly emitting them to your output directory is critical for resolving errors related to the worker's module type [2][4].
Citations:
- 1: https://github.com/cornerstonejs/cornerstone3D/blob/main/packages/dicomImageLoader/src/init.ts
- 2: https://cornerstonejs.org/docs/migration-guides/2x/dicom-image-loader/
- 3: https://github.com/cornerstonejs/cornerstoneWADOImageLoader/blob/master/docs/WebWorkers.md
- 4: [Bug] 2.0: while initializing DICOM image loader: invalid URL at workerFn cornerstone3D#1532
- 5: https://www.npmjs.com/package/@cornerstonejs/dicom-image-loader?activeTab=code
- 6: https://github.com/cornerstonejs/cornerstone3D/blob/c8ce18c7/packages/dicomImageLoader/CHANGELOG.md
- 7: https://community.ohif.org/t/resolving-import-and-path-issues-for-dicom-image-loader-in-liveview-with-cornerstone3d/2055
- 8: feat(dicom-image-loader): Add ability to customise path to web worker and codec wasm files cornerstone3D#1982
Don’t remove { type: "module" } from the ESM decode worker creation.
@cornerstonejs/dicom-image-loader initializes decodeImageFrameWorker.js as an ES-module worker (new URL(..., import.meta.url), plus { type: 'module' }). Dropping { type: 'module' } can make the browser interpret the worker as a classic script and fail at startup when ESM syntax is present. Fix the worker asset/bundling resolution (or target a non-ESM build) instead of changing the worker contract in dist/esm.
Relevant snippet
const workerFn = () => {
- const instance = new Worker(new URL('./decodeImageFrameWorker.js', import.meta.url), { type: 'module' });
+ const instance = new Worker(new URL('./decodeImageFrameWorker.js', import.meta.url));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@patches/`@cornerstonejs+dicom-image-loader+4.22.13.patch around lines 8 - 10,
The worker is being instantiated without the ESM flag which breaks module-based
workers; restore the module worker contract by adding back the `{ type: 'module'
}` option when constructing the Worker in the workerFn that loads
`decodeImageFrameWorker.js` (i.e., ensure new Worker(new
URL('./decodeImageFrameWorker.js', import.meta.url), { type: 'module' }) is
used), and instead address bundler/asset resolution (or produce a non-ESM build)
rather than changing the runtime worker invocation.
|
Now i remove patches |
There was a problem hiding this comment.
♻️ Duplicate comments (1)
package.json (1)
9-9:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
eslintcommand missing target path.The
lintscript runseslintwithout specifying files to lint. While ESLint 9+ will lint the current directory by default when no files are specified, explicitly providing a path is clearer and avoids ambiguity. The previous review suggestedeslint .which is the recommended pattern for Next.js 16 migration.Proposed fix
- "lint": "eslint" + "lint": "eslint ."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@package.json` at line 9, The package.json "lint" script currently runs "eslint" with no target; update the "lint" script value (the "lint" npm script) to explicitly target the project root (e.g., change the command to "eslint .") so ESLint runs against the repository files consistently and matches the recommended Next.js migration pattern.
🧹 Nitpick comments (1)
eslint.config.mjs (1)
1-6: 💤 Low valueTypo in import variable name.
The import name
nextCoreWebVitlsis missing the 'a' in "Vitals". While this doesn't affect functionality, it reduces readability.Proposed fix
-import nextCoreWebVitls from "eslint-config-next/core-web-vitals" +import nextCoreWebVitals from "eslint-config-next/core-web-vitals" const eslintConfig = [ - ...nextCoreWebVitls, + ...nextCoreWebVitals, { ignores: [".next/**","node_modules/**", "next-env.d.ts"], - },] + }, +]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@eslint.config.mjs` around lines 1 - 6, The import binding nextCoreWebVitls has a typo; rename it to nextCoreWebVitals everywhere it's used (both in the import statement and in the spread within eslintConfig) so the symbol matches the intended word "Vitals" and improves readability; ensure you update the identifier in the import line and the spread ...nextCoreWebVitals in the eslintConfig array.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@package.json`:
- Line 9: The package.json "lint" script currently runs "eslint" with no target;
update the "lint" script value (the "lint" npm script) to explicitly target the
project root (e.g., change the command to "eslint .") so ESLint runs against the
repository files consistently and matches the recommended Next.js migration
pattern.
---
Nitpick comments:
In `@eslint.config.mjs`:
- Around line 1-6: The import binding nextCoreWebVitls has a typo; rename it to
nextCoreWebVitals everywhere it's used (both in the import statement and in the
spread within eslintConfig) so the symbol matches the intended word "Vitals" and
improves readability; ensure you update the identifier in the import line and
the spread ...nextCoreWebVitals in the eslintConfig array.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 79a3449e-0d3e-48e8-8b5f-3f3f2fa956c5
⛔ Files ignored due to path filters (2)
package-lock.jsonis excluded by!**/package-lock.jsonyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (4)
empty.tseslint.config.mjsnext.config.mjspackage.json
✅ Files skipped from review due to trivial changes (1)
- empty.ts
Modernizes the template to Next.js 16 + Cornerstone3D 4.x and the current React / Tailwind / ESLint majors. Adds
next/dynamic({ ssr: false })wrappers so cornerstone never runson the server, and pins the bundler to webpack (Next 16 ships
Turbopack as the default, but it can't handle the codec
require('fs')calls or the@icr/polyseg-wasmURL import).Stack updates
next: 14 → 16.2.7 — Turbopack default overridden via--webpackindev/buildscripts@cornerstonejs/core/tools/dicom-image-loader:2.19.11 → 4.22.13
react/react-dom: 18 → 19tailwindcss: 3 → 4 (CSS-first config;@tailwindcss/postcssplugin;tailwind.config.tsdropped)eslint: 8 → 9 +eslint-config-next14 → 16 (flatconfig via
FlatCompat;.eslintrc.json→eslint.config.mjs)typescript: 5 → 6;dcmjsanddicomweb-clientbumpedto current minors
Page structure
app/page.tsxandapp/polyseg/page.tsxare thin"use client"wrappers that load siblingViewer.tsxfiles vianext/dynamic(..., { ssr: false }). Fixes the SSR/prerendercrash on Next 16 where top-level cornerstone imports touch
browser APIs.
viewport.setVolumes(...)beforeviewport.render()(the v3+ signature is async).
d14fa38qiwhyfd.cloudfront.netendpoint; the olderd3t6nz73ql33txhost no longer resolves.node_modules patches (via
patch-package)Webpack-dev's
eval(__webpack_require__.ts("..."))chunk formatcan't be loaded as an ES module worker, and one specific
Emscripten codec (~125 KB openjph) won't parse inside that eval
wrapper. Three minimal patches land in
patches/:@cornerstonejs/dicom-image-loader→init.js: drop{ type: 'module' }from the worker constructor.@cornerstonejs/tools→utilities/registerComputeWorker.js:same drop.
@cornerstonejs/dicom-image-loader→shared/decoders/decodeHTJ2K.js: stub out, removing the@cornerstonejs/codec-openjph/wasmjsimport (HTJ2K decoding nowthrows if invoked — not used by the demo dataset). Restore
upstream when a clean fix for the openjph dev-chunk parse is
found.
"postinstall": "patch-package"keeps these applied acrossreinstalls. Full rationale + refresh steps are in
CLAUDE.md.Test plan
npm install(verify patches apply cleanly)npm run buildsucceeds and prerenders 5 routesnpx tsc --noEmitcleannpm run dev, load/— sagittal CT volume renders, noconsole errors
/polyseg— axial + sagittal + 3D viewports render;brush works; "Convert to 3D" produces a surface
Summary by CodeRabbit
Chores
Refactor