UNPKG

dicom-microscopy-viewer

Version:
848 lines (793 loc) 26.9 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: annotation.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: annotation.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>import { _fetchBulkdata } from './utils.js' const _attrs = Symbol('attrs') /** * Annotation Group. * * Describes an item of the Annotation Group Sequence of a DICOM Microscopy Bulk * Simple Annotations instance. * * @class * @memberof annotation */ class AnnotationGroup { /** * @param {Object} options - Options * @param {string} options.uid - Annotation Group UID * @param {number} options.number - Annotation Group Number (one-based index value) * @param {string} options.label - Annotation Group Label * @param {string} options.algorithmName - Annotation Group Algorithm Name * @param {Object} options.algorithmType - Annotation Group Algorithm Type * @param {Object} options.propertyCategory - Annotation Property Category Code * @param {Object} options.propertyType - Annotation Property Type Code * @param {string} options.studyInstanceUID - Study Instance UID of Annotation instances * @param {string} options.seriesInstanceUID - Series Instance UID of Annotation instances * @param {string[]} options.sopInstanceUIDs - SOP Instance UIDs of Annotation instances */ constructor ({ uid, number, label, propertyCategory, propertyType, algorithmType, algorithmName, studyInstanceUID, seriesInstanceUID, sopInstanceUIDs }) { this[_attrs] = {} if (uid == null) { throw new Error('Annotation Group UID is required.') } else { this[_attrs].uid = uid } if (number == null) { throw new Error('Annotation Group Number is required.') } this[_attrs].number = number if (label == null) { throw new Error('Annotation Group Label is required.') } this[_attrs].label = label if (propertyCategory == null) { throw new Error('Annotation Property Category is required.') } this[_attrs].propertyCategory = propertyCategory if (propertyType == null) { throw new Error('Annotation Property Type is required.') } this[_attrs].propertyType = propertyType if (algorithmName == null) { throw new Error('Annotation Group Algorithm Name is required.') } this[_attrs].algorithmType = algorithmType if (algorithmType == null) { throw new Error('Annotation Group Generation Type is required.') } this[_attrs].algorithmName = algorithmName if (studyInstanceUID == null) { throw new Error('Study Instance UID is required.') } this[_attrs].studyInstanceUID = studyInstanceUID if (seriesInstanceUID == null) { throw new Error('Series Instance UID is required.') } this[_attrs].seriesInstanceUID = seriesInstanceUID if (sopInstanceUIDs == null) { throw new Error('SOP Instance UIDs are required.') } this[_attrs].sopInstanceUIDs = sopInstanceUIDs Object.freeze(this) } /** * Annotation Group UID * * @type string */ get uid () { return this[_attrs].uid } /** * Annotation Group Number. * * @type number */ get number () { return this[_attrs].number } /** * Annotation Group Label * * @type string */ get label () { return this[_attrs].label } /** * Annotation Group Algorithm Identification * * @type string */ get algorithmName () { return this[_attrs].algorithmName } /** * Annotation Group Generation Type * * @type Object */ get algorithmType () { return this[_attrs].algorithmType } /** * Annotation Property Category * * @type Object */ get propertyCategory () { return this[_attrs].propertyCategory } /** * Annotation Property Type * * @type Object */ get propertyType () { return this[_attrs].propertyType } /** * Study Instance UID of DICOM Microscopy Bulk Simple Annotations objects. * * @type string */ get studyInstanceUID () { return this[_attrs].studyInstanceUID } /** * Series Instance UID of DICOM Microscopy Bulk Simple Annotations objects. * * @type string */ get seriesInstanceUID () { return this[_attrs].seriesInstanceUID } /** * SOP Instance UIDs of DICOM Microscopy Bulk Simple Annotations objects. * * @type string[] */ get sopInstanceUIDs () { return this[_attrs].sopInstanceUIDs } } /** * Fetch graphic data (point coordinates) of an annotation group. * * @param {Object} options * @param {Object} options.metadataItem - Metadata of Annotation Group Sequence item * @param {Object} options.bulkdataItem - Bulkdata of Annotation Group Sequence item * @param {Object} options.client - DICOMweb client * * @returns {Promise&lt;TypedArray>} Graphic data * * @private */ async function _fetchGraphicData ({ metadataItem, bulkdataItem, client }) { const uid = metadataItem.AnnotationGroupUID if ('PointCoordinatesData' in metadataItem) { return metadataItem.PointCoordinatesData } else if ('DoublePointCoordinatesData' in metadataItem) { return metadataItem.DoublePointCoordinatesData } else { if (bulkdataItem == null) { throw new Error( `Could not find bulkdata of annotation group "${uid}".` ) } else { if ('PointCoordinatesData' in bulkdataItem) { console.info(`fetch point coordinate data of annotation group "${uid}"`) return await _fetchBulkdata({ client, reference: bulkdataItem.PointCoordinatesData }) } else if ('DoublePointCoordinatesData' in bulkdataItem) { console.info(`fetch point coordinate data of annotation group "${uid}"`) return await _fetchBulkdata({ client, reference: bulkdataItem.DoublePointCoordinatesData }) } else { throw new Error( 'Could not find "PointCoordinatesData" or ' + '"DoublePointCoordinatesData" in bulkdata ' + `of annotation group "${uid}".` ) } } } } /** * Fetch graphic index (long primitive point index) of an annotation group. * * @param {object} options * @param {object} options.metadataItem - Metadata of Annotation Group Sequence item * @param {object} options.bulkdataItem - Bulkdata of Annotation Group Sequence item * @param {object} options.client - DICOMweb client * * @returns {Promise&lt;TypedArray|null>} Graphic index * * @private */ async function _fetchGraphicIndex ({ metadataItem, bulkdataItem, client }) { const uid = metadataItem.AnnotationGroupUID const graphicType = metadataItem.GraphicType if ('LongPrimitivePointIndexList' in metadataItem) { return metadataItem.LongPrimitivePointIndexList } else { if (bulkdataItem == null) { if (graphicType === 'POLYGON') { throw new Error( `Could not find bulkdata of annotation group "${uid}".` ) } else { return null } } else { if ('LongPrimitivePointIndexList' in bulkdataItem) { console.info(`fetch point index list of annotation group "${uid}"`) return await _fetchBulkdata({ client, reference: bulkdataItem.LongPrimitivePointIndexList }) } else { if (graphicType === 'POLYGON') { throw new Error( 'Could not find "LongPrimitivePointIndexList" ' + `in bulkdata of annotation group "${uid}".` ) } else { return null } } } } } /** * Fetch measurement values of an annotation group. * * @param {object} options * @param {object} options.metadataItem - Metadata of Annotation Group Sequence item * @param {object} options.bulkdataItem - Bulkdata of Annotation Group Sequence item * @param {number} options.index - Zero-based index in the Measurements Sequence * @param {object} options.client - DICOMweb client * * @returns {Promise&lt;TypeArray>} Values * * @private */ async function _fetchMeasurementValues ({ metadataItem, bulkdataItem, index, client }) { const uid = metadataItem.AnnotationGroupUID const measurementMetadataItem = metadataItem.MeasurementsSequence[index] const valuesMetadataItem = measurementMetadataItem.MeasurementValuesSequence[0] if ('FloatingPointValues' in valuesMetadataItem) { return valuesMetadataItem.FloatingPointValues } else { if (bulkdataItem == null) { throw new Error( `Could not find bulkdata of annotation group "${uid}".` ) } else if (bulkdataItem.MeasurementsSequence == null) { throw new Error( `Could not find item #${index + 1} of "MeasurementSequence" ` + `in bulkdata of annotation group "${uid}".` ) } else { const measurementBulkdataItem = bulkdataItem.MeasurementsSequence[index] const valuesBulkdataItem = ( measurementBulkdataItem.MeasurementValuesSequence[0] ) if ('FloatingPointValues' in valuesBulkdataItem) { const nameItem = measurementMetadataItem.ConceptNameCodeSequence[0] const name = nameItem.CodeMeaning console.info( `fetch measurement values for measurement #${index} "${name}"` ) return await _fetchBulkdata({ client, reference: valuesBulkdataItem.FloatingPointValues }) } else { throw new Error( `Could not find "FloatingPointValues" in item #${index + 1} ` + 'of "MeasurementSequence" in bulkdata ' + `of annotation group "${uid}".` ) } } } } /** * Fetch measurement annotation indices of an annotation group. * * @param {object} options * @param {object} options.metadataItem - Metadata of Annotation Group Sequence item * @param {object} options.bulkdataItem - Bulkdata of Annotation Group Sequence item * @param {number} options.index - Zero-based index in the Measurements Sequence * @param {object} options.client - DICOMweb client * * @returns {Promise&lt;TypeArray|null>} Annotation indices * * @private */ async function _fetchMeasurementIndices ({ metadataItem, bulkdataItem, index, client }) { const uid = metadataItem.AnnotationGroupUID const measurementMetadataItem = metadataItem.MeasurementsSequence[index] const valuesMetadataItem = measurementMetadataItem.MeasurementValuesSequence[0] if ('AnnotationIndexList' in valuesMetadataItem) { return valuesMetadataItem.AnnotationIndexList } else { if (bulkdataItem == null) { throw new Error( `Could not find bulkdata of annotation group "${uid}".` ) } else if (bulkdataItem.MeasurementsSequence == null) { throw new Error( `Could not find item #${index + 1} of "MeasurementSequence" ` + `in bulkdata of annotation group "${uid}".` ) } else { const measurementBulkdataItem = bulkdataItem.MeasurementsSequence[index] const valuesBulkdataItem = ( measurementBulkdataItem .MeasurementValuesSequence[0] ) if ('AnnotationIndexList' in valuesBulkdataItem) { const nameItem = measurementMetadataItem.ConceptNameCodeSequence[0] const name = nameItem.CodeMeaning console.info( `fetch measurement indices for measurement #${index} "${name}"` ) return await _fetchBulkdata({ client, reference: valuesBulkdataItem.AnnotationIndexList }) } else { return null } } } } /** * Fetch all measurements of an annotation group. * * @param {object} options * @param {object} options.metadataItem - Metadata of Annotation Group Sequence item * @param {object} options.bulkdataItem - Bulkdata of Annotation Group Sequence item * @param {object} options.client - DICOMweb client * * @returns {Promise&lt;Array&lt;object>>} Name, values, and indices of measurements * * @private */ async function _fetchMeasurements ({ metadataItem, bulkdataItem, client }) { const measurements = [] if (metadataItem.MeasurementsSequence !== undefined) { for (let i = 0; i &lt; metadataItem.MeasurementsSequence.length; i++) { const item = metadataItem.MeasurementsSequence[i] const name = item.ConceptNameCodeSequence[0] const unit = item.MeasurementUnitsCodeSequence[0] const values = await _fetchMeasurementValues({ metadataItem, bulkdataItem, index: i, client }) const indices = await _fetchMeasurementIndices({ metadataItem, bulkdataItem, index: i, client }) measurements.push({ name, unit, values, indices }) } } return measurements } /** * Fetch an individual measurement of an annotation group. * * @param {object} options * @param {object} options.metadataItem - Metadata of Annotation Group Sequence item * @param {object} options.bulkdataItem - Bulkdata of Annotation Group Sequence item * @param {object} options.index - Index of the Measurements Sequence item * @param {object} options.client - DICOMweb client * * @returns {Promise&lt;Array&lt;object>>} Name, values, and indices of measurements * * @private */ async function _fetchMeasurement ({ metadataItem, bulkdataItem, index, client }) { if (metadataItem.MeasurementsSequence == null) { throw new Error( 'Measurements Sequence element is not contained in metadata.' ) } if (metadataItem.MeasurementsSequence.length === 0) { throw new Error( 'Measurements Sequence element in empty.' ) } const item = metadataItem.MeasurementsSequence[index] if (item == null) { throw new Error( `Measurements Sequence does not contain an item #${index}.` ) } const name = item.ConceptNameCodeSequence[0] const unit = item.MeasurementUnitsCodeSequence[0] const values = await _fetchMeasurementValues({ metadataItem, bulkdataItem, index, client }) const indices = await _fetchMeasurementIndices({ metadataItem, bulkdataItem, index, client }) return { name, unit, values, indices } } /** * Get dimensionality of coordinates. * * @param {object} metadataItem - Metadata of Annotation Group Sequence item * * @returns {number} Dimensionality (2 or 3) * * @private */ function _getCoordinateDimensionality (metadataItem) { if (metadataItem.CommonZCoordinateValue == null) { if (metadataItem.AnnotationCoordinateType === '2D') { return 2 } return 3 } return 2 } /** * Get common Z coordinate value that is shared across all annotations. * * @param {object} metadataItem - Metadata of Annotation Group Sequence item * * @returns {number} Value (NaN in case there is no common Z coordinate) * * @private */ function _getCommonZCoordinate (metadataItem) { if (metadataItem.CommonZCoordinateValue == null) { return Number.NaN } return Number(metadataItem.CommonZCoordinateValue) } /** * Get coordinates of an annotation. * * @param {TypedArray} graphicData - Points coordinates of all annotations * @param {number} offset - Offset for the annotation of interest. * @param {number} commonZCoordinate - Z coordinate that is shared across annotations. * * @returns {Array&lt;number>} (x, y, z) or (column, row, slice) coordinates * * @private */ function _getCoordinates (graphicData, offset, commonZCoordinate) { const point = [ graphicData[offset], graphicData[offset + 1] ] if (isNaN(commonZCoordinate)) { point.push(graphicData[offset + 2]) } else { point.push(commonZCoordinate) } return point } /** * Get coordinates of a POINT annotation. * * @param {TypedArray} graphicData - Point coordinates of all annotations * @param {TypedArray} graphicIndex - Annotation index of all annotations * @param {number} coordinateDimensionality - Dimensionality of stored coordinates. * @param {number} commonZCoordinate - Z coordinate that is shared across annotations. * @param {number} annotationIndex - Index of the annotation of interest. * @param {number} numberOfAnnotations - Total number of annotations. * * @returns {Array&lt;number>} (x, y, z) or (column, row, slice) coordinates * * @private */ function _getPoint ( graphicData, graphicIndex, coordinateDimensionality, commonZCoordinate, annotationIndex, numberOfAnnotations ) { const length = coordinateDimensionality const offset = annotationIndex * length return _getCoordinates(graphicData, offset, commonZCoordinate) } /** * Get coordinates of the centroid point of a RECTANGLE annotation. * * @param {TypedArray} graphicData - Point coordinates of all annotations * @param {TypedArray} graphicIndex - Annotation index of all annotations * @param {number} coordinateDimensionality - Dimensionality of stored coordinates. * @param {number} commonZCoordinate - Z coordinate that is shared across annotations. * @param {number} annotationIndex - Index of the annotation of interest. * @param {number} numberOfAnnotations - Total number of annotations. * * @returns {Array&lt;number>} (x, y, z) or (column, row, slice) coordinates * * @private */ function _getRectangleCentroid ( graphicData, graphicIndex, coordinateDimensionality, commonZCoordinate, annotationIndex, numberOfAnnotations ) { const length = coordinateDimensionality * 4 const offset = annotationIndex * length const coordinates = [] for (let j = offset; j &lt; offset + length; j++) { const p = _getCoordinates(graphicData, j, commonZCoordinate) coordinates.push(p) j += coordinateDimensionality - 1 } const topLeft = coordinates[0] const topRight = coordinates[1] const bottomLeft = coordinates[3] return [ topLeft[0] + (topRight[0] - topLeft[0]) / 2, topLeft[1] + (topLeft[1] - bottomLeft[1]) / 2, 0 ] } /** * Get coordinates of the centroid point of an ELLIPSE annotation. * * @param {TypedArray} graphicData - Point coordinates of all annotations * @param {TypedArray} graphicIndex - Annotation index of all annotations * @param {number} coordinateDimensionality - Dimensionality of stored coordinates. * @param {number} commonZCoordinate - Z coordinate that is shared across annotations. * @param {number} annotationIndex - Index of the annotation of interest. * @param {number} numberOfAnnotations - Total number of annotations. * * @returns {Array&lt;number>} (x, y, z) or (column, row, slice) coordinates * * @private */ function _getEllipseCentroid ( graphicData, graphicIndex, coordinateDimensionality, commonZCoordinate, annotationIndex, numberOfAnnotations ) { const length = coordinateDimensionality * 4 const offset = annotationIndex * length const coordinates = [] for (let j = offset; j &lt; offset + length; j++) { const p = _getCoordinates(graphicData, j, commonZCoordinate) coordinates.push(p) j += coordinateDimensionality - 1 } const majorAxisFirstEndpoint = coordinates[0] const majorAxisSecondEndpoint = coordinates[1] return [ (majorAxisSecondEndpoint[0] - majorAxisFirstEndpoint[0]) / 2, (majorAxisSecondEndpoint[1] - majorAxisFirstEndpoint[1]) / 2, 0 ] } /** * Get coordinates of the centroid point of a POLYGON annotation. * * @param {TypedArray} graphicData - Point coordinates of all annotations * @param {TypedArray} graphicIndex - Annotation index of all annotations * @param {number} coordinateDimensionality - Dimensionality of stored coordinates. * @param {number} commonZCoordinate - Z coordinate that is shared across annotations. * @param {number} annotationIndex - Index of the annotation of interest. * @param {number} numberOfAnnotations - Total number of annotations. * * @returns {Array&lt;number>} (x, y, z) or (column, row, slice) coordinates * * @private */ function _getPolygonCentroid ( graphicData, graphicIndex, coordinateDimensionality, commonZCoordinate, annotationIndex, numberOfAnnotations ) { const offset = graphicIndex[annotationIndex] - 1 let length if (annotationIndex &lt; (numberOfAnnotations - 1)) { length = graphicIndex[annotationIndex + 1] - offset } else { length = graphicData.length } // https://en.wikipedia.org/wiki/Centroid#Of_a_polygon const point = [0, 0, 0] let area = 0 for (let j = offset; j &lt; offset + length; j++) { const p0 = _getCoordinates(graphicData, j, commonZCoordinate) let p1 if (j === (offset + length - 1)) { p1 = _getCoordinates(graphicData, offset, commonZCoordinate) } else { const nextOffset = j + coordinateDimensionality p1 = _getCoordinates(graphicData, nextOffset, commonZCoordinate) } const a = p0[0] * p1[1] - p1[0] * p0[1] area += a point[0] += (p0[0] + p1[0]) * a point[1] += (p0[1] + p1[1]) * a j += coordinateDimensionality - 1 } area *= 0.5 point[0] /= 6 * area point[1] /= 6 * area return point } /** * Get coordinates of the centroid point of an annotation. * * @param {string} graphicType - Type of annotations (POINT, POLYGON, etc.) * @param {TypedArray} graphicData - Point coordinates of all annotations * @param {TypedArray} graphicIndex - Annotation index of all annotations * @param {number} coordinateDimensionality - Dimensionality of stored coordinates. * @param {number} commonZCoordinate - Z coordinate that is shared across annotations. * @param {number} annotationIndex - Index of the annotation of interest. * @param {number} numberOfAnnotations - Total number of annotations. * * @returns {Array&lt;number>} (x, y, z) or (column, row, slice) coordinates * * @private */ const _getCentroid = ( graphicType, graphicData, graphicIndex, coordinateDimensionality, commonZCoordinate, annotationIndex, numberOfAnnotations ) => { if (graphicType === 'POINT') { return _getPoint( graphicData, graphicIndex, coordinateDimensionality, commonZCoordinate, annotationIndex, numberOfAnnotations ) } else if (graphicType === 'RECTANGLE') { return _getRectangleCentroid( graphicData, graphicIndex, coordinateDimensionality, commonZCoordinate, annotationIndex, numberOfAnnotations ) } else if (graphicType === 'ELLIPSE') { return _getEllipseCentroid( graphicData, graphicIndex, coordinateDimensionality, commonZCoordinate, annotationIndex, numberOfAnnotations ) } else if (graphicType === 'POLYGON') { return _getPolygonCentroid( graphicData, graphicIndex, coordinateDimensionality, commonZCoordinate, annotationIndex, numberOfAnnotations ) } else { throw new Error( `Encountered unexpected graphic type "${graphicType}".` ) } } export { AnnotationGroup, _fetchGraphicData, _fetchGraphicIndex, _fetchMeasurements, _fetchMeasurement, _getCentroid, _getCommonZCoordinate, _getCoordinateDimensionality } </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>