Copyright © 2025 Iris Developers; MIT Software License
We provide two mechanisms to use JavaScript to access Iris Encoded Files. Iris RESTful Server is by far the preferred method. The WASM module on NPM (and jsDelivr) is a convenience method that allows in-browser deserializating of the Iris bytestream for compatability with simple static HTTP file serving services like Amazon S3 or Google Cloud Storage -- but it comes at a cost in initial file load time and a more challenging API. This is a feature not offered by other whole slide image (WSI) formats. It is challenging. Read this document carefully if you chose to use this unique feature and design your viewers to simultaneously open all slides of a case when a case is opened. There is very little memory overhead (1-2 MB per slide) and you will only experience the offset table generation delay once.
The two RESTful compatible options are as follows:
- Iris RESTful Server: This is the most efficient method of streaming image data to a client. This is a server side serialization tool. It will map Iris Slides into virtual memory and return image data quickly. This is only compatible with HTTP servers that have a true file system. This solution is very easy to implement using the OpenSeaDragon IrisTileSource in about 4 lines of code (see below).
- Iris-Codec WebAssembly Module: This is a slower method of streaming image data. This is a client side serialization tool. The major benefit is that this is fully compatible with bucked-based storage solutions such as AWS S3 or Google Cloud Storage. It is slower because it validates and loads the full slide metadata in a series of small HTTP 206 'partial read' responses (including offset tables) upfront; following validation and offset generation however, reads are fast as they use HTTP ranged reads for tile data, hitting the same bucket file over and over again. Because of this limitation, we STRONGLY recommend abstracting the metadata for all slides within a case set simultaneously as it requires a small footprint (1-2 MB/slide) and allows for fast access following this file metadata abstraction.
Please refer to the Iris RESTful Server documentation for a description of deploying the Iris RESTful Server. The easiest way is through deployment of our official container images.
When used in a client side system, jsDelivr will distribute the WebAssembly module from NPM. There is no installation needed from you.
<!DOCTYPE html>
<script type="module">
import createModule
from 'https://cdn.jsdelivr.net/npm/iris-codec@latest';
// Compile the WASM module and stall execution until ready
const irisCodec = await createModule();
console.log("Iris-Codec has been loaded");
// ...Your code goes here...
</script>
The general use case is in web applications. If you wish to use it with NodeJS to access remote slides or for node based testing it can be installed from NPM. Please do not try to build a server with this. It is designed for client-side applications and will bottleneck your server.
npm i iris-codec
Do not use this package from NPM for a server deployment that serves Iris encoded slides! This is a client slide tool and the webassembly module was not designed for use in a server and it will not work well for this purpose. Trust me. If you need a server, use Iris RESTful server instead; frankly, it is significantly faster and more robust than what Node will give you for slide tile serving.
Iris RESTful has a simple API (and supports DICOMweb WADO-RS), outlined here, and explained in greater detail within the Iris RESTful API Exlained Section. This will return the slide metadata in JSON format and slide tile image data as an image blob for direct use as an image element source.
Iris RESTful
GET <URL>/slides/<slide-name>/metadata
GET <URL>/slides/<slide-name>/layers/<layer>/tiles/<tile>
Supported WADO-RS
GET <URL>/studies/<study>/series/<UID>/metadata
GET <URL>/studies/<study>/series/<UID>/instances/<layer>/metadata
GET <URL>/studies/<study>/series/<UID>/instances/<layer>/frames/<tile>
Load the Iris-Codec NPM WebAssembly module via jsDelivr (or download the latest javascript release and include your local copy)
<!DOCTYPE html>
<script type="module">
import createModule
from 'https://cdn.jsdelivr.net/npm/iris-codec@latest';
// Compile the WASM module and stall execution until ready
const irisCodec = await createModule();
console.log("Iris-Codec has been loaded");
// ...Your code goes here...
</script>
Once loaded, you can access image data in a manner similar to the C++ and Python Iris-Codec API. Importantly, the metadata will be returned in IrisCodec::Abstraction C++ types (file metadata abstractions) exposed using Emscripten bindings. Refer to the included TypeScript file for those definitions. Image tile data will be returned as an image (MIME) source and can be used directly as an image data source.
We support callback notation presently as it is both common/well established with JS programmers and easy to construct promises from a callback; however it is more challening to reformat a promise into a callback structure for legacy support. We may simply move to promises in the future. If you wish to wrap file access in promise structures, here are example definitions:
// Wraps Module.validateFileStructure (url, callback) in a Promise
function validateFileStructureAsync(Module, fileUrl) {
return new Promise((resolve, reject) => {
Module.validateFileStructure(fileUrl, (result) => {
const SUCCESS = Module.ResultFlag.IRIS_SUCCESS.value;
if (result.flag.value === SUCCESS) {
resolve();
} else {
// result.message is guaranteed to be a JS string
reject(new Error(result.message));
}
});
});
}
// Wraps Module.openIrisSlide(url, callback) in a Promise
function openIrisSlideAsync(url) {
return new Promise((resolve, reject) => {
Module.openIrisSlide(url, slide => {
if (!slide) {
reject(new Error("Failed to validate"));
} else {
resolve(slide);
}
});
});
}
// Wraps slide.getSlideTile(layer, tileIndex, callback) in a Promise
function getSlideTileAsync(slide, layer, tileIndex) {
return new Promise((resolve, reject) => {
slide.getSlideTile(layer, tileIndex, tile_image => {
if (tile_image) {
resolve(tile_image);
} else {
reject(new Error("Failed to get tile"));
}
});
});
}
Perform a deep validation of the slide file structure. This will navigate the internal offset-chain and check for violations of the IFE standard. This can be omitted if you are confident of the source.
const irisCodec = await createModule();
console.log("Iris-Codec has been loaded");
const url = "https://irisdigitalpathology.s3.us-east-2.amazonaws.com/example-slides/cervix_2x_jpeg.iris";
try {
await validateFileStructureAsync(irisCodec, url);
console.log(`Slide file at ${url} successfully passed validation`);
} catch (error) {
console.log(`Slide file at ${url} failed validation: ${error}`);
}
Open a slide file. The following conditional will succeed without throwing an exception if the slide has already passed validation but you may skip validation to reduce server requests.
const irisCodec = await createModule();
console.log("Iris-Codec has been loaded");
const url = "https://irisdigitalpathology.s3.us-east-2.amazonaws.com/example-slides/cervix_2x_jpeg.iris";
try {
await validateFileStructureAsync(irisCodec,url);
const slide = await openIrisSlideAsync(irisCodec, url);
// ...Do something with slide
slide.delete();
} catch (error) {
console.error(error);
}
Get the slide abstraction, read off the slide dimensions, and then print it to the console.
const irisCodec = await createModule();
const url = "https://irisdigitalpathology.s3.us-east-2.amazonaws.com/example-slides/cervix_4x_jpeg.iris";
try {
await validateFileStructureAsync(irisCodec,url);
const slide = await openIrisSlideAsync (irisCodec, url);
// Let's get the slide dimensions and print them to the console.
const info = slide.getSlideInfo();
const extent = info.extent;
console.log(`Slide file ${extent.width} px by ${extent.height}px at lowest resolution layer. The layer extents are as follows:`);
console.log(`There are ${extent.layers.size()} layers comprising the following dimensions:`)
for (var i = 0; i < extent.layers.size(); i++) {
const layer = extent.layers.get(i);
console.log(` Layer ${i}: ${layer.xTiles} x-tiles, ${layer.xTiles} y-tiles, ${layer.scale}x scale`);
}
slide.delete();
} catch (error) {
console.error(error);
}
Generate a quick view of the one of the images (tileImage
) somewhere earlier in the HTML page.
<img id="tileImage" width="128" height="128" alt="Loading..." style="border: 1px solid black;"/>
<!-- ... Somewhere Earlier -->
<script type="module">
// Earlier Promise definitions
try {
await validateFileStructureAsync(irisCodec,url);
const slide = await openIrisSlideAsync (irisCodec, url);
const layer = 0;
const tile = 0;
const tileData = await getSlideTileAsync(slide, layer, tile);
// Now pass the image off to the 'tileImage' element.
const url = URL.createObjectURL(tileData);
const imgElement = document.getElementById("tileImage");
imgElement.src = url;
// Clean up after the image is loaded
imgElement.onload = () => {
URL.revokeObjectURL(objectUrl);
slide.delete();
};
} catch (error) {
console.error(error);
}
</script>
Bringing it all together, the following full HTML page source will show a low power view of the image using a tile grid view similar to the view Pillow.Images produces in the Python API example.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Iris-Codec WASM: Tile Grid</title>
<style>
/* The grid container: auto-flows row by row */
#tileGrid {
display: grid;
justify-content: start; /* left-align if container is wide */
align-content: start; /* top-align if container is tall */
}
#tileGrid img {
width: 128px;
height: 128px;
object-fit: cover; /* crop or letterbox if needed */
background: #f0f0f0; /* placeholder color */
}
</style>
</head>
<body>
<h1>Iris-Codec WASM: Tile Grid</h1>
<div id="loadTimeText">Load time: —</div>
<div id="tileGrid"></div>
<script type="module">
import createModule from './iris-codec.js';
// ————— Helper Promisified APIs —————
function validateFileStructureAsync(Module, fileUrl) {
return new Promise((resolve, reject) => {
Module.validateFileStructure(fileUrl, (result) => {
const SUCCESS = Module.ResultFlag.IRIS_SUCCESS.value;
if (result.flag.value === SUCCESS) resolve();
else reject(new Error(result.message));
});
});
}
function openIrisSlideAsync(Module, url) {
return new Promise((resolve, reject) => {
Module.openIrisSlide(url, slide => {
slide ? resolve(slide)
: reject(new Error("Failed to open slide"));
});
});
}
function getSlideTileAsync(slide, layer, tileIndex) {
return new Promise((resolve, reject) => {
slide.getSlideTile(layer, tileIndex, tile_blob => {
tile_blob ? resolve(tile_blob)
: reject(new Error("Failed to get tile “" + tileIndex + "”"));
});
});
}
// ————— Main Entry Point —————
(async () => {
const irisCodec = await createModule();
const url = "https://irisdigitalpathology.s3.us-east-2.amazonaws.com/example-slides/cervix_4x_jpeg.iris";
try {
await validateFileStructureAsync(irisCodec,url);
const slide = await openIrisSlideAsync(irisCodec, url);
// 1) Read layer info
const layer = 1;
const info = slide.getSlideInfo();
const extent = info.extent.layers.get(layer);
const { xTiles, yTiles } = extent;
const nTiles = xTiles * yTiles;
// 2) Configure the grid container
const gridEl = document.getElementById("tileGrid");
gridEl.style.gridTemplateColumns = `repeat(${xTiles}, 128px)`;
gridEl.style.gridTemplateRows = `repeat(${yTiles}, 128px)`;
// 3) Fetch all tiles in parallel (or chunked if you prefer)
const startAll = performance.now();
const objectUrls = await Promise.all(
Array.from({ length: nTiles }, (_, idx) =>
getSlideTileAsync(slide, layer, idx)
.then(blob => URL.createObjectURL(blob))
)
);
const endAll = performance.now();
document.getElementById("loadTimeText").textContent =
`All tiles fetched in ${Math.round(endAll - startAll)} ms`;
// 4) Insert <img> elements in tile order
objectUrls.forEach(u => {
const img = new Image(128, 128);
img.src = u;
gridEl.appendChild(img);
});
// 5) Cleanup on unload
window.addEventListener("beforeunload", () => {
// Revoke all blob URLs
objectUrls.forEach(u => URL.revokeObjectURL(u));
// Free the slide
slide.delete();
});
} catch (err) {
console.error(err);
alert("Error: " + err.message);
}
})();
</script>
</body>
</html>