Skip to content
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@
"@cornerstonejs/codec-openjph": "^2.4.5",
"dicomweb-client": "^0.10.3",
"colormap": "^2.3",
"dicomicc": "^0.2",
"dcmjs": "^0.41.0",
"dicomicc": "^0.1",
"image-type": "^4.1",
"mathjs": "^11.2",
"ol": "^10.6.0",
Expand Down
12 changes: 8 additions & 4 deletions src/decode.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ function _processDecodeAndTransformTask (
samplesPerPixel,
sopInstanceUID,
metadata,
iccProfiles
iccProfiles,
iccOutputType = "srgb" // "srgb" or "display-p3"
) {
const priority = undefined
const transferList = undefined
Expand All @@ -26,7 +27,8 @@ function _processDecodeAndTransformTask (
samplesPerPixel,
sopInstanceUID,
metadata,
iccProfiles
iccProfiles,
iccOutputType
},
priority,
transferList
Expand All @@ -42,7 +44,8 @@ async function _decodeAndTransformFrame ({
samplesPerPixel,
sopInstanceUID,
metadata, // metadata of all images (different resolution levels)
iccProfiles // ICC profiles for all images
iccProfiles, // ICC profiles for all images
iccOutputType = "srgb" // "srgb" or "display-p3"
}) {
const result = await _processDecodeAndTransformTask(
frame,
Expand All @@ -53,7 +56,8 @@ async function _decodeAndTransformFrame ({
samplesPerPixel,
sopInstanceUID,
metadata,
iccProfiles
iccProfiles,
iccOutputType
)

const signed = pixelRepresentation === 1
Expand Down
4 changes: 3 additions & 1 deletion src/pyramid.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ function _createTileLoadFunction ({
client,
channel,
iccProfiles,
iccOutputType,
targetElement
}) {
return async (z, y, x) => {
Expand Down Expand Up @@ -521,7 +522,8 @@ function _createTileLoadFunction ({
samplesPerPixel,
sopInstanceUID,
metadata: pyramid.metadata,
iccProfiles
iccProfiles,
iccOutputType
}).then(pixelArray => {
if (pixelArray.constructor === Float64Array) {
// TODO: handle Float64Array using LUT
Expand Down
50 changes: 49 additions & 1 deletion src/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,7 @@ const _tileGrid = Symbol('tileGrid')
const _updateOverviewMapSize = Symbol('updateOverviewMapSize')
const _annotationOptions = Symbol('annotationOptions')
const _isICCProfilesEnabled = Symbol('isICCProfilesEnabled')
const _iccOutputType = Symbol('_iccOutputType')
const _iccProfiles = Symbol('iccProfiles')
const _container = Symbol('container')
const _highResSources = Symbol('highResSources')
Expand Down Expand Up @@ -841,6 +842,7 @@ class VolumeImageViewer {
this[_clients] = {}
this[_errorInterceptor] = options.errorInterceptor || (error => error)
this[_isICCProfilesEnabled] = true
this[_iccOutputType] = "srgb"
this[_container] = null
this[_clients] = {}
this[_iccProfiles] = []
Expand Down Expand Up @@ -1202,6 +1204,26 @@ class VolumeImageViewer {
extent: this[_pyramid].extent
})

/**
* Detect the display color space.
* Note: The WebGLRenderingContext only supports sRGB and Display-P3
* color spaces, Adobe RGB (1998) and ROMM RGB are not supported.
* @returns {string} 'display-p3' or 'srgb'
*/
function detectDisplayColorSpace() {
if (typeof window !== 'undefined' && window.matchMedia) {
if (window.matchMedia("(color-gamut: p3)").matches) {
return 'display-p3';
} else if (window.matchMedia("(color-gamut: srgb)").matches) {
return 'srgb';
}
}
return 'srgb';
}

this[_iccOutputType] = detectDisplayColorSpace();
console.log(`Detected display color space: "${this[_iccOutputType]}"`);

const layers = []
const overviewLayers = []
this[_opticalPaths] = {}
Expand Down Expand Up @@ -1355,7 +1377,11 @@ class VolumeImageViewer {
})
opticalPath.layer.helper = helper
opticalPath.layer.on('precompose', (event) => {
const gl = event.context
const gl = event.context;
if ('drawingBufferColorSpace' in gl) {
gl.drawingBufferColorSpace = this[_iccOutputType]
console.debug("Using color space - layer:", gl.drawingBufferColorSpace)
}
gl.enable(gl.BLEND)
gl.blendEquation(gl.FUNC_ADD)
gl.blendFunc(gl.SRC_COLOR, gl.ONE)
Expand All @@ -1382,6 +1408,10 @@ class VolumeImageViewer {
opticalPath.overviewLayer.helper = overviewHelper
opticalPath.overviewLayer.on('precompose', (event) => {
const gl = event.context
if ('drawingBufferColorSpace' in gl) {
gl.drawingBufferColorSpace = this[_iccOutputType]
console.debug("Using color space - overviewLayer:", gl.drawingBufferColorSpace)
}
gl.enable(gl.BLEND)
gl.blendEquation(gl.FUNC_ADD)
gl.blendFunc(gl.SRC_COLOR, gl.ONE)
Expand Down Expand Up @@ -1451,6 +1481,14 @@ class VolumeImageViewer {
useInterimTilesOnError: false,
cacheSize: this[_options].tilesCacheSize
})
opticalPath.layer.on('precompose', (event) => {
const gl = event.context;
if ('drawingBufferColorSpace' in gl) {
gl.drawingBufferColorSpace = this[_iccOutputType]
console.debug("Using color space - layer:", gl.drawingBufferColorSpace)
}
})

opticalPath.layer.on('error', (event) => {
console.error(
`error rendering optical path "${opticalPathIdentifier}"`,
Expand All @@ -1468,6 +1506,13 @@ class VolumeImageViewer {
preload: 0,
useInterimTilesOnError: false
})
opticalPath.overviewLayer.on('precompose', (event) => {
const gl = event.context;
if ('drawingBufferColorSpace' in gl) {
gl.drawingBufferColorSpace = this[_iccOutputType]
console.debug("Using color space - overviewLayer:", gl.drawingBufferColorSpace)
}
})

layers.push(opticalPath.layer)
overviewLayers.push(opticalPath.overviewLayer)
Expand Down Expand Up @@ -2325,6 +2370,7 @@ class VolumeImageViewer {
const loaderWithICCProfiles = _createTileLoadFunction({
targetElement: this[_container],
iccProfiles: profiles,
iccOutputType: this[_iccOutputType],
...item.loaderParams
})
const loaderWithoutICCProfiles = _createTileLoadFunction({
Expand Down Expand Up @@ -2414,6 +2460,7 @@ class VolumeImageViewer {
const loader = _createTileLoadFunction({
targetElement: container,
iccProfiles: profiles,
iccOutputType: this[_iccOutputType],
...opticalPath.loaderParams
})
const source = opticalPath.layer.getSource()
Expand Down Expand Up @@ -2585,6 +2632,7 @@ class VolumeImageViewer {
const loader = _createTileLoadFunction({
targetElement: container,
iccProfiles: this[_isICCProfilesEnabled] && profiles.length > 0 ? profiles : null,
iccOutputType: this[_isICCProfilesEnabled] && profiles.length > 0 ? this[_iccOutputType] : null,
...item.loaderParams
})
source.setLoader(loader)
Expand Down
5 changes: 3 additions & 2 deletions src/webWorker/decodeAndTransformTask.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function _handler (data, doneCallback) {
frame,
sopInstanceUID,
metadata,
iccProfiles
iccProfiles,
iccOutputType = "srgb" // "srgb" or "display-p3"
} = data.data

_checkImageTypeAndDecode(
Expand All @@ -43,7 +44,7 @@ function _handler (data, doneCallback) {
if (iccProfiles?.length) {
// Only instantiate the transformer once and cache it for reuse.
if (transformerColor === undefined) {
transformerColor = new ColorTransformer(metadata, iccProfiles)
transformerColor = new ColorTransformer(metadata, iccProfiles, iccOutputType)
}
// Apply ICC color transform
transformerColor.transform(
Expand Down
28 changes: 26 additions & 2 deletions src/webWorker/transformers/transformerICC.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ export default class ColorTransformer extends Transformer {
* @param {Array<metadata.VLWholeSlideMicroscopyImage>} - Metadata of each
* image
* @param {Array<TypedArray>} - ICC profiles of each image
* @param {number} [iccOutputType="srgb"] - ICC output type
* ("srgb": sRGB (default), "display-p3": Display-P3, "adobe-rgb": Adobe RGB (1998), "romm-rgb": ROMM RGB).
*/
constructor (metadata, iccProfiles) {
constructor (metadata, iccProfiles, iccOutputType = "srgb") {
super()
if (metadata.length !== iccProfiles.length) {
throw new Error(
Expand All @@ -23,6 +25,7 @@ export default class ColorTransformer extends Transformer {
this.iccProfiles = iccProfiles
this.codec = null
this.transformers = {}
this.iccOutputTypeString = iccOutputType;
}

_initialize () {
Expand Down Expand Up @@ -50,11 +53,31 @@ export default class ColorTransformer extends Transformer {
const samplesPerPixel = this.metadata[index].SamplesPerPixel
const planarConfiguration = this.metadata[index].PlanarConfiguration
const sopInstanceUID = this.metadata[index].SOPInstanceUID

const profile = inlineBinaryToUint8Array(this.iccProfiles[index])
if (!profile) {
console.warn('Unable to convert icc profile: ', this.iccProfiles[index])
return
}

// Determine ICC output type using the exposed enum
let iccOutputType
switch (this.iccOutputTypeString) {
case "display-p3":
iccOutputType = this.codec.DcmIccOutputType.DISPLAY_P3
break
case "adobe-rgb":
iccOutputType = this.codec.DcmIccOutputType.ADOBE_RGB
break
case "romm-rgb":
iccOutputType = this.codec.DcmIccOutputType.ROMM_RGB
break
case "srgb":
default:
iccOutputType = this.codec.DcmIccOutputType.SRGB
break
}

this.transformers[sopInstanceUID] = new this.codec.ColorManager(
{
columns,
Expand All @@ -63,7 +86,8 @@ export default class ColorTransformer extends Transformer {
samplesPerPixel,
planarConfiguration
},
profile
profile,
iccOutputType
)
}
resolve(this.transformers)
Expand Down
Loading