dicom-microscopy-viewer
Version:
Interactive web-based viewer for DICOM Microscopy Images
472 lines (427 loc) • 16.2 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: color.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: color.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>import colormap from 'colormap'
import { _generateUID, rescale } from './utils.js'
const _attrs = Symbol('attrs')
/**
* Enumerated values for color map names.
*
* @memberof color
*/
const ColormapNames = {
VIRIDIS: 'VIRIDIS',
INFERNO: 'INFERNO',
MAGMA: 'MAGMA',
GRAY: 'GRAY',
BLUE_RED: 'BLUE_RED',
PHASE: 'PHASE',
PORTLAND: 'PORTLAND',
HOT: 'HOT'
}
Object.freeze(ColormapNames)
/**
* Create a color map.
*
* @param {Object} options
* @param {string} options.name - Name of the color map
* @param {string} options.bins - Number of color bins
*
* @returns {number[][]} RGB triplet for each color
*
* @memberof color
*/
function createColormap ({ name, bins }) {
const lut = {
INFERNO: ['inferno', false],
MAGMA: ['magma', false],
VIRIDIS: ['viridis', false],
GRAY: ['greys', false],
BLUE_RED: ['RdBu', false],
PHASE: ['phase', true],
PORTLAND: ['portland', false],
HOT: ['HOT', false]
}
const params = lut[name]
if (params === undefined) {
throw new Error(`Unknown colormap "${name}".`)
}
const internalName = params[0]
const reverse = params[1]
const colors = colormap({
colormap: internalName,
nshades: bins,
format: 'rgb'
})
if (reverse) {
return colors.reverse()
}
return colors
}
/**
* Build a palette color lookup table object from a colormap.
*
* @param {Object} options
* @param {number[][]} options.data - Array of RGB triplets for each color
* @param {number} options.firstValueMapped - First value that should be mapped
*
* @returns {color.PaletteColorLookupTable} Mapping of grayscale pixel values to RGB color triplets
*
* @memberof color
*/
function buildPaletteColorLookupTable ({ data, firstValueMapped }) {
if (data == null) {
throw new Error(
'Argument "data" is required for building Palette Color Lookup Table.'
)
}
if (firstValueMapped == null) {
throw new Error(
'Argument "firstValueMapped" is required for building ' +
'Palette Color Lookup Table.'
)
}
const numberOfEntries = data.length
const Type = Uint8Array
const redData = new Type(numberOfEntries)
const greenData = new Type(numberOfEntries)
const blueData = new Type(numberOfEntries)
for (let i = 0; i < numberOfEntries; i++) {
redData[i] = data[i][0]
greenData[i] = data[i][1]
blueData[i] = data[i][2]
}
const descriptor = [numberOfEntries, firstValueMapped, 8]
return new PaletteColorLookupTable({
uid: _generateUID(),
redDescriptor: descriptor,
greenDescriptor: descriptor,
blueDescriptor: descriptor,
redData,
greenData,
blueData
})
}
/**
* A Palette Color Lookup Table
*
* @class
* @memberof color
*/
class PaletteColorLookupTable {
/**
* @param {Object} options
* @param {string} options.uid - UID
* @param {number[]} options.redDescriptor - Red LUT descriptor
* @param {number[]} options.greenDescriptor - Green LUT descriptor
* @param {number[]} options.blueDescriptor - Blue LUT descriptor
* @param {Uint8Array|Uint16Array} options.redData - Red LUT data
* @param {Uint8Array|Uint16Array} options.greenData - Green LUT data
* @param {Uint8Array|Uint16Array} options.blueData - Blue LUT data
* @param {Uint8Array|Uint16Array} options.redSegmentedData - Red segmented LUT data
* @param {Uint8Array|Uint16Array} options.greenSegmentedData - Green segmented LUT data
* @param {Uint8Array|Uint16Array} options.blueSegmentedData - Blue segmented LUT data
*/
constructor ({
uid,
redDescriptor,
greenDescriptor,
blueDescriptor,
redData,
greenData,
blueData,
redSegmentedData,
greenSegmentedData,
blueSegmentedData
}) {
this[_attrs] = { uid }
// Number of entries in the LUT data
const firstDescriptorValues = new Set([
redDescriptor[0],
greenDescriptor[0],
blueDescriptor[0]
])
if (firstDescriptorValues.size !== 1) {
throw new Error(
'First value of Red, Green, and Blue Palette Color Lookup Table ' +
'Descriptor must be the same.'
)
}
const n = [...firstDescriptorValues][0]
if (n === 0) {
this[_attrs].numberOfEntries = Math.pow(2, 16)
} else {
this[_attrs].numberOfEntries = n
}
// Pixel value mapped to the first entry in the LUT data
const secondDescriptorValues = new Set([
redDescriptor[1],
greenDescriptor[1],
blueDescriptor[1]
])
if (secondDescriptorValues.size !== 1) {
throw new Error(
'Second value of Red, Green, and Blue Palette Color Lookup Table ' +
'Descriptor must be the same.'
)
}
this[_attrs].firstValueMapped = [...secondDescriptorValues][0]
// Number of bits for each entry in the LUT Data
const thirdDescriptorValues = new Set([
redDescriptor[2],
greenDescriptor[2],
blueDescriptor[2]
])
if (thirdDescriptorValues.size !== 1) {
throw new Error(
'Third value of Red, Green, and Blue Palette Color Lookup Table ' +
'Descriptor must be the same.'
)
}
this[_attrs].bitsPerEntry = [...thirdDescriptorValues][0]
if ([8, 16].indexOf(this[_attrs].bitsPerEntry) < 0) {
throw new Error(
'Third value of Red, Green, and Blue Palette Color Lookup Table ' +
'Descriptor must be either ' + '8 or 16.'
)
}
if (redSegmentedData != null && redData != null) {
throw new Error(
'Either Segmented Red Palette Color Lookup Data or Red Palette ' +
'Color Lookup Data should be provided, but not both.'
)
} else if (redSegmentedData == null && redData == null) {
throw new Error(
'Either Segmented Red Palette Color Lookup Data or Red Palette ' +
'Color Lookup Data must be provided.'
)
}
if (redData) {
if (redData.length !== this[_attrs].numberOfEntries) {
throw new Error(
'Red Palette Color Lookup Table Data has wrong number of entries.'
)
}
}
this[_attrs].redSegmentedData = redSegmentedData
this[_attrs].redData = redData
if (greenSegmentedData != null && greenData != null) {
throw new Error(
'Either Segmented Green Palette Color Lookup Data or Green Palette ' +
'Color Lookup Data should be provided, but not both.'
)
} else if (greenSegmentedData == null && greenData == null) {
throw new Error(
'Either Segmented Green Palette Color Lookup Data or Green ' +
'Palette Color Lookup Data must be provided.'
)
}
if (greenData) {
if (greenData.length !== this[_attrs].numberOfEntries) {
throw new Error(
'Green Palette Color Lookup Table Data has wrong number of entries.'
)
}
}
this[_attrs].greenSegmentedData = greenSegmentedData
this[_attrs].greenData = greenData
if (blueSegmentedData != null && blueData != null) {
throw new Error(
'Either Segmented Blue Palette Color Lookup Data or Blue Palette ' +
'Color Lookup Data must be provided, but not both.'
)
} else if (blueSegmentedData != null && blueData != null) {
throw new Error(
'Either Segmented Blue Palette Color Lookup Data or Blue Palette ' +
'Color Lookup Data must be provided.'
)
}
if (blueData) {
if (blueData.length !== this[_attrs].numberOfEntries) {
throw new Error(
'Blue Palette Color Lookup Table Data has wrong number of entries.'
)
}
}
this[_attrs].blueSegmentedData = blueSegmentedData
this[_attrs].blueData = blueData
if (this[_attrs].bitsPerEntry === 8) {
this[_attrs].DataType = Uint8Array
} else {
this[_attrs].DataType = Uint16Array
}
// Will be used to cache created colormap for repeated access
this[_attrs].data = null
Object.freeze(this)
}
_expandSegmentedLUTData (segmentedData) {
const lut = new this[_attrs].DataType(this[_attrs].numberOfEntries)
let offset = 0
for (let i = 0; i < segmentedData.length; i++) {
const opcode = segmentedData[i++]
if (opcode === 0) {
// Discrete
const length = segmentedData[i++]
const value = segmentedData[i]
for (let j = offset; j < (offset + length); j++) {
lut[j] = value
}
offset += length
} else if (opcode === 1) {
// Linear (interpolation)
const length = segmentedData[i++]
const endpoint = segmentedData[i]
const startpoint = lut[offset - 1]
const step = (endpoint - startpoint) / (length - 1)
for (let j = 0; j < length; j++) {
const value = startpoint + Math.round(j * step)
lut[offset + j] = value
}
offset += length
} else if (opcode === 2) {
// TODO
throw new Error(
'Indirect segment type is not yet supported for ' +
'Segmented Palette Color Lookup Table.'
)
} else {
throw new Error(
'Encountered unexpected segment type is not yet supported for ' +
'Segmented Palette Color Lookup Table.'
)
}
}
return lut
}
/**
* Palette Color Lookup Table UID
*
* @type string
*/
get uid () {
return this[_attrs].uid
}
/**
* Palette Color Lookup Table Data.
*
* RGB color triplet for each value mapped.
*
* @type number[][]
*/
get data () {
if (this[_attrs].data == null) {
const redLUT = (
this[_attrs].redData
? new this[_attrs].DataType(this[_attrs].redData)
: this._expandSegmentedLUTData(
this[_attrs].redSegmentedData,
this[_attrs].numberOfEntries,
this[_attrs].bitsPerEntry
)
)
const greenLUT = (
this[_attrs].greenData
? new this[_attrs].DataType(this[_attrs].greenData)
: this._expandSegmentedLUTData(
this[_attrs].greenSegmentedData,
this[_attrs].numberOfEntries,
this[_attrs].bitsPerEntry
)
)
const blueLUT = (
this[_attrs].blueData
? new this[_attrs].DataType(this[_attrs].blueData)
: this._expandSegmentedLUTData(
this[_attrs].blueSegmentedData,
this[_attrs].numberOfEntries,
this[_attrs].bitsPerEntry
)
)
const uniqueNumberOfEntries = new Set([
redLUT.length,
greenLUT.length,
blueLUT.length
])
if (uniqueNumberOfEntries.size > 1) {
throw new Error(
'Red, Green, and Blue Palette Color Lookup Tables ' +
'must have the same size.'
)
}
const maxValues = [
Math.max(...redLUT),
Math.max(...greenLUT),
Math.max(...blueLUT)
]
const maxInput = Math.max(...maxValues)
const maxOutput = 255
if (this[_attrs].bitsPerEntry === 16 && maxInput > 255) {
/*
* Only palettes with 256 entries and 8 bit per entry are supported for
* display. Therefore, data need to rescaled and resampled.
*/
const n = 256
const step = this[_attrs].numberOfEntries / n
this[_attrs].data = new Array(n)
for (let i = 0; i < n; i++) {
const j = i * step
this[_attrs].data[i] = [
Math.round(rescale(redLUT[j], 0, maxInput, 0, maxOutput)),
Math.round(rescale(greenLUT[j], 0, maxInput, 0, maxOutput)),
Math.round(rescale(blueLUT[j], 0, maxInput, 0, maxOutput))
]
}
} else {
this[_attrs].data = new Array(this[_attrs].numberOfEntries)
for (let i = 0; i < this[_attrs].numberOfEntries; i++) {
this[_attrs].data[i] = [redLUT[i], greenLUT[i], blueLUT[i]]
}
}
}
return this[_attrs].data
}
/**
* First value mapped
*
* @type number
*/
get firstValueMapped () {
return this[_attrs].firstValueMapped
}
}
export {
ColormapNames,
createColormap,
PaletteColorLookupTable,
buildPaletteColorLookupTable
}
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Namespaces</h3><ul><li><a href="annotation.html">annotation</a></li><li><a href="api.html">api</a></li><li><a href="color.html">color</a></li><li><a href="events.html">events</a></li><li><a href="mapping.html">mapping</a></li><li><a href="metadata.html">metadata</a></li><li><a href="opticalPath.html">opticalPath</a></li><li><a href="roi.html">roi</a></li><li><a href="scoord3d.html">scoord3d</a></li><li><a href="segment.html">segment</a></li><li><a href="utils.html">utils</a></li><li><a href="viewer.html">viewer</a></li></ul><h3>Classes</h3><ul><li><a href="annotation.AnnotationGroup.html">AnnotationGroup</a></li><li><a href="color.PaletteColorLookupTable.html">PaletteColorLookupTable</a></li><li><a href="mapping.ParameterMapping.html">ParameterMapping</a></li><li><a href="mapping.Transformation.html">Transformation</a></li><li><a href="metadata.Comprehensive3DSR.html">Comprehensive3DSR</a></li><li><a href="metadata.MicroscopyBulkSimpleAnnotations.html">MicroscopyBulkSimpleAnnotations</a></li><li><a href="metadata.ParametricMap.html">ParametricMap</a></li><li><a href="metadata.Segmentation.html">Segmentation</a></li><li><a href="metadata.SOPClass.html">SOPClass</a></li><li><a href="metadata.VLWholeSlideMicroscopyImage.html">VLWholeSlideMicroscopyImage</a></li><li><a href="module.exports_module.exports.html">exports</a></li><li><a href="opticalPath.OpticalPath.html">OpticalPath</a></li><li><a href="roi.ROI.html">ROI</a></li><li><a href="scoord3d.Ellipse.html">Ellipse</a></li><li><a href="scoord3d.Ellipsoid.html">Ellipsoid</a></li><li><a href="scoord3d.Multipoint.html">Multipoint</a></li><li><a href="scoord3d.Point.html">Point</a></li><li><a href="scoord3d.Polygon.html">Polygon</a></li><li><a href="scoord3d.Polyline.html">Polyline</a></li><li><a href="scoord3d.Scoord3D.html">Scoord3D</a></li><li><a href="segment.Segment.html">Segment</a></li><li><a href="viewer.LabelImageViewer.html">LabelImageViewer</a></li><li><a href="viewer.OverviewImageViewer.html">OverviewImageViewer</a></li><li><a href="viewer.VolumeImageViewer.html">VolumeImageViewer</a></li></ul><h3>Global</h3><ul><li><a href="global.html#addTask">addTask</a></li><li><a href="global.html#cancelTask">cancelTask</a></li><li><a href="global.html#decode">decode</a></li><li><a href="global.html#getStatistics">getStatistics</a></li><li><a href="global.html#handleMessageFromWorker">handleMessageFromWorker</a></li><li><a href="global.html#initialize">initialize</a></li><li><a href="global.html#loadWebWorkerTask">loadWebWorkerTask</a></li><li><a href="global.html#setTaskPriority">setTaskPriority</a></li><li><a href="global.html#spawnWebWorker">spawnWebWorker</a></li><li><a href="global.html#startTaskOnWebWorker">startTaskOnWebWorker</a></li><li><a href="global.html#terminateAllWebWorkers">terminateAllWebWorkers</a></li><li><a href="global.html#transform">transform</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu Sep 29 2022 16:54:54 GMT-0400 (Eastern Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>