Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: Add JPEG XL decoding #1834

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 31 additions & 28 deletions bun.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"lockfileVersion": 0,
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "root",
Expand Down Expand Up @@ -138,7 +138,7 @@
},
"packages/adapters": {
"name": "@cornerstonejs/adapters",
"version": "2.19.6",
"version": "2.19.7",
"dependencies": {
"@babel/runtime-corejs2": "^7.17.8",
"buffer": "^6.0.3",
Expand All @@ -147,13 +147,13 @@
"ndarray": "^1.0.19",
},
"peerDependencies": {
"@cornerstonejs/core": "packages/core",
"@cornerstonejs/tools": "packages/tools",
"@cornerstonejs/core": "^2.19.7",
"@cornerstonejs/tools": "^2.19.7",
},
},
"packages/ai": {
"name": "@cornerstonejs/ai",
"version": "2.19.6",
"version": "2.19.7",
"dependencies": {
"@babel/runtime-corejs2": "^7.17.8",
"buffer": "^6.0.3",
Expand All @@ -165,13 +165,13 @@
"onnxruntime-web": "1.17.1",
},
"peerDependencies": {
"@cornerstonejs/core": "packages/core",
"@cornerstonejs/tools": "packages/tools",
"@cornerstonejs/core": "^2.19.7",
"@cornerstonejs/tools": "^2.19.7",
},
},
"packages/core": {
"name": "@cornerstonejs/core",
"version": "2.19.6",
"version": "2.19.7",
"dependencies": {
"@kitware/vtk.js": "32.9.0",
"comlink": "^4.4.1",
Expand All @@ -180,10 +180,11 @@
},
"packages/dicomImageLoader": {
"name": "@cornerstonejs/dicom-image-loader",
"version": "2.19.6",
"version": "2.19.7",
"dependencies": {
"@cornerstonejs/codec-charls": "^1.2.3",
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-libjxl": "file:../../cornerstonejs-codec-libjxl-0.0.1.tgz",
"@cornerstonejs/codec-openjpeg": "^1.2.2",
"@cornerstonejs/codec-openjph": "^2.4.5",
"comlink": "^4.4.1",
Expand All @@ -192,19 +193,19 @@
"uuid": "^9.0.0",
},
"peerDependencies": {
"@cornerstonejs/core": "packages/core",
"@cornerstonejs/core": "^2.19.7",
"dicom-parser": "^1.8.9",
},
},
"packages/docs": {
"name": "docs",
"version": "2.1.10",
"dependencies": {
"@cornerstonejs/adapters": "packages/adapters",
"@cornerstonejs/core": "packages/core",
"@cornerstonejs/dicom-image-loader": "packages/dicomImageLoader",
"@cornerstonejs/nifti-volume-loader": "packages/nifti-volume-loader",
"@cornerstonejs/tools": "packages/tools",
"@cornerstonejs/adapters": "^2.19.7",
"@cornerstonejs/core": "^2.19.7",
"@cornerstonejs/dicom-image-loader": "^2.19.7",
"@cornerstonejs/nifti-volume-loader": "^2.19.7",
"@cornerstonejs/tools": "^2.19.7",
"@docusaurus/core": "3.6.3",
"@docusaurus/faster": "3.6.3",
"@docusaurus/module-type-aliases": "3.6.3",
Expand Down Expand Up @@ -242,17 +243,17 @@
},
"packages/nifti-volume-loader": {
"name": "@cornerstonejs/nifti-volume-loader",
"version": "2.19.6",
"version": "2.19.7",
"dependencies": {
"nifti-reader-js": "^0.6.8",
},
"peerDependencies": {
"@cornerstonejs/core": "packages/core",
"@cornerstonejs/core": "^2.19.7",
},
},
"packages/tools": {
"name": "@cornerstonejs/tools",
"version": "2.19.6",
"version": "2.19.7",
"dependencies": {
"@types/offscreencanvas": "2019.7.3",
"comlink": "^4.4.1",
Expand All @@ -262,7 +263,7 @@
"canvas": "^2.11.2",
},
"peerDependencies": {
"@cornerstonejs/core": "packages/core",
"@cornerstonejs/core": "^2.19.7",
"@kitware/vtk.js": "32.9.0",
"@types/d3-array": "^3.0.4",
"@types/d3-interpolate": "^3.0.1",
Expand Down Expand Up @@ -583,27 +584,29 @@

"@colors/colors": ["@colors/[email protected]", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="],

"@cornerstonejs/adapters": ["@cornerstonejs/adapters@workspace:packages/adapters", { "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", "dcmjs": "^0.29.8", "gl-matrix": "^3.4.3", "ndarray": "^1.0.19" }, "peerDependencies": { "@cornerstonejs/core": "packages/core", "@cornerstonejs/tools": "packages/tools" } }],
"@cornerstonejs/adapters": ["@cornerstonejs/adapters@workspace:packages/adapters"],

"@cornerstonejs/ai": ["@cornerstonejs/ai@workspace:packages/ai", { "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", "dcmjs": "^0.29.8", "gl-matrix": "^3.4.3", "lodash.clonedeep": "^4.5.0", "ndarray": "^1.0.19", "onnxruntime-common": "1.17.1", "onnxruntime-web": "1.17.1" }, "peerDependencies": { "@cornerstonejs/core": "packages/core", "@cornerstonejs/tools": "packages/tools" } }],
"@cornerstonejs/ai": ["@cornerstonejs/ai@workspace:packages/ai"],

"@cornerstonejs/calculate-suv": ["@cornerstonejs/[email protected]", "", {}, "sha512-2SwVJKzC1DzyxdxJtCht9dhTND2GFjLwhhkDyyC7vJq5tIgbhxgPk1CSwovO1pxmoybAXzjOxnaubllxLgoT+w=="],

"@cornerstonejs/codec-charls": ["@cornerstonejs/[email protected]", "", {}, "sha512-qKUe6DN0dnGzhhfZLYhH9UZacMcudjxcaLXCrpxJImT/M/PQvZCT2rllu6VGJbWKJWG+dMVV2zmmleZcdJ7/cA=="],

"@cornerstonejs/codec-libjpeg-turbo-8bit": ["@cornerstonejs/[email protected]", "", {}, "sha512-aAUMK2958YNpOb/7G6e2/aG7hExTiFTASlMt/v90XA0pRHdWiNg5ny4S5SAju0FbIw4zcMnR0qfY+yW3VG2ivg=="],

"@cornerstonejs/codec-libjxl": ["@cornerstonejs/codec-libjxl@../../cornerstonejs-codec-libjxl-0.0.1.tgz", {}],

"@cornerstonejs/codec-openjpeg": ["@cornerstonejs/[email protected]", "", {}, "sha512-UT2su6xZZnCPSuWf2ldzKa/2+guQ7BGgfBSKqxanggwJHh48gZqIAzekmsLyJHMMK5YDK+ti+fzvVJhBS3Xi/g=="],

"@cornerstonejs/codec-openjph": ["@cornerstonejs/[email protected]", "", {}, "sha512-qvP4q4JDib7mi9r7LqKOwqz7YZ8gjtDX4ZCezeYf8+eb7MBXCz5uXAMeVF3yz9Axw4XiIMdB/pqXkm8tqCl13w=="],

"@cornerstonejs/core": ["@cornerstonejs/core@workspace:packages/core", { "dependencies": { "@kitware/vtk.js": "32.9.0", "comlink": "^4.4.1", "gl-matrix": "^3.4.3" } }],
"@cornerstonejs/core": ["@cornerstonejs/core@workspace:packages/core"],

"@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@workspace:packages/dicomImageLoader", { "dependencies": { "@cornerstonejs/codec-charls": "^1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.5", "comlink": "^4.4.1", "jpeg-lossless-decoder-js": "^2.1.0", "pako": "^2.0.4", "uuid": "^9.0.0" }, "peerDependencies": { "@cornerstonejs/core": "packages/core", "dicom-parser": "^1.8.9" } }],
"@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@workspace:packages/dicomImageLoader"],

"@cornerstonejs/nifti-volume-loader": ["@cornerstonejs/nifti-volume-loader@workspace:packages/nifti-volume-loader", { "dependencies": { "nifti-reader-js": "^0.6.8" }, "peerDependencies": { "@cornerstonejs/core": "packages/core" } }],
"@cornerstonejs/nifti-volume-loader": ["@cornerstonejs/nifti-volume-loader@workspace:packages/nifti-volume-loader"],

"@cornerstonejs/tools": ["@cornerstonejs/tools@workspace:packages/tools", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "^4.4.1", "lodash.get": "^4.4.2" }, "devDependencies": { "canvas": "^2.11.2" }, "peerDependencies": { "@cornerstonejs/core": "packages/core", "@kitware/vtk.js": "32.9.0", "@types/d3-array": "^3.0.4", "@types/d3-interpolate": "^3.0.1", "d3-array": "^3.2.3", "d3-interpolate": "^3.0.1", "gl-matrix": "^3.4.3" } }],
"@cornerstonejs/tools": ["@cornerstonejs/tools@workspace:packages/tools"],

"@cspotcode/source-map-support": ["@cspotcode/[email protected]", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],

Expand Down Expand Up @@ -811,9 +814,9 @@

"@eslint/js": ["@eslint/[email protected]", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],

"@externals/dicom-microscopy-viewer": ["@externals/dicom-microscopy-viewer@workspace:addOns/externals/dicom-microscopy-viewer", { "dependencies": { "dicom-microscopy-viewer": "^0.46.1" } }],
"@externals/dicom-microscopy-viewer": ["@externals/dicom-microscopy-viewer@workspace:addOns/externals/dicom-microscopy-viewer"],

"@externals/polyseg-wasm": ["@externals/polyseg-wasm@workspace:addOns/externals/polyseg-wasm", { "dependencies": { "@icr/polyseg-wasm": "^0.4.0", "@itk-wasm/morphological-contour-interpolation": "1.0.1" } }],
"@externals/polyseg-wasm": ["@externals/polyseg-wasm@workspace:addOns/externals/polyseg-wasm"],

"@fastify/accept-negotiator": ["@fastify/[email protected]", "", {}, "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ=="],

Expand Down Expand Up @@ -2397,7 +2400,7 @@

"docdash": ["[email protected]", "", {}, "sha512-IYZbgYthPTspgqYeciRJNPhSwL51yer7HAwDXhF5p+H7mTDbPvY3PCk/QDjNxdPCpWkaJVFC4t7iCNB/t9E5Kw=="],

"docs": ["docs@workspace:packages/docs", { "dependencies": { "@cornerstonejs/adapters": "packages/adapters", "@cornerstonejs/core": "packages/core", "@cornerstonejs/dicom-image-loader": "packages/dicomImageLoader", "@cornerstonejs/nifti-volume-loader": "packages/nifti-volume-loader", "@cornerstonejs/tools": "packages/tools", "@docusaurus/core": "3.6.3", "@docusaurus/faster": "3.6.3", "@docusaurus/module-type-aliases": "3.6.3", "@docusaurus/plugin-google-gtag": "3.6.3", "@docusaurus/preset-classic": "3.6.3", "@kitware/vtk.js": "32.9.0", "@mdx-js/react": "^3.0.1", "@svgr/webpack": "^8.1.0", "clsx": "^1.1.1", "dcmjs": "^0.33.0", "dicom-parser": "^1.8.21", "dicomweb-client": "0.10.4", "docusaurus-plugin-copy": "0.1.1", "docusaurus-plugin-typedoc": "1.0.5", "file-loader": "^6.2.0", "gl-matrix": "^3.4.3", "hammerjs": "^2.0.8", "prism-react-renderer": "2.4.0", "react": "18.3.1", "react-dom": "18.3.1", "react-resize-detector": "11.0.1", "react-router-dom": "6.23.1", "typedoc-plugin-markdown": "4.2.9", "url-loader": "^4.1.1" }, "devDependencies": { "copyfiles": "2.4.1", "esbuild-loader": "^2.18.0", "karma-chrome-launcher": "^3.1.0", "netlify-plugin-cache": "^1.0.3", "puppeteer": "^13.1.3", "shader-loader": "^1.3.1", "typedoc": "0.26.10" } }],
"docs": ["docs@workspace:packages/docs"],

"doctrine": ["[email protected]", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="],

Expand Down
8 changes: 3 additions & 5 deletions packages/core/examples/stackBasic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,9 @@ async function run() {

// 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',
StudyInstanceUID: '999.999.2.19941105.112000',
SeriesInstanceUID: '999.999.2.19941105.112000.2',
wadoRsRoot: 'http://localhost:5000/dicomweb',
});

// Instantiate a rendering engine
Expand Down
1 change: 1 addition & 0 deletions packages/dicomImageLoader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.2",
"@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/codec-libjxl": "file:../../cornerstonejs-codec-libjxl-0.0.1.tgz",
"comlink": "^4.4.1",
"jpeg-lossless-decoder-js": "^2.1.0",
"pako": "^2.0.4",
Expand Down
11 changes: 11 additions & 0 deletions packages/dicomImageLoader/src/decodeImageFrameWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import decodeJPEGBaseline8Bit from './shared/decoders/decodeJPEGBaseline8Bit';
import decodeJPEGBaseline12Bit from './shared/decoders/decodeJPEGBaseline12Bit-js';
import decodeJPEGLossless from './shared/decoders/decodeJPEGLossless';
import decodeJPEGLS from './shared/decoders/decodeJPEGLS';
import decodeJPEGXL from './shared/decoders/decodeJPEGXL';
import decodeJPEG2000 from './shared/decoders/decodeJPEG2000';
import decodeHTJ2K from './shared/decoders/decodeHTJ2K';
// Note that the scaling is pixel value scaling, which is applying a modality LUT
Expand Down Expand Up @@ -413,6 +414,16 @@ export async function decodeImageFrame(

decodePromise = decodeHTJ2K(pixelData, opts);
break;
case '1.2.840.10008.1.2.4.110':
case '1.2.840.10008.1.2.4.111':
case '1.2.840.10008.1.2.4.112':
// JPEG XL
opts = {
...imageFrame,
};

decodePromise = decodeJPEGXL(pixelData, opts);
break;
default:
throw new Error(`no decoder for transfer syntax ${transferSyntax}`);
}
Expand Down
11 changes: 11 additions & 0 deletions packages/dicomImageLoader/src/imageLoader/decodeImageFrame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,17 @@ function decodeImageFrame(
decodeConfig
);

case '1.2.840.10008.1.2.4.110':
case '1.2.840.10008.1.2.4.111':
case '1.2.840.10008.1.2.4.112':
// JPEGXL
return processDecodeTask(
imageFrame,
transferSyntax,
pixelData,
options,
decodeConfig
);
case '3.2.840.10008.1.2.4.96':
case '1.2.840.10008.1.2.4.201':
case '1.2.840.10008.1.2.4.202':
Expand Down
150 changes: 150 additions & 0 deletions packages/dicomImageLoader/src/shared/decoders/decodeJPEGXL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import type { ByteArray } from 'dicom-parser';
// @ts-ignore
import libjxlFactory from '@cornerstonejs/codec-libjxl';
// @ts-ignore
// import libjxlWasm from '@cornerstonejs/codec-libjxl/wasm';
const libjxlWasm = new URL('@cornerstonejs/codec-libjxl/wasm', import.meta.url);

import type { LoaderDecodeOptions } from '../../types';

const local: {
codec: unknown;
decoder: unknown;
decodeConfig: LoaderDecodeOptions;
} = {
codec: undefined,
decoder: undefined,
decodeConfig: {},
};

function calculateSizeAtDecompositionLevel(
decompositionLevel: number,
frameWidth: number,
frameHeight: number
) {
const result = { width: frameWidth, height: frameHeight };
while (decompositionLevel > 0) {
result.width = Math.ceil(result.width / 2);
result.height = Math.ceil(result.height / 2);
decompositionLevel--;
}
return result;
}

export function initialize(decodeConfig?: LoaderDecodeOptions): Promise<void> {
local.decodeConfig = decodeConfig;

if (local.codec) {
return Promise.resolve();
}

const libjxlModule = libjxlFactory({
locateFile: (f) => {
if (f.endsWith('.wasm')) {
return libjxlWasm.toString();
}

return f;
},
});

return new Promise<void>((resolve, reject) => {
libjxlModule.then((instance) => {
local.codec = instance;
local.decoder = new instance.JpegXLDecoder();
resolve();
}, reject);
});
}

// https://github.com/chafey/openjpegjs/blob/master/test/browser/index.html
async function decodeAsync(compressedImageFrame: ByteArray, imageInfo) {
await initialize();
debugger;
// const decoder = local.decoder;
const decoder = new local.codec.JpegXLDecoder();

// get pointer to the source/encoded bit stream buffer in WASM memory
// that can hold the encoded bitstream
const encodedBufferInWASM = decoder.getEncodedBuffer(
compressedImageFrame.length
);

// copy the encoded bitstream into WASM memory buffer
encodedBufferInWASM.set(compressedImageFrame);

// decode it
decoder.decode();

// get information about the decoded image
const frameInfo = decoder.getFrameInfo();
console.log('frameInfo=', frameInfo);

// get the decoded pixels
const decodedPixelsInWASM = decoder.getDecodedBuffer();

const encodedImageInfo = {
columns: frameInfo.width,
rows: frameInfo.height,
bitsPerPixel: frameInfo.bitsPerSample,
signed: imageInfo.signed,
bytesPerPixel: imageInfo.bytesPerPixel,
componentsPerPixel: frameInfo.componentCount,
};

console.log('decodedPixelsInWASM', decodedPixelsInWASM);
const pixelData = getPixelData(
frameInfo,
decodedPixelsInWASM,
imageInfo.signed
);

const encodeOptions = {
frameInfo,
};

// local.codec.doLeakCheck();

return {
...imageInfo,
pixelData,
imageInfo: encodedImageInfo,
encodeOptions,
...encodeOptions,
...encodedImageInfo,
};
}

function getPixelData(frameInfo, decodedBuffer) {
if (frameInfo.bitsPerSample > 8) {
if (frameInfo.isSigned) {
return new Int16Array(
decodedBuffer.buffer,
decodedBuffer.byteOffset,
decodedBuffer.byteLength / 2
);
}

return new Uint16Array(
decodedBuffer.buffer,
decodedBuffer.byteOffset,
decodedBuffer.byteLength / 2
);
}

if (frameInfo.isSigned) {
return new Int8Array(
decodedBuffer.buffer,
decodedBuffer.byteOffset,
decodedBuffer.byteLength
);
}

return new Uint8Array(
decodedBuffer.buffer,
decodedBuffer.byteOffset,
decodedBuffer.byteLength
);
}

export default decodeAsync;
Loading
Loading