Skip to content
Closed
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
34 changes: 34 additions & 0 deletions .github/workflows/codspeed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: CodSpeed

on:
push:
branches:
- "main"
pull_request:
# `workflow_dispatch` allows CodSpeed to trigger backtest
# performance analysis in order to generate initial data.
workflow_dispatch:

permissions:
contents: read
id-token: write

jobs:
codspeed:
name: Run benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Run benchmarks
uses: CodSpeedHQ/action@v4
with:
mode: simulation
run: npx vitest bench --run
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# codecs

[![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/cornerstonejs/codecs?utm_source=badge)

## Packages

This repository is maintained as a monorepo. This means that this repository, instead of containing a single project, contains many projects. If you explore our project structure, you'll see the following:
Expand Down Expand Up @@ -59,7 +61,7 @@ Transfer Syntax is the language used in DICOM to describe the DICOM file format

- \* - 1.2.840.10008.1.2.4.50: 8-bit RGB can leverage the browser's built in decoder.
- \*\* - 1.2.840.10008.1.2.4.\[92|93\]: Not supported in previous image loaders; OpenJPEG may work with these
- \*\*\* - Unlike all other DICOM transfer syntaxes, the deflate transfer syntaxes compress the whole of the DICOM data (tags, lengths, VR etc.) rather than just the pixel data - this is done using the standard deflate mechanism as used in gzip etc.) It is therefore most suitable for non-pixel objects such as structured reports, presentation states etc.
- \*\*\* - Unlike all other DICOM transfer syntaxes, the deflate transfer syntaxes compress the whole of the DICOM data (tags, lengths, VR etc.) rather than just the pixel data - this is done using the standard "deflate" mechanism as used in gzip etc.) It is therefore most suitable for non-pixel objects such as structured reports, presentation states etc.

- 5: [JS Decoder](https://github.com/cornerstonejs/cornerstoneWADOImageLoader/blob/4bfa04759412d58647cc5d6bd0204aa37e4542e3/src/shared/decoders/decodeRLE.js)
- 57 & 70: [JS Decoder](https://github.com/cornerstonejs/cornerstoneWADOImageLoader/blob/4bfa04759412d58647cc5d6bd0204aa37e4542e3/codecs/jpegLossless.js)
Expand Down
66 changes: 66 additions & 0 deletions benchmarks/big-endian.bench.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { bench, describe } from "vitest";
import decode from "../packages/big-endian/src/index.js";

/**
* Generate synthetic pixel data for benchmarking.
* Simulates real DICOM image data at various resolutions.
*/
function generatePixelData(size) {
const buffer = new ArrayBuffer(size);
const view = new Uint8Array(buffer);
for (let i = 0; i < size; i++) {
view[i] = i & 0xff;
}
return view;
}

// Typical DICOM image sizes (in bytes)
const SMALL_IMAGE = 256 * 256 * 2; // 256x256 16-bit
const MEDIUM_IMAGE = 512 * 512 * 2; // 512x512 16-bit (standard CT/MR)
const LARGE_IMAGE = 1024 * 1024 * 2; // 1024x1024 16-bit

const smallData = generatePixelData(SMALL_IMAGE);
const mediumData = generatePixelData(MEDIUM_IMAGE);
const largeData = generatePixelData(LARGE_IMAGE);
const small8bit = generatePixelData(256 * 256);

describe("big-endian decode - 16-bit unsigned", () => {
bench("256x256", () => {
decode(
{ bitsAllocated: 16, pixelRepresentation: 0 },
new Uint8Array(smallData.buffer.slice(0))
);
});

bench("512x512", () => {
decode(
{ bitsAllocated: 16, pixelRepresentation: 0 },
new Uint8Array(mediumData.buffer.slice(0))
);
});

bench("1024x1024", () => {
decode(
{ bitsAllocated: 16, pixelRepresentation: 0 },
new Uint8Array(largeData.buffer.slice(0))
);
});
});

describe("big-endian decode - 16-bit signed", () => {
bench("512x512", () => {
decode(
{ bitsAllocated: 16, pixelRepresentation: 1 },
new Uint8Array(mediumData.buffer.slice(0))
);
});
});

describe("big-endian decode - 8-bit passthrough", () => {
bench("256x256", () => {
decode(
{ bitsAllocated: 8, pixelRepresentation: 0 },
new Uint8Array(small8bit.buffer.slice(0))
);
});
});
76 changes: 76 additions & 0 deletions benchmarks/little-endian.bench.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { bench, describe } from "vitest";
import decode from "../packages/little-endian/src/index.js";

/**
* Generate synthetic pixel data for benchmarking.
* Simulates real DICOM image data at various resolutions.
*/
function generatePixelData(size) {
const buffer = new ArrayBuffer(size);
const view = new Uint8Array(buffer);
for (let i = 0; i < size; i++) {
view[i] = i & 0xff;
}
return view;
}

// Typical DICOM image sizes (in bytes)
const SMALL_IMAGE = 256 * 256 * 2; // 256x256 16-bit
const MEDIUM_IMAGE = 512 * 512 * 2; // 512x512 16-bit (standard CT/MR)
const LARGE_IMAGE = 1024 * 1024 * 2; // 1024x1024 16-bit

const smallData = generatePixelData(SMALL_IMAGE);
const mediumData = generatePixelData(MEDIUM_IMAGE);
const largeData = generatePixelData(LARGE_IMAGE);
const small8bit = generatePixelData(256 * 256);
const float32Data = generatePixelData(512 * 512 * 4); // 512x512 32-bit float

describe("little-endian decode - 16-bit unsigned", () => {
bench("256x256", () => {
decode(
{ bitsAllocated: 16, pixelRepresentation: 0 },
new Uint8Array(smallData.buffer.slice(0))
);
});

bench("512x512", () => {
decode(
{ bitsAllocated: 16, pixelRepresentation: 0 },
new Uint8Array(mediumData.buffer.slice(0))
);
});

bench("1024x1024", () => {
decode(
{ bitsAllocated: 16, pixelRepresentation: 0 },
new Uint8Array(largeData.buffer.slice(0))
);
});
});

describe("little-endian decode - 16-bit signed", () => {
bench("512x512", () => {
decode(
{ bitsAllocated: 16, pixelRepresentation: 1 },
new Uint8Array(mediumData.buffer.slice(0))
);
});
});

describe("little-endian decode - 32-bit float", () => {
bench("512x512", () => {
decode(
{ bitsAllocated: 32, pixelRepresentation: 0 },
new Uint8Array(float32Data.buffer.slice(0))
);
});
});

describe("little-endian decode - 8-bit passthrough", () => {
bench("256x256", () => {
decode(
{ bitsAllocated: 8, pixelRepresentation: 0 },
new Uint8Array(small8bit.buffer.slice(0))
);
});
});
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
"packages/*"
],
"devDependencies": {
"@codspeed/vitest-plugin": "^5.4.0",
"dotenv": "^14.1.0",
"jest": "^26.6.3",
"lerna": "^8.0.0"
"lerna": "^8.0.0",
"vitest": "^4.1.6"
},
"engines": {
"node": ">=0.14"
Expand Down
11 changes: 11 additions & 0 deletions vitest.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineConfig } from "vitest/config";
import codspeed from "@codspeed/vitest-plugin";

export default defineConfig({
plugins: [codspeed()],
test: {
benchmark: {
include: ["benchmarks/**/*.bench.{js,mjs}"],
},
},
});
Loading
Loading