UNPKG

dicom-microscopy-viewer

Version:
472 lines (427 loc) 16.2 kB
<!DOCTYPE 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 &lt; 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) &lt; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &lt; segmentedData.length; i++) { const opcode = segmentedData[i++] if (opcode === 0) { // Discrete const length = segmentedData[i++] const value = segmentedData[i] for (let j = offset; j &lt; (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 &lt; 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 &amp;&amp; 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 &lt; 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 &lt; 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>