diff --git a/README.md b/README.md index 926ee8965..7396bbd7c 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ ## Dicom Image Toolkit for CornerstoneJS -### Current version: 2.1.11 +### Current version: 2.1.12 -### Latest Published Release: 2.1.11 +### Latest Published Release: 2.1.12 This library provides common DICOM functionalities to be used in web-applications: it's wrapper that simplifies the use of cornerstone-js environment. diff --git a/dist/larvitar.js b/dist/larvitar.js index dcae22bd7..1d1467ba3 100644 --- a/dist/larvitar.js +++ b/dist/larvitar.js @@ -11,61 +11,52 @@ return /******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ -/***/ "./imaging/imageTools.js": -/*!*******************************!*\ - !*** ./imaging/imageTools.js ***! - \*******************************/ +/***/ 4973: /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; +// ESM COMPAT FLAG __webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ addContoursTool: () => (/* binding */ addContoursTool), -/* harmony export */ addDefaultTools: () => (/* binding */ addDefaultTools), -/* harmony export */ addDiameterTool: () => (/* binding */ addDiameterTool), -/* harmony export */ addMaskEditingTool: () => (/* binding */ addMaskEditingTool), -/* harmony export */ addSeedsTool: () => (/* binding */ addSeedsTool), -/* harmony export */ addStackStateToElement: () => (/* binding */ addStackStateToElement), -/* harmony export */ addToolStateSingleSlice: () => (/* binding */ addToolStateSingleSlice), -/* harmony export */ clearCornerstoneElements: () => (/* binding */ clearCornerstoneElements), -/* harmony export */ clearMeasurements: () => (/* binding */ clearMeasurements), -/* harmony export */ clearToolStateByName: () => (/* binding */ clearToolStateByName), -/* harmony export */ getCurrentMaskData: () => (/* binding */ getCurrentMaskData), -/* harmony export */ getToolState: () => (/* binding */ getToolState), -/* harmony export */ setSegmentationConfig: () => (/* binding */ setSegmentationConfig), -/* harmony export */ setToolActive: () => (/* binding */ setToolActive), -/* harmony export */ setToolDisabled: () => (/* binding */ setToolDisabled), -/* harmony export */ setToolEnabled: () => (/* binding */ setToolEnabled), -/* harmony export */ setToolPassive: () => (/* binding */ setToolPassive), -/* harmony export */ syncToolStack: () => (/* binding */ syncToolStack), -/* harmony export */ updateDiameterTool: () => (/* binding */ updateDiameterTool), -/* harmony export */ updateStackToolState: () => (/* binding */ updateStackToolState) -/* harmony export */ }); -/* harmony import */ var cornerstone_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! cornerstone-core */ "./node_modules/cornerstone-core/dist/cornerstone.js"); -/* harmony import */ var cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cornerstone_core__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! cornerstone-tools */ "./node_modules/cornerstone-tools/dist/cornerstoneTools.js"); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(cornerstone_tools__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! lodash */ "./node_modules/lodash/lodash.js"); -/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(lodash__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _tools_default__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./tools/default */ "./imaging/tools/default.ts"); -/* harmony import */ var _tools_default__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_tools_default__WEBPACK_IMPORTED_MODULE_3__); -/* harmony import */ var _tools_custom_contourTool__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./tools/custom/contourTool */ "./imaging/tools/custom/contourTool.js"); -/* harmony import */ var _tools_custom_editMaskTool__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./tools/custom/editMaskTool */ "./imaging/tools/custom/editMaskTool.js"); -/* harmony import */ var _tools_custom_diameterTool__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./tools/custom/diameterTool */ "./imaging/tools/custom/diameterTool.js"); -/* harmony import */ var _loaders_nrrdLoader__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./loaders/nrrdLoader */ "./imaging/loaders/nrrdLoader.ts"); -/* harmony import */ var _loaders_nrrdLoader__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(_loaders_nrrdLoader__WEBPACK_IMPORTED_MODULE_7__); -/* harmony import */ var _loaders_commonLoader__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./loaders/commonLoader */ "./imaging/loaders/commonLoader.ts"); -/* harmony import */ var _loaders_commonLoader__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(_loaders_commonLoader__WEBPACK_IMPORTED_MODULE_8__); -/* harmony import */ var _imageContours__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./imageContours */ "./imaging/imageContours.ts"); -/* harmony import */ var _imageUtils__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./imageUtils */ "./imaging/imageUtils.ts"); -/* harmony import */ var _imageUtils__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(_imageUtils__WEBPACK_IMPORTED_MODULE_10__); -/** @module imaging/imageTools - * @desc This file provides functionalities for - * interacting with cornerstone tools - * DEPRECATION WARNING: these are legacy functions - * that will be removed soon. Use the corresponding - * functions in /tools/main.js instead. - * For this reason, this file will not be translated to TypeScript. + +// EXPORTS +__webpack_require__.d(__webpack_exports__, { + addContoursTool: () => (/* binding */ addContoursTool), + addDefaultTools: () => (/* binding */ addDefaultTools), + addDiameterTool: () => (/* binding */ addDiameterTool), + addMaskEditingTool: () => (/* binding */ addMaskEditingTool), + addSeedsTool: () => (/* binding */ addSeedsTool), + addStackStateToElement: () => (/* binding */ addStackStateToElement), + addToolStateSingleSlice: () => (/* binding */ addToolStateSingleSlice), + clearCornerstoneElements: () => (/* binding */ clearCornerstoneElements), + clearMeasurements: () => (/* binding */ clearMeasurements), + clearToolStateByName: () => (/* binding */ clearToolStateByName), + getCurrentMaskData: () => (/* binding */ getCurrentMaskData), + getToolState: () => (/* binding */ imageTools_getToolState), + setSegmentationConfig: () => (/* binding */ setSegmentationConfig), + setToolActive: () => (/* binding */ setToolActive), + setToolDisabled: () => (/* binding */ setToolDisabled), + setToolEnabled: () => (/* binding */ setToolEnabled), + setToolPassive: () => (/* binding */ setToolPassive), + syncToolStack: () => (/* binding */ syncToolStack), + updateDiameterTool: () => (/* binding */ updateDiameterTool), + updateStackToolState: () => (/* binding */ updateStackToolState) +}); + +// EXTERNAL MODULE: ./node_modules/cornerstone-core/dist/cornerstone.js +var cornerstone = __webpack_require__(7371); +var cornerstone_default = /*#__PURE__*/__webpack_require__.n(cornerstone); +// EXTERNAL MODULE: ./node_modules/cornerstone-tools/dist/cornerstoneTools.js +var cornerstoneTools = __webpack_require__(4030); +var cornerstoneTools_default = /*#__PURE__*/__webpack_require__.n(cornerstoneTools); +// EXTERNAL MODULE: ./node_modules/lodash/lodash.js +var lodash = __webpack_require__(6486); +// EXTERNAL MODULE: ./imaging/tools/default.ts +var tools_default = __webpack_require__(6694); +;// CONCATENATED MODULE: ./imaging/tools/custom/contourTool.js +/** @module imaging/tools/custom/contourTool + * @desc This file provides functionalities for + * rendering segmentation contours with a + * custom cornestone tool */ // external libraries @@ -75,2237 +66,1055 @@ __webpack_require__.r(__webpack_exports__); // internal libraries -// import { SeedsTool } from "./tools/custom/seedTool"; - - - - - +// cornerstone tools imports +const external = (cornerstoneTools_default()).external; +const EVENTS = (cornerstoneTools_default()).EVENTS; +const BaseAnnotationTool = cornerstoneTools_default().importInternal("base/BaseAnnotationTool"); +const getToolState = (cornerstoneTools_default()).getToolState; +const addToolState = (cornerstoneTools_default()).addToolState; +const removeToolState = (cornerstoneTools_default()).removeToolState; +const toolStyle = (cornerstoneTools_default()).toolStyle; +const toolColors = (cornerstoneTools_default()).toolColors; +const triggerEvent = cornerstoneTools_default().importInternal("util/triggerEvent"); +const pointInsideBoundingBox = cornerstoneTools_default().importInternal("util/pointInsideBoundingBox"); +const calculateSUV = cornerstoneTools_default().importInternal("util/calculateSUV"); +const numbersWithCommas = cornerstoneTools_default().importInternal("util/numbersWithCommas"); +const getNewContext = cornerstoneTools_default().importInternal("drawing/getNewContext"); +const draw = cornerstoneTools_default().importInternal("drawing/draw"); +const drawJoinedLines = cornerstoneTools_default().importInternal("drawing/drawJoinedLines"); +const drawHandles = cornerstoneTools_default().importInternal("drawing/drawHandles"); +const drawLinkedTextBox = cornerstoneTools_default().importInternal("drawing/drawLinkedTextBox"); +const clipToBox = cornerstoneTools_default().importInternal("util/clip"); +const freehandRoiCursor = cornerstoneTools_default().importInternal("cursors/freehandRoiCursor"); +const getLogger = cornerstoneTools_default().importInternal("util/getLogger"); +const throttle = cornerstoneTools_default().importInternal("util/throttle"); +const logger = getLogger("tools:annotation:FreehandRoiTool"); +const freehandUtils = cornerstoneTools_default().importInternal("util/freehandUtils"); +// TODO check how to import these +// const toolCursor = csTools.importInternal("store/setToolCursor"); +// const hideToolCursor = toolCursor.hideToolCursor; +// const setToolCursor = toolCursor.setToolCursor; +// const findAndMoveHelpers = csTools.importInternal("util/findAndMoveHelpers"); +// const moveHandleNearImagePoint = findAndMoveHelpers.moveHandleNearImagePoint; -/* - * This module provides the following functions to be exported: - * csToolsCreateStack(element) - * addDefaultTools(toolToActivate) - * clearMeasurements() - * addContoursTool(rawContours, maskName) - * addMaskEditingTool(seriesId,mask,setConfig,callback, targetViewport) - * getCurrentMaskData(viewportId) - * addStackStateToElement(seriesId, element) - * addSeedsTool(preLoadSeeds, initViewport) - * addDiameterTool(targetElementId, diameters, seriesId) - * setToolActive(toolName, options, activeViewport, viewports) - * setToolDisabled(toolName, options, activeViewport, viewports) - * setToolEnabled(toolName, options, activeViewport, viewports) - * setToolPassive(toolName, options, activeViewport, viewports) - * getToolState(toolName) - * updateDiameterTool(diameterId, value, seriesId) - * addToolStateSingleSlice(element, toolType, data, slice, seriesId) - * clearToolStateByName(toolName, options) - * clearCornerstoneElements() - * syncToolStack(srcSliceNumber, toolName, viewport, seriesId) - * updateStackToolState(element, imageIndex) - */ +const { + insertOrDelete, + freehandArea, + calculateFreehandStatistics, + freehandIntersect, + FreehandHandleData +} = freehandUtils; +const state = { + // Global + globalTools: {}, + globalToolChangeHistory: [], + // Tracking + enabledElements: [], + tools: [], + isToolLocked: false, + activeMultiPartTool: null, + mousePositionImage: {}, + // Settings + clickProximity: 6, + touchProximity: 10, + handleRadius: 6, + deleteIfHandleOutsideImage: true, + preventHandleOutsideImage: false, + // Cursor + svgCursorUrl: null +}; /** - * Add all default tools, as listed in tools/default.js - * @function addDefaultTools - * @deprecated (OBSOLETE) + * @public + * @class ContoursTool + * @memberof Tools.Annotation + * @classdesc Tool for drawing a set of contours + * @extends Tools.Base.BaseAnnotationTool */ -const addDefaultTools = function (toolToActivate) { - // for each default tool - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(_tools_default__WEBPACK_IMPORTED_MODULE_3__.DEFAULT_TOOLS, tool => { - // check if already added - if (!isToolMissing(tool.name)) { +class ContoursTool extends BaseAnnotationTool { + constructor(props = {}) { + const defaultProps = { + name: "ContoursTool", + supportedInteractionTypes: ["Mouse", "Touch"], + configuration: defaultFreehandConfiguration(), + svgCursor: freehandRoiCursor + }; + super(props, defaultProps); + this.initializeContours(props.contoursParsedData, props.segmentationName); + this.isMultiPartTool = true; + this._drawing = false; + this._dragging = false; + this._modifying = false; + + // Create bound callback functions for private event loops + this._drawingMouseDownCallback = this._drawingMouseDownCallback.bind(this); + this._drawingMouseMoveCallback = this._drawingMouseMoveCallback.bind(this); + this._drawingMouseDragCallback = this._drawingMouseDragCallback.bind(this); + this._drawingMouseUpCallback = this._drawingMouseUpCallback.bind(this); + this._drawingMouseDoubleClickCallback = this._drawingMouseDoubleClickCallback.bind(this); + this._editMouseUpCallback = this._editMouseUpCallback.bind(this); + this._editMouseDragCallback = this._editMouseDragCallback.bind(this); + this._drawingTouchStartCallback = this._drawingTouchStartCallback.bind(this); + this._drawingTouchDragCallback = this._drawingTouchDragCallback.bind(this); + this._drawingDoubleTapClickCallback = this._drawingDoubleTapClickCallback.bind(this); + this._editTouchDragCallback = this._editTouchDragCallback.bind(this); + this.throttledUpdateCachedStats = throttle(this.updateCachedStats, 110); + } + initializeContours(contourData, segmentationName) { + var elements = cornerstone_default().getEnabledElements(); + (0,lodash.each)(elements, el => { + var slices = contourData[el.element.id][segmentationName]; + for (var slice in slices) { + var linesPerSlice = contourData[el.element.id][segmentationName][slice].lines; + var lines = (0,lodash.map)(linesPerSlice, line => { + let dataToInject = { + visible: true, + active: false, + invalidated: false, + color: "#FF0000", + handles: { + points: line + } + }; + return dataToInject; + }); + addToolStateSingleSlice(el.element, "ContoursTool", lines, slice); + } + }); + } + createNewMeasurement(eventData) { + const goodEventData = eventData && eventData.currentPoints && eventData.currentPoints.image; + if (!goodEventData) { + logger.error(`required eventData not supplied to tool ${this.name}'s createNewMeasurement`); return; } - let configuration = tool.configuration; - let toolClass = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default())[tool.class]; + const measurementData = { + visible: true, + active: true, + invalidated: true, + color: undefined, + handles: { + points: [] + } + }; + measurementData.handles.textBox = { + active: false, + hasMoved: false, + movesIndependently: false, + drawnIndependently: true, + allowedOutsideImage: true, + hasBoundingBox: true + }; + return measurementData; + } - // check target viewports and call add tool with options - if (tool.viewports == "all") { - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().addTool(toolClass, { - configuration - }); - } else { - // call add tool for element for each element - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(tool.viewports, targetElement => { - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().addToolForElement(targetElement, toolClass, configuration); - }); + /** + * + * + * @param {*} element element + * @param {*} data data + * @param {*} coords coords + * @returns {Boolean} + */ + pointNearTool(element, data, coords) { + const validParameters = data && data.handles && data.handles.points; + if (!validParameters) { + throw new Error(`invalid parameters supplied to tool ${this.name}'s pointNearTool`); } - let elements = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElements(); - - // if sync tool, enable - if (tool.sync) { - const synchronizer = new (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().Synchronizer)("cornerstoneimagerendered", (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default())[tool.sync]); - elements.forEach(element => { - synchronizer.add(element.element); - }); - synchronizer.enabled = true; + if (!validParameters || data.visible === false) { + return false; } - if (tool.defaultActive || tool.name == toolToActivate) { - setToolActive(tool.name, tool.options); + const isPointNearTool = this._pointNearHandle(element, data, coords); + if (isPointNearTool !== undefined) { + return true; } - }); + return false; + } - // set wheel scroll active - setToolActive("CustomMouseWheelScroll", { - loop: false, - // default false - allowSkipping: false, - // default true - invert: false - }); -}; + /** + * @param {*} element + * @param {*} data + * @param {*} coords + * @returns {number} the distance in px from the provided coordinates to the + * closest rendered portion of the annotation. -1 if the distance cannot be + * calculated. + */ + distanceFromPoint(element, data, coords) { + let distance = Infinity; + for (let i = 0; i < data.handles.points.length; i++) { + const distanceI = external.cornerstoneMath.point.distance(data.handles.points[i], coords); + distance = Math.min(distance, distanceI); + } -/** - * Add Diameter tool - * @function addDiameterTool - * @param {String} elementId - The target hmtl element id or its DOM HTMLElement - * @param {Array} diameters - The array of diameter objects. - * @param {String} seriesId - The id of the target serie. - */ -const addDiameterTool = function (elementId, diameters, seriesId) { - if (isToolMissing("Diameter")) { - let element = (0,_imageUtils__WEBPACK_IMPORTED_MODULE_10__.isElement)(elementId) ? elementId : document.getElementById(elementId); - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().addToolForElement(element, _tools_custom_diameterTool__WEBPACK_IMPORTED_MODULE_6__.DiameterTool, { - dataArray: diameters, - seriesId: seriesId - }); - setToolPassive("Diameter"); + // If an error caused distance not to be calculated, return -1. + if (distance === Infinity) { + return -1; + } + return distance; } -}; -/** - * Add Contour tool - * @function addContoursTool - * @param {Object} rawContours - The contours object (generated from a segmentation mask). - * @param {String} maskName - The name tag that identify the mask - */ -const addContoursTool = function (rawContours, maskName) { - var pointBatchSize = 2; - console.time("...parsing contours"); - var contoursParsedData = (0,_imageContours__WEBPACK_IMPORTED_MODULE_9__.parseContours)(rawContours, pointBatchSize, maskName); - console.timeEnd("...parsing contours"); - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().addTool(_tools_custom_contourTool__WEBPACK_IMPORTED_MODULE_4__.ContoursTool, { - contoursParsedData, - maskName - }); -}; + /** + * @param {*} element + * @param {*} data + * @param {*} coords + * @returns {number} the distance in canvas units from the provided coordinates to the + * closest rendered portion of the annotation. -1 if the distance cannot be + * calculated. + */ + distanceFromPointCanvas(element, data, coords) { + let distance = Infinity; + if (!data) { + return -1; + } + const canvasCoords = external.cornerstone.pixelToCanvas(element, coords); + const points = data.handles.points; + for (let i = 0; i < points.length; i++) { + const handleCanvas = external.cornerstone.pixelToCanvas(element, points[i]); + const distanceI = external.cornerstoneMath.point.distance(handleCanvas, canvasCoords); + distance = Math.min(distance, distanceI); + } -/** - * Add mask editing tool - * @function addMaskEditingTool - * @param {Array} mask - The mask data. - * @param {Function} callback - The tool initialization callback - * @param {String} targetViewport - The target hmtl element id. - */ -const addMaskEditingTool = function (mask, callback, targetViewport) { - let enabledElements = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElements(); - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(enabledElements, el => { - if (el.element.id == targetViewport) { - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().addToolForElement(el.element, _tools_custom_editMaskTool__WEBPACK_IMPORTED_MODULE_5__.EditMaskTool, { - mask: mask, - initCallback: callback, - configuration: { - alwaysEraseOnClick: false - } - }); - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().setToolEnabledForElement(el.element, "EditMask", { - mouseButtonMask: 1 - }); + // If an error caused distance not to be calculated, return -1. + if (distance === Infinity) { + return -1; } - }); - let defaultConfig = { - radius: 5, - fillAlpha: 0.5 - }; - setSegmentationConfig(defaultConfig); -}; + return distance; + } -/** - * Modify configuration for cornerstone tools segmentation module - * @function setSegmentationConfig - * @param {Object} config - The custom configuration. - * @example - * Example of custom configuration - * config = { - renderOutline: true, - renderFill: true, - shouldRenderInactiveLabelmaps: true, - radius: 10, - minRadius: 1, - maxRadius: 50, - segmentsPerLabelmap: 65535, - fillAlpha: 0.7, - fillAlphaInactive: 0.1, - outlineAlpha: 0.7, - outlineAlphaInactive: 0.35, - outlineWidth: 3, - storeHistory: true + /** + * + * + * + * @param {Object} image image + * @param {Object} element element + * @param {Object} data data + * + * @returns {void} void + */ + updateCachedStats(image, element, data) { + // Define variables for the area and mean/standard deviation + let meanStdDev, meanStdDevSUV; + const seriesModule = external.cornerstone.metaData.get("generalSeriesModule", image.imageId); + const modality = seriesModule ? seriesModule.modality : null; + const points = data.handles.points; + // If the data has been invalidated, and the tool is not currently active, + // We need to calculate it again. + + // Retrieve the bounds of the ROI in image coordinates + const bounds = { + left: points[0].x, + right: points[0].x, + bottom: points[0].y, + top: points[0].x + }; + for (let i = 0; i < points.length; i++) { + bounds.left = Math.min(bounds.left, points[i].x); + bounds.right = Math.max(bounds.right, points[i].x); + bounds.bottom = Math.min(bounds.bottom, points[i].y); + bounds.top = Math.max(bounds.top, points[i].y); + } + const polyBoundingBox = { + left: bounds.left, + top: bounds.bottom, + width: Math.abs(bounds.right - bounds.left), + height: Math.abs(bounds.top - bounds.bottom) }; - */ -const setSegmentationConfig = function (config) { - let { - configuration - } = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().getModule("segmentation"); - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.extend)(configuration, config); - let enabledElements = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElements(); - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(enabledElements, el => { - cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().updateImage(el.element); - }); -}; -/** - * Get mask editing tool current data from state - * @function getCurrentMaskData - * @param {String} viewportId - The target hmtl element id. - * @return {Array} labelmap3D - The mask array - */ -const getCurrentMaskData = function (viewportId) { - const { - getters - } = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().getModule("segmentation"); - let enabledElement = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElements().filter(e => e.element.id == viewportId).pop(); - const { - labelmap3D - } = getters.labelmap2D(enabledElement.element); - return labelmap3D; -}; + // Store the bounding box information for the text box + data.polyBoundingBox = polyBoundingBox; -/** - * Add Stack State to a single hmtl element - * @function addStackStateToElement - * @param {String} seriesId - The id of the target serie. - * @param {HTMLElement} element - The target hmtl element. - */ -const addStackStateToElement = function (seriesId, element) { - // Define the Stack object - const stack = (0,_loaders_commonLoader__WEBPACK_IMPORTED_MODULE_8__.getSeriesDataFromLarvitarManager)(seriesId)[element.id]; - // Add the stack tool state to the enabled element - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().addStackStateManager(element, ["stack"]); - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().addToolState(element, "stack", stack); -}; + // First, make sure this is not a color image, since no mean / standard + // Deviation will be calculated for color images. + if (!image.color) { + // Retrieve the array of pixels that the ROI bounds cover + const pixels = external.cornerstone.getPixels(element, polyBoundingBox.left, polyBoundingBox.top, polyBoundingBox.width, polyBoundingBox.height); -/** - * Add seeds tool - * @function addSeedsTool - * @param {Array} preLoadSeeds - The array of seeds to load as initialization. - * @param {String} initViewport - The hmtl element id to be used for tool initialization. - */ -const addSeedsTool = function (preLoadSeeds, initViewport) { - if (isToolMissing("Seeds")) { - let enabledElements = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElements(); - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(enabledElements, el => { - let initialize = el.element.id == initViewport; - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().addToolForElement(el.element, SeedsTool, { - preLoadSeeds, - initialize, - initViewport - }); - }); - setToolEnabled("Seeds"); - } -}; + // Calculate the mean & standard deviation from the pixels and the object shape + meanStdDev = calculateFreehandStatistics.call(this, pixels, polyBoundingBox, data.handles.points); + if (modality === "PT") { + // If the image is from a PET scan, use the DICOM tags to + // Calculate the SUV from the mean and standard deviation. -/** - * Delete all measurements from tools state, for tools that have the "cleaneable" prop set to true in tools/default.js - * @function clearMeasurements - */ -const clearMeasurements = function () { - let enabledElements = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElements(); - let clenableTools = (0,lodash__WEBPACK_IMPORTED_MODULE_2__.filter)(_tools_default__WEBPACK_IMPORTED_MODULE_3__.DEFAULT_TOOLS, "cleanable"); - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(enabledElements, el => { - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(clenableTools, function (tool) { - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().clearToolState(el.element, tool.name); - }); - }); - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(enabledElements, el => { - cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().updateImage(el.element); - }); -}; + // Note that because we are using modality pixel values from getPixels, and + // The calculateSUV routine also rescales to modality pixel values, we are first + // Returning the values to storedPixel values before calcuating SUV with them. + // TODO: Clean this up? Should we add an option to not scale in calculateSUV? + meanStdDevSUV = { + mean: calculateSUV(image, (meanStdDev.mean - image.intercept) / image.slope), + stdDev: calculateSUV(image, (meanStdDev.stdDev - image.intercept) / image.slope) + }; + } -/** - * Set Tool "active" on all elements (ie, rendered and manipulable) & refresh cornerstone elements - * @function setToolActive - * @param {String} toolName - The tool name. - * @param {Object} options - The custom options. @default from tools/default.js - * @param {String} activeViewport - The active viewport (if "all", viewports array will be used) - * @param {Array} viewports - The hmtl element id to be used for tool initialization. - * @deprecated (OBSOLETE) - */ -const setToolActive = function (toolName, options, activeViewport, viewports) { - let defaultOpt = _tools_default__WEBPACK_IMPORTED_MODULE_3__.DEFAULT_TOOLS[toolName]?.options || {}; - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.extend)(defaultOpt, options); - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().setToolActive(toolName, defaultOpt); - if (activeViewport == "all") { - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(viewports, function (elementId) { - let el = document.getElementById(elementId); - if (el) { - cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().updateImage(el); + // If the mean and standard deviation values are sane, store them for later retrieval + if (meanStdDev && !isNaN(meanStdDev.mean)) { + data.meanStdDev = meanStdDev; + data.meanStdDevSUV = meanStdDevSUV; } - }); - } else { - let el = document.getElementById(activeViewport); - if (el) { - cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().updateImage(el); } - } -}; -/** - * Set Tool "disabled" on all elements (ie, not rendered) & refresh cornerstone elements - * @function setToolDisabled - * @param {String} toolName - The tool name. - * @param {String} activeViewport - The active viewport (if "all", viewports array will be used) - * @param {Array} viewports - The hmtl element id to be used for tool initialization. - * @deprecated (OBSOLETE) - */ -const setToolDisabled = function (toolName, activeViewport, viewports) { - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().setToolDisabled(toolName); - if (activeViewport == "all") { - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(viewports, function (elementId) { - let el = document.getElementById(elementId); - if (el) { - cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().updateImage(el); - } - }); - } else { - let el = document.getElementById(activeViewport); - if (el) { - cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().updateImage(el); + // Retrieve the pixel spacing values, and if they are not + // Real non-zero values, set them to 1 + const columnPixelSpacing = image.columnPixelSpacing || 1; + const rowPixelSpacing = image.rowPixelSpacing || 1; + const scaling = columnPixelSpacing * rowPixelSpacing; + const area = freehandArea(data.handles.points, scaling); + + // If the area value is sane, store it for later retrieval + if (!isNaN(area)) { + data.area = area; } + + // Set the invalidated flag to false so that this data won't automatically be recalculated + data.invalidated = false; } -}; -/** - * Set Tool "enabled" on all elements (ie, rendered but not manipulable) & refresh cornerstone elements - * @function setToolEnabled - * @param {String} toolName - The tool name. - * @param {String} activeViewport - The active viewport (if "all", viewports array will be used) - * @param {Array} viewports - The hmtl element id to be used for tool initialization. - * @deprecated (OBSOLETE) - */ -const setToolEnabled = function (toolName, activeViewport, viewports) { - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().setToolEnabled(toolName); - if (activeViewport == "all") { - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(viewports, function (elementId) { - let el = document.getElementById(elementId); - if (el) { - cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().updateImage(el); - } - }); - } else { - let el = document.getElementById(activeViewport); - if (el) { - cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().updateImage(el); + /** + * + * + * @param {*} evt + * @returns {undefined} + */ + renderToolData(evt) { + const eventData = evt.detail; + + // If we have no toolState for this element, return immediately as there is nothing to do + const toolState = getToolState(evt.currentTarget, this.name); + if (!toolState) { + return; } - } -}; + const { + image, + element + } = eventData; + const config = this.configuration; + const seriesModule = external.cornerstone.metaData.get("generalSeriesModule", image.imageId); + const modality = seriesModule ? seriesModule.modality : null; -/** - * Set Tool "enabled" on all elements (ie, rendered and manipulable passively) & refresh cornerstone elements - * @function setToolPassive - * @param {String} toolName - The tool name. - * @param {String} activeViewport - The active viewport (if "all", viewports array will be used) - * @param {Array} viewports - The hmtl element id to be used for tool initialization. - * @deprecated (OBSOLETE) - */ -const setToolPassive = function (toolName, activeViewport, viewports) { - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().setToolPassive(toolName); - if (activeViewport == "all") { - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(viewports, function (elementId) { - let el = document.getElementById(elementId); - if (el) { - cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().updateImage(el); + // We have tool data for this element - iterate over each one and draw it + const context = getNewContext(eventData.canvasContext.canvas); + const lineWidth = toolStyle.getToolWidth(); + for (let i = 0; i < toolState.data.length; i++) { + const data = toolState.data[i]; + if (data.visible === false) { + continue; } - }); - } else { - let el = document.getElementById(activeViewport); - if (el) { - cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().updateImage(el); + draw(context, context => { + let color = toolColors.getColorIfActive(data); + let fillColor; + if (data.active) { + if (data.handles.invalidHandlePlacement) { + color = config.invalidColor; + fillColor = config.invalidColor; + } else { + color = toolColors.getColorIfActive(data); + fillColor = toolColors.getFillColor(); + } + } else { + fillColor = toolColors.getToolColor(); + } + if (data.handles.points.length) { + for (let j = 0; j < data.handles.points.length; j++) { + const lines = [...data.handles.points[j].lines]; + const points = data.handles.points; + if (j === points.length - 1 && !data.polyBoundingBox) { + // If it's still being actively drawn, keep the last line to + // The mouse location + lines.push(config.mouseLocation.handles.start); + } + drawJoinedLines(context, element, data.handles.points[j], lines, { + color + }); + } + } + + // Draw handles + + const options = { + color, + fill: fillColor + }; + if (config.alwaysShowHandles || data.active && data.polyBoundingBox) { + // Render all handles + options.handleRadius = config.activeHandleRadius; + drawHandles(context, eventData, data.handles.points, options); + } + if (data.canComplete) { + // Draw large handle at the origin if can complete drawing + options.handleRadius = config.completeHandleRadius; + const handle = data.handles.points[0]; + drawHandles(context, eventData, [handle], options); + } + if (data.active && !data.polyBoundingBox) { + // Draw handle at origin and at mouse if actively drawing + options.handleRadius = config.activeHandleRadius; + drawHandles(context, eventData, config.mouseLocation.handles, options); + const firstHandle = data.handles.points[0]; + drawHandles(context, eventData, [firstHandle], options); + } + + // Update textbox stats + if (data.invalidated === true && !data.active) { + if (data.meanStdDev && data.meanStdDevSUV && data.area) { + this.throttledUpdateCachedStats(image, element, data); + } else { + this.updateCachedStats(image, element, data); + } + } + + // Only render text if polygon ROI has been completed and freehand 'shiftKey' mode was not used: + if (data.polyBoundingBox && !data.handles.textBox.freehand) { + // If the textbox has not been moved by the user, it should be displayed on the right-most + // Side of the tool. + if (!data.handles.textBox.hasMoved) { + // Find the rightmost side of the polyBoundingBox at its vertical center, and place the textbox here + // Note that this calculates it in image coordinates + data.handles.textBox.x = data.polyBoundingBox.left + data.polyBoundingBox.width; + data.handles.textBox.y = data.polyBoundingBox.top + data.polyBoundingBox.height / 2; + } + const text = textBoxText.call(this, data); + drawLinkedTextBox(context, element, data.handles.textBox, text, data.handles.points, textBoxAnchorPoints, color, lineWidth, 0, true); + } + }); } - } -}; + function textBoxText(data) { + const { + meanStdDev, + meanStdDevSUV, + area + } = data; + // Define an array to store the rows of text for the textbox + const textLines = []; -/** - * Get tool data for all enabled elements - * @function getToolState - * @param {String} toolName - The tool name. - * @return {Object} - Tool data grouped by element id - */ -const getToolState = function (toolName) { - let enabledElements = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElements(); - let toolData = {}; - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(enabledElements, el => { - toolData[el.element.id] = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().getToolState(el.element, toolName); - }); - return toolData; -}; + // If the mean and standard deviation values are present, display them + if (meanStdDev && meanStdDev.mean !== undefined) { + // If the modality is CT, add HU to denote Hounsfield Units + let moSuffix = ""; + if (modality === "CT") { + moSuffix = "HU"; + } + data.unit = moSuffix; -/** - * Clear tool data for a subset of seeds - * @function clearToolStateByName - * @param {String} toolName - The tool name. - * @param {Object} options - Props used to select the data to delete (at the moment only {name : "targetName"} is implemented) - */ -const clearToolStateByName = function (toolName, options) { - let enabledElements = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElements(); - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(enabledElements, el => { - const toolStateManager = el.toolStateManager; - let imageIds = Object.keys(toolStateManager.toolState); - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(imageIds, imageId => { - let toolData = toolStateManager.toolState[imageId]; - if (toolData[toolName]) { - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.remove)(toolData[toolName].data, singleData => { - return singleData.name == options.name; - }); - } - }); - }); - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(enabledElements, el => { - cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().updateImage(el.element); - }); -}; + // Create a line of text to display the mean and any units that were specified (i.e. HU) + let meanText = `Mean: ${numbersWithCommas(meanStdDev.mean.toFixed(2))} ${moSuffix}`; + // Create a line of text to display the standard deviation and any units that were specified (i.e. HU) + let stdDevText = `StdDev: ${numbersWithCommas(meanStdDev.stdDev.toFixed(2))} ${moSuffix}`; -/** - * Update diameter tool with new value (removing old one) - * @function updateDiameterTool - * @param {String | Number} diameterId - The id that identify the diameter data. - * @param {Object} value - The object representing the new diameter data. - * @param {String} seriesId - The target serie id. - * @param {String} viewportId - The viewport id. - */ -const updateDiameterTool = function (diameterId, value, seriesId, viewportId) { - // clear target diameter - if (!diameterId) { - console.warn("no diameterId, return"); - return; - } - clearToolStateByName("Diameter", { - name: diameterId - }); - // insert new one - let data = { - toolType: "Diameter", - name: diameterId, - isCreating: true, - visible: true, - active: false, - invalidated: false, - handles: { - start: { - x: value.tool.x1, - y: value.tool.y1, - index: 0, - drawnIndependently: false, - allowedOutsideImage: false, - highlight: true, - active: false - }, - end: { - x: value.tool.x2, - y: value.tool.y2, - index: 1, - drawnIndependently: false, - allowedOutsideImage: false, - highlight: true, - active: false - }, - perpendicularStart: { - x: value.tool.x3, - y: value.tool.y3, - index: 2, - drawnIndependently: false, - allowedOutsideImage: false, - highlight: true, - active: false, - locked: false - }, - perpendicularEnd: { - x: value.tool.x4, - y: value.tool.y4, - index: 3, - drawnIndependently: false, - allowedOutsideImage: false, - highlight: true, - active: false - }, - textBox: { - x: value.tool.value_max, - y: value.tool.value_min, - index: null, - drawnIndependently: true, - allowedOutsideImage: true, - highlight: false, - active: false, - hasMoved: true, - movesIndependently: false, - hasBoundingBox: true, - boundingBox: { - width: 59.6484375, - height: 47, - left: 165.02487562189057, - top: 240.53482587064684 + // If this image has SUV values to display, concatenate them to the text line + if (meanStdDevSUV && meanStdDevSUV.mean !== undefined) { + const SUVtext = " SUV: "; + meanText += SUVtext + numbersWithCommas(meanStdDevSUV.mean.toFixed(2)); + stdDevText += SUVtext + numbersWithCommas(meanStdDevSUV.stdDev.toFixed(2)); } + + // Add these text lines to the array to be displayed in the textbox + textLines.push(meanText); + textLines.push(stdDevText); } - }, - longestDiameter: value.tool.value_max.toString(), - shortestDiameter: value.tool.value_min.toString() - }; - let sliceNumber = value.tool.slice; - let enabledElement = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElements().filter(el => el.element.id == viewportId).pop(); - // add to master viewport - addToolStateSingleSlice(enabledElement.element, "Diameter", data, sliceNumber, seriesId); -}; + // If the area is a sane value, display it + if (area) { + // Determine the area suffix based on the pixel spacing in the image. + // If pixel spacing is present, use millimeters. Otherwise, use pixels. + // This uses Char code 178 for a superscript 2 + let suffix = ` mm${String.fromCharCode(178)}`; + if (!image.rowPixelSpacing || !image.columnPixelSpacing) { + suffix = ` pixels${String.fromCharCode(178)}`; + } -/** - * Add tool data for a single target slice - * @function addToolStateSingleSlice - * @param {HTMLElement} element - The target hmtl element. - * @param {String} toolName - The tool name. - * @param {Object | Array} data - The tool data to add (tool-specific) - * @param {Number} slice - The target slice to put the data in. - * @param {String} seriesId - The target serie id. - */ -const addToolStateSingleSlice = function (element, toolName, data, slice, seriesId) { - const enabledElement = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElement(element); - if (!enabledElement.image) { - console.warn("no image"); - return; + // Create a line of text to display the area and its units + const areaText = `Area: ${numbersWithCommas(area.toFixed(2))}${suffix}`; + + // Add this text line to the array to be displayed in the textbox + textLines.push(areaText); + } + return textLines; + } + function textBoxAnchorPoints(handles) { + return handles; + } } - let targetImageId = (0,_loaders_nrrdLoader__WEBPACK_IMPORTED_MODULE_7__.getImageIdFromSlice)(slice, element.id, seriesId); - if (enabledElement.toolStateManager === undefined) { - console.warn("State Manager undefined"); - return; + addNewMeasurement(evt) { + const eventData = evt.detail; + this._startDrawing(evt); + this._addPoint(eventData); + preventPropagation(evt); } - let toolState = enabledElement.toolStateManager.toolState; - if (toolState.hasOwnProperty(targetImageId) === false) { - toolState[targetImageId] = {}; + preMouseDownCallback(evt) { + const eventData = evt.detail; + const nearby = this._pointNearHandleAllTools(eventData); + if (eventData.event.ctrlKey) { + if (nearby !== undefined && nearby.handleNearby.hasBoundingBox) { + // Ctrl + clicked textBox, do nothing but still consume event. + } else { + insertOrDelete.call(this, evt, nearby); + } + preventPropagation(evt); + return true; + } + return false; } - const imageIdToolState = toolState[targetImageId]; + handleSelectedCallback(evt, toolData, handle, interactionType = "mouse") { + const { + element + } = evt.detail; + const toolState = getToolState(element, this.name); + console.info(interactionType); + // if (handle.hasBoundingBox) { + // // Use default move handler. + // moveHandleNearImagePoint(evt, this, toolData, handle, interactionType); - // If we don't have tool state for this type of tool, add an empty object - if (imageIdToolState.hasOwnProperty(toolName) === false) { - imageIdToolState[toolName] = { - data: [] + // return; + // } + + const config = this.configuration; + config.dragOrigin = { + x: handle.x, + y: handle.y }; - } - const toolData = imageIdToolState[toolName]; - // if an array is provided, override data - // if (Array.isArray(data)) { - // toolData.data = data; - // } else { - // toolData.data.push(data); - // } + // Iterating over handles of all toolData instances to find the indices of the selected handle + for (let toolIndex = 0; toolIndex < toolState.data.length; toolIndex++) { + const points = toolState.data[toolIndex].handles.points; + for (let p = 0; p < points.length; p++) { + if (points[p] === handle) { + config.currentHandle = p; + config.currentTool = toolIndex; + } + } + } + this._modifying = true; + this._activateModify(element); - // This implementation works better - let singledata = typeof data.pop == "function" ? data.pop() : data; - // remove old data for this id (avoid doubling contours) // TODO generalize - if (toolName == "ContoursTool") { - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.remove)(toolData.data, entry => entry && entry.id == singledata.id); + // Interupt eventDispatchers + preventPropagation(evt); } - toolData.data.push(singledata); -}; -/** - * Clear tool state and disable all cornerstone elements - * @function clearCornerstoneElements - */ -const clearCornerstoneElements = function () { - var enabledElements = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElements(); - var inMemElements = (0,lodash__WEBPACK_IMPORTED_MODULE_2__.cloneDeep)(enabledElements); // copy before modifying - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(inMemElements, el => { - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(_tools_default__WEBPACK_IMPORTED_MODULE_3__.DEFAULT_TOOLS, function (tool) { - if (tool.cleanable) { - cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().clearToolState(el.element, tool.name); - } - }); - cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().disable(el.element); - }); -}; + /** + * Event handler for MOUSE_MOVE during drawing event loop. + * + * @event + * @param {Object} evt - The event. + * @returns {undefined} + */ + _drawingMouseMoveCallback(evt) { + const eventData = evt.detail; + const { + currentPoints, + element + } = eventData; + const toolState = getToolState(element, this.name); + const config = this.configuration; + const currentTool = config.currentTool; + const data = toolState.data[currentTool]; + const coords = currentPoints.canvas; -/** - * Sync the cornerstone tools stack given a slice as data source - * @function syncToolStack - * @param {Number} srcSliceNumber - The slice to be used as data source. - * @param {String} toolName - The name of the tool to sync. - * @param {String} viewport - The target viewport id. - * @param {String} seriesId - The target serie id. - */ -const syncToolStack = function (srcSliceNumber, toolName, viewport, seriesId) { - // get the imageIds array - let seriesData = (0,_loaders_commonLoader__WEBPACK_IMPORTED_MODULE_8__.getSeriesDataFromLarvitarManager)(seriesId); - let imageIds = seriesData[viewport].imageIds; + // Set the mouseLocation handle + this._getMouseLocation(eventData); + this._checkInvalidHandleLocation(data, eventData); - // get the tool state of source imageId - let element = document.getElementById(viewport); - let enabledElement = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElement(element); - let srcImageId = (0,_loaders_nrrdLoader__WEBPACK_IMPORTED_MODULE_7__.getImageIdFromSlice)(srcSliceNumber, viewport, seriesId); - let srcImageToolState = enabledElement.toolStateManager.toolState[srcImageId][toolName]; - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(Object.keys(imageIds), sliceNumber => { - if (sliceNumber == srcSliceNumber) { - return; + // Mouse move -> Polygon Mode + const handleNearby = this._pointNearHandle(element, data, coords); + const points = data.handles.points; + // If there is a handle nearby to snap to + // (and it's not the actual mouse handle) + + if (handleNearby !== undefined && !handleNearby.hasBoundingBox && handleNearby < points.length - 1) { + config.mouseLocation.handles.start.x = points[handleNearby].x; + config.mouseLocation.handles.start.y = points[handleNearby].y; } - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(srcImageToolState, data => { - addToolStateSingleSlice(element, toolName, data, sliceNumber, seriesId); - }); - }); -}; -/** - * Update slice index in cornerstone tools stack state - * @function updateStackToolState - * @param {String} elementId - The html div id used for rendering or its DOM HTMLElement - * @param {Number} imageIndex - The new imageIndex value. - */ -const updateStackToolState = function (elementId, imageIndex) { - let element = (0,_imageUtils__WEBPACK_IMPORTED_MODULE_10__.isElement)(elementId) ? elementId : document.getElementById(elementId); - if (!element) { - console.error("invalid html element: " + elementId); - return; + // Force onImageRendered + external.cornerstone.updateImage(element); } - let enabledElement = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElement(element); - if (!enabledElement.toolStateManager) { - return; + + /** + * Event handler for MOUSE_DRAG during drawing event loop. + * + * @event + * @param {Object} evt - The event. + * @returns {undefined} + */ + _drawingMouseDragCallback(evt) { + if (!this.options.mouseButtonMask.includes(evt.detail.buttons)) { + return; + } + this._drawingDrag(evt); } - let stackState = enabledElement.toolStateManager.toolState["stack"]; - if (!stackState) { - return; + + /** + * Event handler for TOUCH_DRAG during drawing event loop. + * + * @event + * @param {Object} evt - The event. + * @returns {undefined} + */ + _drawingTouchDragCallback(evt) { + this._drawingDrag(evt); } - // READY for different segmentations data (data[segmentation_label_id]) - stackState.data[0].currentImageIdIndex = imageIndex; -}; + _drawingDrag(evt) { + const eventData = evt.detail; + const { + element + } = eventData; + const toolState = getToolState(element, this.name); + const config = this.configuration; + const currentTool = config.currentTool; + const data = toolState.data[currentTool]; -/** @inner Internal module functions */ + // Set the mouseLocation handle + this._getMouseLocation(eventData); + this._checkInvalidHandleLocation(data, eventData); + this._addPointPencilMode(eventData, data.handles.points); + this._dragging = true; -/** - * Check if a tool has already been added - * @function isToolMissing - * @param {String} toolName - The tool name. - * @param {Array} _viewports - The viewports to check. - */ -const isToolMissing = function (toolName, _viewports) { - let isToolMissing = false; - if (_viewports) { - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(_viewports, function (viewport) { - let element = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElement(document.getElementById(viewport)); - let added = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().getToolForElement(element, toolName); - if (added === undefined) { - isToolMissing = true; - } - }); - } else { - let elements = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElements(); - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(elements, function (element) { - let added = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().getToolForElement(element, toolName); - if (added === undefined) { - isToolMissing = true; - } - }); + // Force onImageRendered + external.cornerstone.updateImage(element); } - return isToolMissing; -}; - -/***/ }), - -/***/ "./imaging/parsers/nrrd.js": -/*!*********************************!*\ - !*** ./imaging/parsers/nrrd.js ***! - \*********************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ parse: () => (/* binding */ parse) -/* harmony export */ }); -/* harmony import */ var pako__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! pako */ "./node_modules/pako/index.js"); -/* harmony import */ var pako__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(pako__WEBPACK_IMPORTED_MODULE_0__); - - -/** - * This is the mapping from the NRRD datatype as written in the NRRD header - * to the JS typed array equivalent. - */ -const NRRD_TYPES_TO_TYPEDARRAY = { - "signed char": Int8Array, - int8: Int8Array, - int8_t: Int8Array, - uchar: Uint8Array, - "unsigned char": Uint8Array, - uint8: Uint8Array, - uint8_t: Uint8Array, - short: Int16Array, - "short int": Int16Array, - "signed short": Int16Array, - "signed short int": Int16Array, - int16: Int16Array, - int16_t: Int16Array, - ushort: Uint16Array, - "unsigned short": Uint16Array, - "unsigned short int": Uint16Array, - uint16: Uint16Array, - uint16_t: Uint16Array, - int: Int32Array, - "signed int": Int32Array, - int32: Int32Array, - int32_t: Int32Array, - uint: Uint32Array, - "unsigned int": Uint32Array, - uint32: Uint32Array, - uint32_t: Uint32Array, - // OK for Node/V8/Chrome but not Firefox - // longlong: BigInt64Array, - // "long long": BigInt64Array, - // "long long int": BigInt64Array, - // "signed long long": BigInt64Array, - // "signed long long int": BigInt64Array, - // int64: BigInt64Array, - // int64_t: BigInt64Array, - // ulonglong: BigUint64Array, - // "unsigned long long": BigUint64Array, - // "unsigned long long int": BigUint64Array, - // uint64: BigUint64Array, - // uint64_t: BigUint64Array, - float: Float32Array, - double: Float64Array -}; -const NRRD_TYPES_TO_VIEW_GET = { - "signed char": "getInt8", - int8: "getInt8", - int8_t: "getInt8", - uchar: "getUint8", - "unsigned char": "getUint8", - uint8: "getUint8", - uint8_t: "getUint8", - short: "getInt16", - "short int": "getInt16", - "signed short": "getInt16", - "signed short int": "getInt16", - int16: "getInt16", - int16_t: "getInt16", - ushort: "getUint16", - "unsigned short": "getUint16", - "unsigned short int": "getUint16", - uint16: "getUint16", - uint16_t: "getUint16", - int: "getInt32", - "signed int": "getInt32", - int32: "getInt32", - int32_t: "getInt32", - uint: "getUint32", - "unsigned int": "getUint32", - uint32: "getUint32", - uint32_t: "getUint32", - // OK for Node/V8/Chrome but not Firefox - // longlong: null, - // "long long": null, - // "long long int": null, - // "signed long long": null, - // "signed long long int": null, - // int64: null, - // int64_t: null, - // ulonglong: null, - // "unsigned long long": null, - // "unsigned long long int": null, - // uint64: null, - // uint64_t: null, - float: "getFloat32", - double: "getFloat64" -}; -const SPACE_TO_SPACEDIMENSIONS = { - "right-anterior-superior": 3, - ras: 3, - "left-anterior-superior": 3, - las: 3, - "left-posterior-superior": 3, - lps: 3, - "right-anterior-superior-time": 4, - rast: 4, - "left-anterior-superior-time": 4, - last: 4, - "left-posterior-superior-time": 4, - lpst: 4, - "scanner-xyz": 3, - "scanner-xyz-time": 4, - "3d-right-handed": 3, - "3d-left-handed": 3, - "3d-right-handed-time": 4, - "3d-left-handed-time": 4 -}; - -// in NRRD, some "kinds" have to respect a certain size. For example, the kind -// "quaternion" has to be of size 4 (xyzw). -// When the value is 'null', then no enforcement is made. -// Note: the fields have been turned to lowercase here -const KIND_TO_SIZE = { - domain: null, - space: null, - time: null, - list: null, - point: null, - vector: null, - "covariant-vector": null, - normal: null, - stub: 1, - scalar: 1, - complex: 2, - "2-vector": 2, - "3-color": 3, - "rgb-color": 3, - "hsv-color": 3, - "xyz-color": 3, - "4-color": 4, - "rgba-color": 4, - "3-vector": 3, - "3-gradient": 3, - "3-normal": 3, - "4-vector": 4, - quaternion: 4, - "2d-symmetric-matrix": 3, - "2d-masked-symmetric-matrix": 4, - "2d-matrix": 4, - "2d-masked-matrix": 4, - "3d-symmetric-matrix": 6, - "3d-masked-symmetric-matrix": 7, - "3d-matrix": 9, - "3d-masked-matrix": 10, - "???": null -}; - -/** - * Parse a buffer of a NRRD file. - * Throws an exception if the file is not a proper NRRD file. - * @instance - * @function parse - * @param {ArrayBuffer} nrrdBuffer - the NRRD file buffer - * @param {Object} options - the option object - * @param {boolean} options.headerOnly - Parses only the header if true, parses header and data if false (default: false) - * @return {Object} NRRD header and data such as {header: Object, data: TypedArray } - */ -const parse = function (nrrdBuffer, options) { - let magicControl = "NRRD000"; - let magicTest = String.fromCharCode.apply(null, new Uint8Array(nrrdBuffer, 0, magicControl.length)); - if (magicControl !== magicTest) { - throw new Error("This file is not a NRRD file"); - } - let { - header, - dataByteOffset - } = parseHeader(nrrdBuffer); - if ("headerOnly" in options && options.headerOnly) { - return { - header: header, - data: null - }; - } - let data = parseData(nrrdBuffer, header, dataByteOffset); - return { - header: header, - data: data - }; -}; -/** - * @private - * Parses the header - */ -function parseHeader(nrrdBuffer) { - let byteArrayHeader = []; - let dataStartPosition = null; - let view = new DataView(nrrdBuffer); - for (let i = 0; i < nrrdBuffer.byteLength; i++) { - byteArrayHeader.push(String.fromCharCode(view.getUint8(i))); - if (i > 0 && byteArrayHeader[i - 1] === "\n" && byteArrayHeader[i] === "\n") { - dataStartPosition = i + 1; - break; + /** + * Event handler for MOUSE_UP during drawing event loop. + * + * @event + * @param {Object} evt - The event. + * @returns {undefined} + */ + _drawingMouseUpCallback(evt) { + const { + element + } = evt.detail; + if (!this._dragging) { + return; } - } - if (dataStartPosition === null) { - throw new Error("The NRRD header is corrupted."); - } - let comments = []; - let headerLines = byteArrayHeader.join("").trim().split(/\r\n|\n/).map(l => l.trim()); - let preMap = headerLines.slice(1).filter(s => { - // removing empty lines - return s.length > 0; - }).filter(s => { - // removing comments - if (s[0] === "#") { - comments.push(s.slice(1).trim()); + this._dragging = false; + const config = this.configuration; + const currentTool = config.currentTool; + const toolState = getToolState(element, this.name); + const data = toolState.data[currentTool]; + if (!freehandIntersect.end(data.handles.points) && data.canComplete) { + const lastHandlePlaced = config.currentHandle; + this._endDrawing(element, lastHandlePlaced); } - return s[0] !== "#"; - }).map(s => { - let keyVal = s.split(":"); - return { - key: keyVal[0].trim(), - val: keyVal[1].trim() - }; - }); - let nrrdHeader = {}; - preMap.forEach(field => { - nrrdHeader[field.key] = field.val; - }); - - // parsing each fields of the header - if (nrrdHeader["sizes"]) { - nrrdHeader["sizes"] = nrrdHeader.sizes.split(/\s+/).map(n => parseInt(n)); - } - if (nrrdHeader["space dimension"]) { - nrrdHeader["space dimension"] = parseInt(nrrdHeader["space dimension"]); - } - if (nrrdHeader["space"]) { - nrrdHeader["space dimension"] = SPACE_TO_SPACEDIMENSIONS[nrrdHeader["space"].toLowerCase()]; - } - if (nrrdHeader["dimension"]) { - nrrdHeader["dimension"] = parseInt(nrrdHeader["dimension"]); + preventPropagation(evt); + return; } - if (nrrdHeader["space directions"]) { - nrrdHeader["space directions"] = nrrdHeader["space directions"].split(/\s+/).map(triple => { - if (triple.trim() === "none") { - return null; - } - return triple.slice(1, triple.length - 1).split(",").map(n => parseFloat(n)); - }); - if (nrrdHeader["space directions"].length !== nrrdHeader["dimension"]) { - throw new Error('"space direction" property has to contain as many elements as dimensions. Non-spatial dimesnsions must be refered as "none". See http://teem.sourceforge.net/nrrd/format.html#spacedirections for more info.'); + + /** + * Event handler for MOUSE_DOWN during drawing event loop. + * + * @event + * @param {Object} evt - The event. + * @returns {undefined} + */ + _drawingMouseDownCallback(evt) { + const eventData = evt.detail; + const { + buttons, + currentPoints, + element + } = eventData; + if (!this.options.mouseButtonMask.includes(buttons)) { + return; } - } - if (nrrdHeader["space units"]) { - nrrdHeader["space units"] = nrrdHeader["space units"].split(/\s+/); - } - if (nrrdHeader["space origin"]) { - nrrdHeader["space origin"] = nrrdHeader["space origin"].slice(1, nrrdHeader["space origin"].length - 1).split(",").map(n => parseFloat(n)); - } - if (nrrdHeader["measurement frame"]) { - nrrdHeader["measurement frame"] = nrrdHeader["measurement frame"].split(/\s+/).map(triple => { - if (triple.trim() === "none") { - return null; - } - return triple.slice(1, triple.length - 1).split(",").map(n => parseFloat(n)); - }); - } - if (nrrdHeader["kinds"]) { - nrrdHeader["kinds"] = nrrdHeader["kinds"].split(/\s+/); - if (nrrdHeader["kinds"].length !== nrrdHeader["sizes"].length) { - throw new Error(`The "kinds" property is expected to have has many elements as the "size" property.`); + const coords = currentPoints.canvas; + const config = this.configuration; + const currentTool = config.currentTool; + const toolState = getToolState(element, this.name); + const data = toolState.data[currentTool]; + const handleNearby = this._pointNearHandle(element, data, coords); + if (!freehandIntersect.end(data.handles.points) && data.canComplete) { + const lastHandlePlaced = config.currentHandle; + this._endDrawing(element, lastHandlePlaced); + } else if (handleNearby === undefined) { + this._addPoint(eventData); } - nrrdHeader["kinds"].forEach((k, i) => { - let expectedLength = KIND_TO_SIZE[k.toLowerCase()]; - let foundLength = nrrdHeader["sizes"][i]; - if (expectedLength !== null && expectedLength !== foundLength) { - throw new Error(`The kind "${k}" expect a size of ${expectedLength} but ${foundLength} found`); - } - }); - } - if (nrrdHeader["min"]) { - nrrdHeader["min"] = parseFloat(nrrdHeader["min"]); - } - if (nrrdHeader["max"]) { - nrrdHeader["max"] = parseFloat(nrrdHeader["max"]); - } - if (nrrdHeader["old min"]) { - nrrdHeader["old min"] = parseFloat(nrrdHeader["old min"]); - } - if (nrrdHeader["old max"]) { - nrrdHeader["old max"] = parseFloat(nrrdHeader["old max"]); - } - if (nrrdHeader["spacings"]) { - nrrdHeader["spacings"] = nrrdHeader["spacings"].split(/\s+/).map(n => parseFloat(n)); - } - if (nrrdHeader["thicknesses"]) { - nrrdHeader["thicknesses"] = nrrdHeader["thicknesses"].split(/\s+/).map(n => parseFloat(n)); - } - if (nrrdHeader["axis mins"]) { - nrrdHeader["axis mins"] = nrrdHeader["axis mins"].split(/\s+/).map(n => parseFloat(n)); - } - if (nrrdHeader["axismins"]) { - nrrdHeader["axismins"] = nrrdHeader["axismins"].split(/\s+/).map(n => parseFloat(n)); - } - if (nrrdHeader["axis maxs"]) { - nrrdHeader["axis maxs"] = nrrdHeader["axis maxs"].split(/\s+/).map(n => parseFloat(n)); - } - if (nrrdHeader["axismaxs"]) { - nrrdHeader["axismaxs"] = nrrdHeader["axismaxs"].split(/\s+/).map(n => parseFloat(n)); - } - if (nrrdHeader["centers"]) { - nrrdHeader["centers"] = nrrdHeader["centers"].split(/\s+/).map(mode => { - if (mode === "cell" || mode === "node") { - return mode; - } else { - return null; - } - }); - } - if (nrrdHeader["labels"]) { - nrrdHeader["labels"] = nrrdHeader["labels"].split(/\s+/); + preventPropagation(evt); + return; } - // some additional metadata that are not part of the header will be added here - nrrdHeader.extra = {}; - - // adding the comments from lines starting with # - nrrdHeader.extra.comments = comments; - - // having the stride can be handy. - nrrdHeader.extra.stride = [1]; - for (let i = 1; i < nrrdHeader.sizes.length; i++) { - nrrdHeader.extra.stride.push(nrrdHeader.extra.stride[i - 1] * nrrdHeader.sizes[i - 1]); + /** + * Event handler for TOUCH_START during drawing event loop. + * + * @event + * @param {Object} evt - The event. + * @returns {undefined} + */ + _drawingTouchStartCallback(evt) { + const eventData = evt.detail; + const { + currentPoints, + element + } = eventData; + const coords = currentPoints.canvas; + const config = this.configuration; + const currentTool = config.currentTool; + const toolState = getToolState(element, this.name); + const data = toolState.data[currentTool]; + const handleNearby = this._pointNearHandle(element, data, coords); + if (!freehandIntersect.end(data.handles.points) && data.canComplete) { + const lastHandlePlaced = config.currentHandle; + this._endDrawing(element, lastHandlePlaced); + } else if (handleNearby === undefined) { + this._addPoint(eventData); + } + preventPropagation(evt); + return; } - return { - header: nrrdHeader, - dataByteOffset: dataStartPosition - }; -} -/** - * @private - * Parses the data - */ -function parseData(nrrdBuffer, header, dataByteOffset) { - let dataBuffer = null; - let arrayType = NRRD_TYPES_TO_TYPEDARRAY[header.type]; - let nbElementsFromHeader = header.sizes.reduce((prev, curr) => prev * curr); - let min = +Infinity; - let max = -Infinity; - let data = null; - let isTextEncoded = header.encoding === "ascii" || header.encoding === "txt" || header.encoding === "text"; - if (header.encoding === "raw") { - dataBuffer = new Uint8Array(nrrdBuffer).slice(dataByteOffset).buffer; - } else if (isTextEncoded) { - let numbers = String.fromCharCode.apply(null, new Uint8Array(nrrdBuffer, dataByteOffset)).split(/\r\n|\n|\s/).map(s => s.trim()).filter(s => s !== "").map(s => { - let numValue = parseFloat(s); - min = Math.min(min, numValue); - max = Math.max(max, numValue); - return numValue; - }); - data = new arrayType(numbers); - } else if (header.encoding === "gzip" || header.encoding === "gz") { - dataBuffer = pako__WEBPACK_IMPORTED_MODULE_0___default().inflate(new Uint8Array(nrrdBuffer).slice(dataByteOffset)).buffer; - } else { - throw new Error('Only "raw", "ascii" and "gzip" encoding are supported.'); - } - if (isTextEncoded) { - if (nbElementsFromHeader !== data.length) { - throw new Error("Unconsistency in data buffer length"); - } - } else { - let nbElementsFromBufferAndType = dataBuffer.byteLength / arrayType.BYTES_PER_ELEMENT; - if (nbElementsFromHeader !== nbElementsFromBufferAndType) { - throw new Error("Unconsistency in data buffer length"); + /** Ends the active drawing loop and completes the polygon. + * + * @public + * @param {Object} element - The element on which the roi is being drawn. + * @returns {null} + */ + completeDrawing(element) { + if (!this._drawing) { + return; } - data = new arrayType(nbElementsFromHeader); - let dataView = new DataView(dataBuffer); - let viewMethod = NRRD_TYPES_TO_VIEW_GET[header.type]; - let littleEndian = header.endian === "little" ? true : false; - for (let i = 0; i < nbElementsFromHeader; i++) { - data[i] = dataView[viewMethod](i * arrayType.BYTES_PER_ELEMENT, littleEndian); - min = Math.min(min, data[i]); - max = Math.max(max, data[i]); + const toolState = getToolState(element, this.name); + const config = this.configuration; + const data = toolState.data[config.currentTool]; + if (!freehandIntersect.end(data.handles.points) && data.handles.points.length >= 2) { + const lastHandlePlaced = config.currentHandle; + data.polyBoundingBox = {}; + this._endDrawing(element, lastHandlePlaced); } } - header.extra.min = min; - header.extra.max = max; - return data; -} - -/***/ }), - -/***/ "./imaging/tools/custom/BorderMagnifyTool.js": -/*!***************************************************!*\ - !*** ./imaging/tools/custom/BorderMagnifyTool.js ***! - \***************************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "default": () => (/* binding */ BorderMagnifyTool) -/* harmony export */ }); -/* harmony import */ var cornerstone_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! cornerstone-core */ "./node_modules/cornerstone-core/dist/cornerstone.js"); -/* harmony import */ var cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cornerstone_core__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! cornerstone-tools */ "./node_modules/cornerstone-tools/dist/cornerstoneTools.js"); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(cornerstone_tools__WEBPACK_IMPORTED_MODULE_1__); - - -const MagnifyTool = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().MagnifyTool); -const drawTextBox = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("drawing/drawTextBox"); -const textStyle = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().textStyle); -const toolColors = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().toolColors); -class BorderMagnifyTool extends MagnifyTool { - constructor(props = {}) { - super(props); - - // Additional configuration options - this.configuration.showBorders = props.showBorders || true; // Default to true - this.configuration.showInfo = props.showInfo || true; // Default to true - this.configuration.borderColor = props.borderColor; // Default border color is green - // Add global keydown event listener - window.addEventListener('keydown', this.handleKeyDown.bind(this)); + /** + * Event handler for MOUSE_DOUBLE_CLICK during drawing event loop. + * + * @event + * @param {Object} evt - The event. + * @returns {undefined} + */ + _drawingMouseDoubleClickCallback(evt) { + const { + element + } = evt.detail; + this.completeDrawing(element); + preventPropagation(evt); } /** - * Event handler for the "keydown" event to toggle the visibility of borders and info on "B" key press. - * @param {KeyboardEvent} event - * @returns {void} + * Event handler for DOUBLE_TAP during drawing event loop. + * + * @event + * @param {Object} evt - The event. + * @returns {undefined} */ - handleKeyDown(event) { - if (event.key === 'M' || event.key === 'm') { - // Toggle the visibility of borders - this.configuration.showBorders = !this.configuration.showBorders; + _drawingDoubleTapClickCallback(evt) { + const { + element + } = evt.detail; + this.completeDrawing(element); + preventPropagation(evt); + } - // Toggle the visibility of zoom/ROI info - this.configuration.showInfo = !this.configuration.showInfo; + /** + * Event handler for MOUSE_DRAG during handle drag event loop. + * + * @event + * @param {Object} evt - The event. + * @returns {undefined} + */ + _editMouseDragCallback(evt) { + const eventData = evt.detail; + const { + element, + buttons + } = eventData; + if (!this.options.mouseButtonMask.includes(buttons)) { + return; + } + const toolState = getToolState(element, this.name); + const config = this.configuration; + const data = toolState.data[config.currentTool]; + const currentHandle = config.currentHandle; + const points = data.handles.points; + let handleIndex = -1; - // Redraw the magnification tool - this._drawMagnificationTool(); + // Set the mouseLocation handle + this._getMouseLocation(eventData); + data.handles.invalidHandlePlacement = freehandIntersect.modify(points, currentHandle); + data.active = true; + data.highlight = true; + points[currentHandle].x = config.mouseLocation.handles.start.x; + points[currentHandle].y = config.mouseLocation.handles.start.y; + handleIndex = this._getPrevHandleIndex(currentHandle, points); + if (currentHandle >= 0) { + const lastLineIndex = points[handleIndex].lines.length - 1; + const lastLine = points[handleIndex].lines[lastLineIndex]; + lastLine.x = config.mouseLocation.handles.start.x; + lastLine.y = config.mouseLocation.handles.start.y; } + + // Update the image + external.cornerstone.updateImage(element); } /** - * Overrides the _drawMagnificationTool method to add configurable borders and display zoom/ROI dimensions. - * @param {*} evt + * Event handler for TOUCH_DRAG during handle drag event loop. + * + * @event + * @param {Object} evt - The event. * @returns {void} */ - _drawMagnificationTool(evt) { - // Call the parent method to draw the magnification tool - super._drawMagnificationTool(evt); - // Query for the magnify canvas using the correct class name - const magnifyCanvas = evt ? evt.detail.element.querySelector('.magnifyTool') : null; - if (magnifyCanvas) { - const context = magnifyCanvas.getContext('2d'); - - // Check if the user wants to show borders - if (this.configuration.showBorders) { - // Add configurable borders - context.strokeStyle = this.configuration.borderColor || toolColors.getColorIfActive({ - active: true - }); - context.lineWidth = 4; - context.strokeRect(0, 0, magnifyCanvas.width, magnifyCanvas.height); - } + _editTouchDragCallback(evt) { + const eventData = evt.detail; + const { + element + } = eventData; + const toolState = getToolState(element, this.name); + const config = this.configuration; + const data = toolState.data[config.currentTool]; + const currentHandle = config.currentHandle; + const points = data.handles.points; + let handleIndex = -1; - // Check if the user wants to show info - if (this.configuration.showInfo) { - // Get the zoom level - const viewport = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getViewport(evt.detail.element); - const zoomLevel = viewport.scale.toFixed(2); // Adjust the precision as needed + // Set the mouseLocation handle + this._getMouseLocation(eventData); + data.handles.invalidHandlePlacement = freehandIntersect.modify(points, currentHandle); + data.active = true; + data.highlight = true; + points[currentHandle].x = config.mouseLocation.handles.start.x; + points[currentHandle].y = config.mouseLocation.handles.start.y; + handleIndex = this._getPrevHandleIndex(currentHandle, points); + if (currentHandle >= 0) { + const lastLineIndex = points[handleIndex].lines.length - 1; + const lastLine = points[handleIndex].lines[lastLineIndex]; + lastLine.x = config.mouseLocation.handles.start.x; + lastLine.y = config.mouseLocation.handles.start.y; + } - // Get ROI dimensions - const roiWidth = magnifyCanvas.width; - const roiHeight = magnifyCanvas.height; - const roiArea = (roiWidth * roiHeight).toFixed(2); // Area of the ROI + // Update the image + external.cornerstone.updateImage(element); + } - // Display the zoom level and ROI dimensions - //context.fillStyle = this.configuration.borderColor; // Adjust text color as needed - //context.font = 'bold 14px Arial'; - //context.fillText(`Zoom: x${zoomLevel}`, 10, 20); - //context.fillText(`ROI Area: ${roiWidth} x ${roiHeight} square pixels`, 10, 40); - const text = `Zoom: x${zoomLevel}`; - const str = `ROI: ${roiWidth}px x ${roiHeight}px`; - const fontHeight = textStyle.getFontSize(); - const color = this.configuration.borderColor || toolColors.getColorIfActive({ - active: true - }); - // Draw text 5px away from cursor - const textCoords = { - x: 5, - y: 2 - }; - drawTextBox(context, str, textCoords.x, textCoords.y + fontHeight + 5, color); - drawTextBox(context, text, textCoords.x, textCoords.y, color); - } + /** + * Returns the previous handle to the current one. + * @param {Number} currentHandle - the current handle index + * @param {Array} points - the handles Array of the freehand data + * @returns {Number} - The index of the previos handle + */ + _getPrevHandleIndex(currentHandle, points) { + if (currentHandle === 0) { + return points.length - 1; } + return currentHandle - 1; } -} - -/***/ }), - -/***/ "./imaging/tools/custom/contourTool.js": -/*!*********************************************!*\ - !*** ./imaging/tools/custom/contourTool.js ***! - \*********************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ ContoursTool: () => (/* binding */ ContoursTool) -/* harmony export */ }); -/* harmony import */ var cornerstone_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! cornerstone-core */ "./node_modules/cornerstone-core/dist/cornerstone.js"); -/* harmony import */ var cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cornerstone_core__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! cornerstone-tools */ "./node_modules/cornerstone-tools/dist/cornerstoneTools.js"); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(cornerstone_tools__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! lodash */ "./node_modules/lodash/lodash.js"); -/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(lodash__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _imageTools__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../imageTools */ "./imaging/imageTools.js"); -/** @module imaging/tools/custom/contourTool - * @desc This file provides functionalities for - * rendering segmentation contours with a - * custom cornestone tool - */ -// external libraries + /** + * Event handler for MOUSE_UP during handle drag event loop. + * + * @private + * @param {Object} evt - The event. + * @returns {undefined} + */ + _editMouseUpCallback(evt) { + const eventData = evt.detail; + const { + element + } = eventData; + const toolState = getToolState(element, this.name); + this._deactivateModify(element); + this._dropHandle(eventData, toolState); + this._endDrawing(element); + external.cornerstone.updateImage(element); + } + /** + * Places a handle of the freehand tool if the new location is valid. + * If the new location is invalid the handle snaps back to its previous position. + * + * @private + * @param {Object} eventData - Data object associated with the event. + * @param {Object} toolState - The data associated with the freehand tool. + * @modifies {toolState} + * @returns {undefined} + */ + _dropHandle(eventData, toolState) { + const config = this.configuration; + const currentTool = config.currentTool; + const handles = toolState.data[currentTool].handles; + const points = handles.points; + // Don't allow the line being modified to intersect other lines + if (handles.invalidHandlePlacement) { + const currentHandle = config.currentHandle; + const currentHandleData = points[currentHandle]; + let previousHandleData; + if (currentHandle === 0) { + const lastHandleID = points.length - 1; + previousHandleData = points[lastHandleID]; + } else { + previousHandleData = points[currentHandle - 1]; + } + // Snap back to previous position + currentHandleData.x = config.dragOrigin.x; + currentHandleData.y = config.dragOrigin.y; + previousHandleData.lines[0] = currentHandleData; + handles.invalidHandlePlacement = false; + } + } -// internal libraries + /** + * Begining of drawing loop when tool is active and a click event happens far + * from existing handles. + * + * @private + * @param {Object} evt - The event. + * @returns {undefined} + */ + _startDrawing(evt) { + const eventData = evt.detail; + const measurementData = this.createNewMeasurement(eventData); + const { + element + } = eventData; + const config = this.configuration; + let interactionType; + if (evt.type === EVENTS.MOUSE_DOWN_ACTIVATE) { + interactionType = "Mouse"; + } else if (evt.type === EVENTS.TOUCH_START_ACTIVE) { + interactionType = "Touch"; + } + this._activateDraw(element, interactionType); + this._getMouseLocation(eventData); + addToolState(element, this.name, measurementData); + const toolState = getToolState(element, this.name); + config.currentTool = toolState.data.length - 1; + this._activeDrawingToolReference = toolState.data[config.currentTool]; + } + /** + * Adds a point on mouse click in polygon mode. + * + * @private + * @param {Object} eventData - data object associated with an event. + * @returns {undefined} + */ + _addPoint(eventData) { + const { + currentPoints, + element + } = eventData; + const toolState = getToolState(element, this.name); -// cornerstone tools imports -const external = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().external); -const EVENTS = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().EVENTS); -const BaseAnnotationTool = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("base/BaseAnnotationTool"); -const getToolState = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().getToolState); -const addToolState = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().addToolState); -const removeToolState = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().removeToolState); -const toolStyle = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().toolStyle); -const toolColors = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().toolColors); -const triggerEvent = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("util/triggerEvent"); -const pointInsideBoundingBox = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("util/pointInsideBoundingBox"); -const calculateSUV = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("util/calculateSUV"); -const numbersWithCommas = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("util/numbersWithCommas"); -const getNewContext = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("drawing/getNewContext"); -const draw = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("drawing/draw"); -const drawJoinedLines = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("drawing/drawJoinedLines"); -const drawHandles = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("drawing/drawHandles"); -const drawLinkedTextBox = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("drawing/drawLinkedTextBox"); -const clipToBox = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("util/clip"); -const freehandRoiCursor = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("cursors/freehandRoiCursor"); -const getLogger = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("util/getLogger"); -const throttle = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("util/throttle"); -const logger = getLogger("tools:annotation:FreehandRoiTool"); -const freehandUtils = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("util/freehandUtils"); + // Get the toolState from the last-drawn polygon + const config = this.configuration; + const data = toolState.data[config.currentTool]; + if (data.handles.invalidHandlePlacement) { + return; + } + const newHandleData = new FreehandHandleData(currentPoints.image); -// TODO check how to import these -// const toolCursor = csTools.importInternal("store/setToolCursor"); -// const hideToolCursor = toolCursor.hideToolCursor; -// const setToolCursor = toolCursor.setToolCursor; -// const findAndMoveHelpers = csTools.importInternal("util/findAndMoveHelpers"); -// const moveHandleNearImagePoint = findAndMoveHelpers.moveHandleNearImagePoint; + // If this is not the first handle + if (data.handles.points.length) { + // Add the line from the current handle to the new handle + data.handles.points[config.currentHandle - 1].lines.push(currentPoints.image); + } -const { - insertOrDelete, - freehandArea, - calculateFreehandStatistics, - freehandIntersect, - FreehandHandleData -} = freehandUtils; -const state = { - // Global - globalTools: {}, - globalToolChangeHistory: [], - // Tracking - enabledElements: [], - tools: [], - isToolLocked: false, - activeMultiPartTool: null, - mousePositionImage: {}, - // Settings - clickProximity: 6, - touchProximity: 10, - handleRadius: 6, - deleteIfHandleOutsideImage: true, - preventHandleOutsideImage: false, - // Cursor - svgCursorUrl: null -}; + // Add the new handle + data.handles.points.push(newHandleData); -/** - * @public - * @class ContoursTool - * @memberof Tools.Annotation - * @classdesc Tool for drawing a set of contours - * @extends Tools.Base.BaseAnnotationTool - */ -class ContoursTool extends BaseAnnotationTool { - constructor(props = {}) { - const defaultProps = { - name: "ContoursTool", - supportedInteractionTypes: ["Mouse", "Touch"], - configuration: defaultFreehandConfiguration(), - svgCursor: freehandRoiCursor - }; - super(props, defaultProps); - this.initializeContours(props.contoursParsedData, props.segmentationName); - this.isMultiPartTool = true; - this._drawing = false; - this._dragging = false; - this._modifying = false; + // Increment the current handle value + config.currentHandle += 1; - // Create bound callback functions for private event loops - this._drawingMouseDownCallback = this._drawingMouseDownCallback.bind(this); - this._drawingMouseMoveCallback = this._drawingMouseMoveCallback.bind(this); - this._drawingMouseDragCallback = this._drawingMouseDragCallback.bind(this); - this._drawingMouseUpCallback = this._drawingMouseUpCallback.bind(this); - this._drawingMouseDoubleClickCallback = this._drawingMouseDoubleClickCallback.bind(this); - this._editMouseUpCallback = this._editMouseUpCallback.bind(this); - this._editMouseDragCallback = this._editMouseDragCallback.bind(this); - this._drawingTouchStartCallback = this._drawingTouchStartCallback.bind(this); - this._drawingTouchDragCallback = this._drawingTouchDragCallback.bind(this); - this._drawingDoubleTapClickCallback = this._drawingDoubleTapClickCallback.bind(this); - this._editTouchDragCallback = this._editTouchDragCallback.bind(this); - this.throttledUpdateCachedStats = throttle(this.updateCachedStats, 110); - } - initializeContours(contourData, segmentationName) { - var elements = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getEnabledElements(); - (0,lodash__WEBPACK_IMPORTED_MODULE_2__.each)(elements, el => { - var slices = contourData[el.element.id][segmentationName]; - for (var slice in slices) { - var linesPerSlice = contourData[el.element.id][segmentationName][slice].lines; - var lines = (0,lodash__WEBPACK_IMPORTED_MODULE_2__.map)(linesPerSlice, line => { - let dataToInject = { - visible: true, - active: false, - invalidated: false, - color: "#FF0000", - handles: { - points: line - } - }; - return dataToInject; - }); - (0,_imageTools__WEBPACK_IMPORTED_MODULE_3__.addToolStateSingleSlice)(el.element, "ContoursTool", lines, slice); - } - }); + // Force onImageRendered to fire + external.cornerstone.updateImage(element); + this.fireModifiedEvent(element, data); } - createNewMeasurement(eventData) { - const goodEventData = eventData && eventData.currentPoints && eventData.currentPoints.image; - if (!goodEventData) { - logger.error(`required eventData not supplied to tool ${this.name}'s createNewMeasurement`); - return; + + /** + * If in pencilMode, check the mouse position is farther than the minimum + * distance between points, then add a point. + * + * @private + * @param {Object} eventData - Data object associated with an event. + * @param {Object} points - Data object associated with the tool. + * @returns {undefined} + */ + _addPointPencilMode(eventData, points) { + const config = this.configuration; + const { + element + } = eventData; + const mousePoint = config.mouseLocation.handles.start; + const handleFurtherThanMinimumSpacing = handle => this._isDistanceLargerThanSpacing(element, handle, mousePoint); + if (points.every(handleFurtherThanMinimumSpacing)) { + this._addPoint(eventData); } - const measurementData = { - visible: true, - active: true, - invalidated: true, - color: undefined, - handles: { - points: [] - } - }; - measurementData.handles.textBox = { - active: false, - hasMoved: false, - movesIndependently: false, - drawnIndependently: true, - allowedOutsideImage: true, - hasBoundingBox: true - }; - return measurementData; } /** + * Ends the active drawing loop and completes the polygon. * - * - * @param {*} element element - * @param {*} data data - * @param {*} coords coords - * @returns {Boolean} + * @private + * @param {Object} element - The element on which the roi is being drawn. + * @param {Object} handleNearby - the handle nearest to the mouse cursor. + * @returns {undefined} */ - pointNearTool(element, data, coords) { - const validParameters = data && data.handles && data.handles.points; - if (!validParameters) { - throw new Error(`invalid parameters supplied to tool ${this.name}'s pointNearTool`); + _endDrawing(element, handleNearby) { + const toolState = getToolState(element, this.name); + const config = this.configuration; + const data = toolState.data[config.currentTool]; + data.active = false; + data.highlight = false; + data.handles.invalidHandlePlacement = false; + + // Connect the end handle to the origin handle + if (handleNearby !== undefined) { + const points = data.handles.points; + points[config.currentHandle - 1].lines.push(points[0]); } - if (!validParameters || data.visible === false) { - return false; + if (this._modifying) { + this._modifying = false; + data.invalidated = true; } - const isPointNearTool = this._pointNearHandle(element, data, coords); - if (isPointNearTool !== undefined) { - return true; + + // Reset the current handle + config.currentHandle = 0; + config.currentTool = -1; + data.canComplete = false; + if (this._drawing) { + this._deactivateDraw(element); } - return false; + external.cornerstone.updateImage(element); + this.fireModifiedEvent(element, data); + this.fireCompletedEvent(element, data); } /** - * @param {*} element - * @param {*} data + * Returns a handle of a particular tool if it is close to the mouse cursor + * + * @private + * @param {Object} element - The element on which the roi is being drawn. + * @param {Object} data Data object associated with the tool. * @param {*} coords - * @returns {number} the distance in px from the provided coordinates to the - * closest rendered portion of the annotation. -1 if the distance cannot be - * calculated. + * @returns {Number|Object|Boolean} */ - distanceFromPoint(element, data, coords) { - let distance = Infinity; + _pointNearHandle(element, data, coords) { + if (data.handles === undefined || data.handles.points === undefined) { + return; + } + if (data.visible === false) { + return; + } for (let i = 0; i < data.handles.points.length; i++) { - const distanceI = external.cornerstoneMath.point.distance(data.handles.points[i], coords); - distance = Math.min(distance, distanceI); + const handleCanvas = external.cornerstone.pixelToCanvas(element, data.handles.points[i]); + if (external.cornerstoneMath.point.distance(handleCanvas, coords) < 6) { + return i; + } } - // If an error caused distance not to be calculated, return -1. - if (distance === Infinity) { - return -1; - } - return distance; - } - - /** - * @param {*} element - * @param {*} data - * @param {*} coords - * @returns {number} the distance in canvas units from the provided coordinates to the - * closest rendered portion of the annotation. -1 if the distance cannot be - * calculated. - */ - distanceFromPointCanvas(element, data, coords) { - let distance = Infinity; - if (!data) { - return -1; - } - const canvasCoords = external.cornerstone.pixelToCanvas(element, coords); - const points = data.handles.points; - for (let i = 0; i < points.length; i++) { - const handleCanvas = external.cornerstone.pixelToCanvas(element, points[i]); - const distanceI = external.cornerstoneMath.point.distance(handleCanvas, canvasCoords); - distance = Math.min(distance, distanceI); - } - - // If an error caused distance not to be calculated, return -1. - if (distance === Infinity) { - return -1; - } - return distance; - } - - /** - * - * - * - * @param {Object} image image - * @param {Object} element element - * @param {Object} data data - * - * @returns {void} void - */ - updateCachedStats(image, element, data) { - // Define variables for the area and mean/standard deviation - let meanStdDev, meanStdDevSUV; - const seriesModule = external.cornerstone.metaData.get("generalSeriesModule", image.imageId); - const modality = seriesModule ? seriesModule.modality : null; - const points = data.handles.points; - // If the data has been invalidated, and the tool is not currently active, - // We need to calculate it again. - - // Retrieve the bounds of the ROI in image coordinates - const bounds = { - left: points[0].x, - right: points[0].x, - bottom: points[0].y, - top: points[0].x - }; - for (let i = 0; i < points.length; i++) { - bounds.left = Math.min(bounds.left, points[i].x); - bounds.right = Math.max(bounds.right, points[i].x); - bounds.bottom = Math.min(bounds.bottom, points[i].y); - bounds.top = Math.max(bounds.top, points[i].y); - } - const polyBoundingBox = { - left: bounds.left, - top: bounds.bottom, - width: Math.abs(bounds.right - bounds.left), - height: Math.abs(bounds.top - bounds.bottom) - }; - - // Store the bounding box information for the text box - data.polyBoundingBox = polyBoundingBox; - - // First, make sure this is not a color image, since no mean / standard - // Deviation will be calculated for color images. - if (!image.color) { - // Retrieve the array of pixels that the ROI bounds cover - const pixels = external.cornerstone.getPixels(element, polyBoundingBox.left, polyBoundingBox.top, polyBoundingBox.width, polyBoundingBox.height); - - // Calculate the mean & standard deviation from the pixels and the object shape - meanStdDev = calculateFreehandStatistics.call(this, pixels, polyBoundingBox, data.handles.points); - if (modality === "PT") { - // If the image is from a PET scan, use the DICOM tags to - // Calculate the SUV from the mean and standard deviation. - - // Note that because we are using modality pixel values from getPixels, and - // The calculateSUV routine also rescales to modality pixel values, we are first - // Returning the values to storedPixel values before calcuating SUV with them. - // TODO: Clean this up? Should we add an option to not scale in calculateSUV? - meanStdDevSUV = { - mean: calculateSUV(image, (meanStdDev.mean - image.intercept) / image.slope), - stdDev: calculateSUV(image, (meanStdDev.stdDev - image.intercept) / image.slope) - }; - } - - // If the mean and standard deviation values are sane, store them for later retrieval - if (meanStdDev && !isNaN(meanStdDev.mean)) { - data.meanStdDev = meanStdDev; - data.meanStdDevSUV = meanStdDevSUV; - } - } - - // Retrieve the pixel spacing values, and if they are not - // Real non-zero values, set them to 1 - const columnPixelSpacing = image.columnPixelSpacing || 1; - const rowPixelSpacing = image.rowPixelSpacing || 1; - const scaling = columnPixelSpacing * rowPixelSpacing; - const area = freehandArea(data.handles.points, scaling); - - // If the area value is sane, store it for later retrieval - if (!isNaN(area)) { - data.area = area; - } - - // Set the invalidated flag to false so that this data won't automatically be recalculated - data.invalidated = false; - } - - /** - * - * - * @param {*} evt - * @returns {undefined} - */ - renderToolData(evt) { - const eventData = evt.detail; - - // If we have no toolState for this element, return immediately as there is nothing to do - const toolState = getToolState(evt.currentTarget, this.name); - if (!toolState) { - return; - } - const { - image, - element - } = eventData; - const config = this.configuration; - const seriesModule = external.cornerstone.metaData.get("generalSeriesModule", image.imageId); - const modality = seriesModule ? seriesModule.modality : null; - - // We have tool data for this element - iterate over each one and draw it - const context = getNewContext(eventData.canvasContext.canvas); - const lineWidth = toolStyle.getToolWidth(); - for (let i = 0; i < toolState.data.length; i++) { - const data = toolState.data[i]; - if (data.visible === false) { - continue; - } - draw(context, context => { - let color = toolColors.getColorIfActive(data); - let fillColor; - if (data.active) { - if (data.handles.invalidHandlePlacement) { - color = config.invalidColor; - fillColor = config.invalidColor; - } else { - color = toolColors.getColorIfActive(data); - fillColor = toolColors.getFillColor(); - } - } else { - fillColor = toolColors.getToolColor(); - } - if (data.handles.points.length) { - for (let j = 0; j < data.handles.points.length; j++) { - const lines = [...data.handles.points[j].lines]; - const points = data.handles.points; - if (j === points.length - 1 && !data.polyBoundingBox) { - // If it's still being actively drawn, keep the last line to - // The mouse location - lines.push(config.mouseLocation.handles.start); - } - drawJoinedLines(context, element, data.handles.points[j], lines, { - color - }); - } - } - - // Draw handles - - const options = { - color, - fill: fillColor - }; - if (config.alwaysShowHandles || data.active && data.polyBoundingBox) { - // Render all handles - options.handleRadius = config.activeHandleRadius; - drawHandles(context, eventData, data.handles.points, options); - } - if (data.canComplete) { - // Draw large handle at the origin if can complete drawing - options.handleRadius = config.completeHandleRadius; - const handle = data.handles.points[0]; - drawHandles(context, eventData, [handle], options); - } - if (data.active && !data.polyBoundingBox) { - // Draw handle at origin and at mouse if actively drawing - options.handleRadius = config.activeHandleRadius; - drawHandles(context, eventData, config.mouseLocation.handles, options); - const firstHandle = data.handles.points[0]; - drawHandles(context, eventData, [firstHandle], options); - } - - // Update textbox stats - if (data.invalidated === true && !data.active) { - if (data.meanStdDev && data.meanStdDevSUV && data.area) { - this.throttledUpdateCachedStats(image, element, data); - } else { - this.updateCachedStats(image, element, data); - } - } - - // Only render text if polygon ROI has been completed and freehand 'shiftKey' mode was not used: - if (data.polyBoundingBox && !data.handles.textBox.freehand) { - // If the textbox has not been moved by the user, it should be displayed on the right-most - // Side of the tool. - if (!data.handles.textBox.hasMoved) { - // Find the rightmost side of the polyBoundingBox at its vertical center, and place the textbox here - // Note that this calculates it in image coordinates - data.handles.textBox.x = data.polyBoundingBox.left + data.polyBoundingBox.width; - data.handles.textBox.y = data.polyBoundingBox.top + data.polyBoundingBox.height / 2; - } - const text = textBoxText.call(this, data); - drawLinkedTextBox(context, element, data.handles.textBox, text, data.handles.points, textBoxAnchorPoints, color, lineWidth, 0, true); - } - }); - } - function textBoxText(data) { - const { - meanStdDev, - meanStdDevSUV, - area - } = data; - // Define an array to store the rows of text for the textbox - const textLines = []; - - // If the mean and standard deviation values are present, display them - if (meanStdDev && meanStdDev.mean !== undefined) { - // If the modality is CT, add HU to denote Hounsfield Units - let moSuffix = ""; - if (modality === "CT") { - moSuffix = "HU"; - } - data.unit = moSuffix; - - // Create a line of text to display the mean and any units that were specified (i.e. HU) - let meanText = `Mean: ${numbersWithCommas(meanStdDev.mean.toFixed(2))} ${moSuffix}`; - // Create a line of text to display the standard deviation and any units that were specified (i.e. HU) - let stdDevText = `StdDev: ${numbersWithCommas(meanStdDev.stdDev.toFixed(2))} ${moSuffix}`; - - // If this image has SUV values to display, concatenate them to the text line - if (meanStdDevSUV && meanStdDevSUV.mean !== undefined) { - const SUVtext = " SUV: "; - meanText += SUVtext + numbersWithCommas(meanStdDevSUV.mean.toFixed(2)); - stdDevText += SUVtext + numbersWithCommas(meanStdDevSUV.stdDev.toFixed(2)); - } - - // Add these text lines to the array to be displayed in the textbox - textLines.push(meanText); - textLines.push(stdDevText); - } - - // If the area is a sane value, display it - if (area) { - // Determine the area suffix based on the pixel spacing in the image. - // If pixel spacing is present, use millimeters. Otherwise, use pixels. - // This uses Char code 178 for a superscript 2 - let suffix = ` mm${String.fromCharCode(178)}`; - if (!image.rowPixelSpacing || !image.columnPixelSpacing) { - suffix = ` pixels${String.fromCharCode(178)}`; - } - - // Create a line of text to display the area and its units - const areaText = `Area: ${numbersWithCommas(area.toFixed(2))}${suffix}`; - - // Add this text line to the array to be displayed in the textbox - textLines.push(areaText); - } - return textLines; - } - function textBoxAnchorPoints(handles) { - return handles; - } - } - addNewMeasurement(evt) { - const eventData = evt.detail; - this._startDrawing(evt); - this._addPoint(eventData); - preventPropagation(evt); - } - preMouseDownCallback(evt) { - const eventData = evt.detail; - const nearby = this._pointNearHandleAllTools(eventData); - if (eventData.event.ctrlKey) { - if (nearby !== undefined && nearby.handleNearby.hasBoundingBox) { - // Ctrl + clicked textBox, do nothing but still consume event. - } else { - insertOrDelete.call(this, evt, nearby); - } - preventPropagation(evt); - return true; - } - return false; - } - handleSelectedCallback(evt, toolData, handle, interactionType = "mouse") { - const { - element - } = evt.detail; - const toolState = getToolState(element, this.name); - console.info(interactionType); - // if (handle.hasBoundingBox) { - // // Use default move handler. - // moveHandleNearImagePoint(evt, this, toolData, handle, interactionType); - - // return; - // } - - const config = this.configuration; - config.dragOrigin = { - x: handle.x, - y: handle.y - }; - - // Iterating over handles of all toolData instances to find the indices of the selected handle - for (let toolIndex = 0; toolIndex < toolState.data.length; toolIndex++) { - const points = toolState.data[toolIndex].handles.points; - for (let p = 0; p < points.length; p++) { - if (points[p] === handle) { - config.currentHandle = p; - config.currentTool = toolIndex; - } - } - } - this._modifying = true; - this._activateModify(element); - - // Interupt eventDispatchers - preventPropagation(evt); - } - - /** - * Event handler for MOUSE_MOVE during drawing event loop. - * - * @event - * @param {Object} evt - The event. - * @returns {undefined} - */ - _drawingMouseMoveCallback(evt) { - const eventData = evt.detail; - const { - currentPoints, - element - } = eventData; - const toolState = getToolState(element, this.name); - const config = this.configuration; - const currentTool = config.currentTool; - const data = toolState.data[currentTool]; - const coords = currentPoints.canvas; - - // Set the mouseLocation handle - this._getMouseLocation(eventData); - this._checkInvalidHandleLocation(data, eventData); - - // Mouse move -> Polygon Mode - const handleNearby = this._pointNearHandle(element, data, coords); - const points = data.handles.points; - // If there is a handle nearby to snap to - // (and it's not the actual mouse handle) - - if (handleNearby !== undefined && !handleNearby.hasBoundingBox && handleNearby < points.length - 1) { - config.mouseLocation.handles.start.x = points[handleNearby].x; - config.mouseLocation.handles.start.y = points[handleNearby].y; - } - - // Force onImageRendered - external.cornerstone.updateImage(element); - } - - /** - * Event handler for MOUSE_DRAG during drawing event loop. - * - * @event - * @param {Object} evt - The event. - * @returns {undefined} - */ - _drawingMouseDragCallback(evt) { - if (!this.options.mouseButtonMask.includes(evt.detail.buttons)) { - return; - } - this._drawingDrag(evt); - } - - /** - * Event handler for TOUCH_DRAG during drawing event loop. - * - * @event - * @param {Object} evt - The event. - * @returns {undefined} - */ - _drawingTouchDragCallback(evt) { - this._drawingDrag(evt); - } - _drawingDrag(evt) { - const eventData = evt.detail; - const { - element - } = eventData; - const toolState = getToolState(element, this.name); - const config = this.configuration; - const currentTool = config.currentTool; - const data = toolState.data[currentTool]; - - // Set the mouseLocation handle - this._getMouseLocation(eventData); - this._checkInvalidHandleLocation(data, eventData); - this._addPointPencilMode(eventData, data.handles.points); - this._dragging = true; - - // Force onImageRendered - external.cornerstone.updateImage(element); - } - - /** - * Event handler for MOUSE_UP during drawing event loop. - * - * @event - * @param {Object} evt - The event. - * @returns {undefined} - */ - _drawingMouseUpCallback(evt) { - const { - element - } = evt.detail; - if (!this._dragging) { - return; - } - this._dragging = false; - const config = this.configuration; - const currentTool = config.currentTool; - const toolState = getToolState(element, this.name); - const data = toolState.data[currentTool]; - if (!freehandIntersect.end(data.handles.points) && data.canComplete) { - const lastHandlePlaced = config.currentHandle; - this._endDrawing(element, lastHandlePlaced); - } - preventPropagation(evt); - return; - } - - /** - * Event handler for MOUSE_DOWN during drawing event loop. - * - * @event - * @param {Object} evt - The event. - * @returns {undefined} - */ - _drawingMouseDownCallback(evt) { - const eventData = evt.detail; - const { - buttons, - currentPoints, - element - } = eventData; - if (!this.options.mouseButtonMask.includes(buttons)) { - return; - } - const coords = currentPoints.canvas; - const config = this.configuration; - const currentTool = config.currentTool; - const toolState = getToolState(element, this.name); - const data = toolState.data[currentTool]; - const handleNearby = this._pointNearHandle(element, data, coords); - if (!freehandIntersect.end(data.handles.points) && data.canComplete) { - const lastHandlePlaced = config.currentHandle; - this._endDrawing(element, lastHandlePlaced); - } else if (handleNearby === undefined) { - this._addPoint(eventData); - } - preventPropagation(evt); - return; - } - - /** - * Event handler for TOUCH_START during drawing event loop. - * - * @event - * @param {Object} evt - The event. - * @returns {undefined} - */ - _drawingTouchStartCallback(evt) { - const eventData = evt.detail; - const { - currentPoints, - element - } = eventData; - const coords = currentPoints.canvas; - const config = this.configuration; - const currentTool = config.currentTool; - const toolState = getToolState(element, this.name); - const data = toolState.data[currentTool]; - const handleNearby = this._pointNearHandle(element, data, coords); - if (!freehandIntersect.end(data.handles.points) && data.canComplete) { - const lastHandlePlaced = config.currentHandle; - this._endDrawing(element, lastHandlePlaced); - } else if (handleNearby === undefined) { - this._addPoint(eventData); - } - preventPropagation(evt); - return; - } - - /** Ends the active drawing loop and completes the polygon. - * - * @public - * @param {Object} element - The element on which the roi is being drawn. - * @returns {null} - */ - completeDrawing(element) { - if (!this._drawing) { - return; - } - const toolState = getToolState(element, this.name); - const config = this.configuration; - const data = toolState.data[config.currentTool]; - if (!freehandIntersect.end(data.handles.points) && data.handles.points.length >= 2) { - const lastHandlePlaced = config.currentHandle; - data.polyBoundingBox = {}; - this._endDrawing(element, lastHandlePlaced); - } - } - - /** - * Event handler for MOUSE_DOUBLE_CLICK during drawing event loop. - * - * @event - * @param {Object} evt - The event. - * @returns {undefined} - */ - _drawingMouseDoubleClickCallback(evt) { - const { - element - } = evt.detail; - this.completeDrawing(element); - preventPropagation(evt); - } - - /** - * Event handler for DOUBLE_TAP during drawing event loop. - * - * @event - * @param {Object} evt - The event. - * @returns {undefined} - */ - _drawingDoubleTapClickCallback(evt) { - const { - element - } = evt.detail; - this.completeDrawing(element); - preventPropagation(evt); - } - - /** - * Event handler for MOUSE_DRAG during handle drag event loop. - * - * @event - * @param {Object} evt - The event. - * @returns {undefined} - */ - _editMouseDragCallback(evt) { - const eventData = evt.detail; - const { - element, - buttons - } = eventData; - if (!this.options.mouseButtonMask.includes(buttons)) { - return; - } - const toolState = getToolState(element, this.name); - const config = this.configuration; - const data = toolState.data[config.currentTool]; - const currentHandle = config.currentHandle; - const points = data.handles.points; - let handleIndex = -1; - - // Set the mouseLocation handle - this._getMouseLocation(eventData); - data.handles.invalidHandlePlacement = freehandIntersect.modify(points, currentHandle); - data.active = true; - data.highlight = true; - points[currentHandle].x = config.mouseLocation.handles.start.x; - points[currentHandle].y = config.mouseLocation.handles.start.y; - handleIndex = this._getPrevHandleIndex(currentHandle, points); - if (currentHandle >= 0) { - const lastLineIndex = points[handleIndex].lines.length - 1; - const lastLine = points[handleIndex].lines[lastLineIndex]; - lastLine.x = config.mouseLocation.handles.start.x; - lastLine.y = config.mouseLocation.handles.start.y; - } - - // Update the image - external.cornerstone.updateImage(element); - } - - /** - * Event handler for TOUCH_DRAG during handle drag event loop. - * - * @event - * @param {Object} evt - The event. - * @returns {void} - */ - _editTouchDragCallback(evt) { - const eventData = evt.detail; - const { - element - } = eventData; - const toolState = getToolState(element, this.name); - const config = this.configuration; - const data = toolState.data[config.currentTool]; - const currentHandle = config.currentHandle; - const points = data.handles.points; - let handleIndex = -1; - - // Set the mouseLocation handle - this._getMouseLocation(eventData); - data.handles.invalidHandlePlacement = freehandIntersect.modify(points, currentHandle); - data.active = true; - data.highlight = true; - points[currentHandle].x = config.mouseLocation.handles.start.x; - points[currentHandle].y = config.mouseLocation.handles.start.y; - handleIndex = this._getPrevHandleIndex(currentHandle, points); - if (currentHandle >= 0) { - const lastLineIndex = points[handleIndex].lines.length - 1; - const lastLine = points[handleIndex].lines[lastLineIndex]; - lastLine.x = config.mouseLocation.handles.start.x; - lastLine.y = config.mouseLocation.handles.start.y; - } - - // Update the image - external.cornerstone.updateImage(element); - } - - /** - * Returns the previous handle to the current one. - * @param {Number} currentHandle - the current handle index - * @param {Array} points - the handles Array of the freehand data - * @returns {Number} - The index of the previos handle - */ - _getPrevHandleIndex(currentHandle, points) { - if (currentHandle === 0) { - return points.length - 1; - } - return currentHandle - 1; - } - - /** - * Event handler for MOUSE_UP during handle drag event loop. - * - * @private - * @param {Object} evt - The event. - * @returns {undefined} - */ - _editMouseUpCallback(evt) { - const eventData = evt.detail; - const { - element - } = eventData; - const toolState = getToolState(element, this.name); - this._deactivateModify(element); - this._dropHandle(eventData, toolState); - this._endDrawing(element); - external.cornerstone.updateImage(element); - } - - /** - * Places a handle of the freehand tool if the new location is valid. - * If the new location is invalid the handle snaps back to its previous position. - * - * @private - * @param {Object} eventData - Data object associated with the event. - * @param {Object} toolState - The data associated with the freehand tool. - * @modifies {toolState} - * @returns {undefined} - */ - _dropHandle(eventData, toolState) { - const config = this.configuration; - const currentTool = config.currentTool; - const handles = toolState.data[currentTool].handles; - const points = handles.points; - - // Don't allow the line being modified to intersect other lines - if (handles.invalidHandlePlacement) { - const currentHandle = config.currentHandle; - const currentHandleData = points[currentHandle]; - let previousHandleData; - if (currentHandle === 0) { - const lastHandleID = points.length - 1; - previousHandleData = points[lastHandleID]; - } else { - previousHandleData = points[currentHandle - 1]; - } - - // Snap back to previous position - currentHandleData.x = config.dragOrigin.x; - currentHandleData.y = config.dragOrigin.y; - previousHandleData.lines[0] = currentHandleData; - handles.invalidHandlePlacement = false; - } - } - - /** - * Begining of drawing loop when tool is active and a click event happens far - * from existing handles. - * - * @private - * @param {Object} evt - The event. - * @returns {undefined} - */ - _startDrawing(evt) { - const eventData = evt.detail; - const measurementData = this.createNewMeasurement(eventData); - const { - element - } = eventData; - const config = this.configuration; - let interactionType; - if (evt.type === EVENTS.MOUSE_DOWN_ACTIVATE) { - interactionType = "Mouse"; - } else if (evt.type === EVENTS.TOUCH_START_ACTIVE) { - interactionType = "Touch"; - } - this._activateDraw(element, interactionType); - this._getMouseLocation(eventData); - addToolState(element, this.name, measurementData); - const toolState = getToolState(element, this.name); - config.currentTool = toolState.data.length - 1; - this._activeDrawingToolReference = toolState.data[config.currentTool]; - } - - /** - * Adds a point on mouse click in polygon mode. - * - * @private - * @param {Object} eventData - data object associated with an event. - * @returns {undefined} - */ - _addPoint(eventData) { - const { - currentPoints, - element - } = eventData; - const toolState = getToolState(element, this.name); - - // Get the toolState from the last-drawn polygon - const config = this.configuration; - const data = toolState.data[config.currentTool]; - if (data.handles.invalidHandlePlacement) { - return; - } - const newHandleData = new FreehandHandleData(currentPoints.image); - - // If this is not the first handle - if (data.handles.points.length) { - // Add the line from the current handle to the new handle - data.handles.points[config.currentHandle - 1].lines.push(currentPoints.image); - } - - // Add the new handle - data.handles.points.push(newHandleData); - - // Increment the current handle value - config.currentHandle += 1; - - // Force onImageRendered to fire - external.cornerstone.updateImage(element); - this.fireModifiedEvent(element, data); - } - - /** - * If in pencilMode, check the mouse position is farther than the minimum - * distance between points, then add a point. - * - * @private - * @param {Object} eventData - Data object associated with an event. - * @param {Object} points - Data object associated with the tool. - * @returns {undefined} - */ - _addPointPencilMode(eventData, points) { - const config = this.configuration; - const { - element - } = eventData; - const mousePoint = config.mouseLocation.handles.start; - const handleFurtherThanMinimumSpacing = handle => this._isDistanceLargerThanSpacing(element, handle, mousePoint); - if (points.every(handleFurtherThanMinimumSpacing)) { - this._addPoint(eventData); - } - } - - /** - * Ends the active drawing loop and completes the polygon. - * - * @private - * @param {Object} element - The element on which the roi is being drawn. - * @param {Object} handleNearby - the handle nearest to the mouse cursor. - * @returns {undefined} - */ - _endDrawing(element, handleNearby) { - const toolState = getToolState(element, this.name); - const config = this.configuration; - const data = toolState.data[config.currentTool]; - data.active = false; - data.highlight = false; - data.handles.invalidHandlePlacement = false; - - // Connect the end handle to the origin handle - if (handleNearby !== undefined) { - const points = data.handles.points; - points[config.currentHandle - 1].lines.push(points[0]); - } - if (this._modifying) { - this._modifying = false; - data.invalidated = true; - } - - // Reset the current handle - config.currentHandle = 0; - config.currentTool = -1; - data.canComplete = false; - if (this._drawing) { - this._deactivateDraw(element); - } - external.cornerstone.updateImage(element); - this.fireModifiedEvent(element, data); - this.fireCompletedEvent(element, data); - } - - /** - * Returns a handle of a particular tool if it is close to the mouse cursor - * - * @private - * @param {Object} element - The element on which the roi is being drawn. - * @param {Object} data Data object associated with the tool. - * @param {*} coords - * @returns {Number|Object|Boolean} - */ - _pointNearHandle(element, data, coords) { - if (data.handles === undefined || data.handles.points === undefined) { - return; - } - if (data.visible === false) { - return; - } - for (let i = 0; i < data.handles.points.length; i++) { - const handleCanvas = external.cornerstone.pixelToCanvas(element, data.handles.points[i]); - if (external.cornerstoneMath.point.distance(handleCanvas, coords) < 6) { - return i; - } - } - - // Check to see if mouse in bounding box of textbox - if (data.handles.textBox) { - if (pointInsideBoundingBox(data.handles.textBox, coords)) { - return data.handles.textBox; - } + // Check to see if mouse in bounding box of textbox + if (data.handles.textBox) { + if (pointInsideBoundingBox(data.handles.textBox, coords)) { + return data.handles.textBox; + } } } @@ -2456,651 +1265,1782 @@ class ContoursTool extends BaseAnnotationTool { return false; } - /** - * Returns true if two points are closer than this.configuration.spacing. - * - * @private - * @param {Object} element The element on which the roi is being drawn. - * @param {Object} p1 The first point, in pixel space. - * @param {Object} p2 The second point, in pixel space. - * @returns {boolean} True if the distance is smaller than the - * allowed canvas spacing. - */ - _isDistanceSmallerThanCompleteSpacingCanvas(element, p1, p2) { - const p1Canvas = external.cornerstone.pixelToCanvas(element, p1); - const p2Canvas = external.cornerstone.pixelToCanvas(element, p2); - let completeHandleRadius; - if (this._drawingInteractionType === "Mouse") { - completeHandleRadius = this.configuration.completeHandleRadius; - } else if (this._drawingInteractionType === "Touch") { - completeHandleRadius = this.configuration.completeHandleRadiusTouch; - } - return this._compareDistanceToSpacing(element, p1Canvas, p2Canvas, "<", completeHandleRadius); - } + /** + * Returns true if two points are closer than this.configuration.spacing. + * + * @private + * @param {Object} element The element on which the roi is being drawn. + * @param {Object} p1 The first point, in pixel space. + * @param {Object} p2 The second point, in pixel space. + * @returns {boolean} True if the distance is smaller than the + * allowed canvas spacing. + */ + _isDistanceSmallerThanCompleteSpacingCanvas(element, p1, p2) { + const p1Canvas = external.cornerstone.pixelToCanvas(element, p1); + const p2Canvas = external.cornerstone.pixelToCanvas(element, p2); + let completeHandleRadius; + if (this._drawingInteractionType === "Mouse") { + completeHandleRadius = this.configuration.completeHandleRadius; + } else if (this._drawingInteractionType === "Touch") { + completeHandleRadius = this.configuration.completeHandleRadiusTouch; + } + return this._compareDistanceToSpacing(element, p1Canvas, p2Canvas, "<", completeHandleRadius); + } + + /** + * Returns true if two points are closer than this.configuration.spacing. + * + * @private + * @param {Object} element The element on which the roi is being drawn. + * @param {Object} p1 The first point, in pixel space. + * @param {Object} p2 The second point, in pixel space. + * @returns {boolean} True if the distance is smaller than the + * allowed canvas spacing. + */ + _isDistanceSmallerThanSpacing(element, p1, p2) { + return this._compareDistanceToSpacing(element, p1, p2, "<"); + } + + /** + * Returns true if two points are farther than this.configuration.spacing. + * + * @private + * @param {Object} element The element on which the roi is being drawn. + * @param {Object} p1 The first point, in pixel space. + * @param {Object} p2 The second point, in pixel space. + * @returns {boolean} True if the distance is smaller than the + * allowed canvas spacing. + */ + _isDistanceLargerThanSpacing(element, p1, p2) { + return this._compareDistanceToSpacing(element, p1, p2, ">"); + } + + /** + * Compares the distance between two points to this.configuration.spacing. + * + * @private + * @param {Object} element The element on which the roi is being drawn. + * @param {Object} p1 The first point, in pixel space. + * @param {Object} p2 The second point, in pixel space. + * @param {string} comparison The comparison to make. + * @param {number} spacing The allowed canvas spacing + * @returns {boolean} True if the distance is smaller than the + * allowed canvas spacing. + */ + _compareDistanceToSpacing(element, p1, p2, comparison = ">", spacing = this.configuration.spacing) { + if (comparison === ">") { + return external.cornerstoneMath.point.distance(p1, p2) > spacing; + } + return external.cornerstoneMath.point.distance(p1, p2) < spacing; + } + + /** + * Adds drawing loop event listeners. + * + * @private + * @param {Object} element - The viewport element to add event listeners to. + * @param {string} interactionType - The interactionType used for the loop. + * @modifies {element} + * @returns {undefined} + */ + _activateDraw(element, interactionType = "Mouse") { + this._drawing = true; + this._drawingInteractionType = interactionType; + state.isMultiPartToolActive = true; + // hideToolCursor(this.element); + + // Polygonal Mode + element.addEventListener(EVENTS.MOUSE_DOWN, this._drawingMouseDownCallback); + element.addEventListener(EVENTS.MOUSE_MOVE, this._drawingMouseMoveCallback); + element.addEventListener(EVENTS.MOUSE_DOUBLE_CLICK, this._drawingMouseDoubleClickCallback); + + // Drag/Pencil Mode + element.addEventListener(EVENTS.MOUSE_DRAG, this._drawingMouseDragCallback); + element.addEventListener(EVENTS.MOUSE_UP, this._drawingMouseUpCallback); + + // Touch + element.addEventListener(EVENTS.TOUCH_START, this._drawingMouseMoveCallback); + element.addEventListener(EVENTS.TOUCH_START, this._drawingTouchStartCallback); + element.addEventListener(EVENTS.TOUCH_DRAG, this._drawingTouchDragCallback); + element.addEventListener(EVENTS.TOUCH_END, this._drawingMouseUpCallback); + element.addEventListener(EVENTS.DOUBLE_TAP, this._drawingDoubleTapClickCallback); + external.cornerstone.updateImage(element); + } + + /** + * Removes drawing loop event listeners. + * + * @private + * @param {Object} element - The viewport element to add event listeners to. + * @modifies {element} + * @returns {undefined} + */ + _deactivateDraw(element) { + this._drawing = false; + state.isMultiPartToolActive = false; + this._activeDrawingToolReference = null; + this._drawingInteractionType = null; + // setToolCursor(this.element, this.svgCursor); + + element.removeEventListener(EVENTS.MOUSE_DOWN, this._drawingMouseDownCallback); + element.removeEventListener(EVENTS.MOUSE_MOVE, this._drawingMouseMoveCallback); + element.removeEventListener(EVENTS.MOUSE_DOUBLE_CLICK, this._drawingMouseDoubleClickCallback); + element.removeEventListener(EVENTS.MOUSE_DRAG, this._drawingMouseDragCallback); + element.removeEventListener(EVENTS.MOUSE_UP, this._drawingMouseUpCallback); + + // Touch + element.removeEventListener(EVENTS.TOUCH_START, this._drawingTouchStartCallback); + element.removeEventListener(EVENTS.TOUCH_DRAG, this._drawingTouchDragCallback); + element.removeEventListener(EVENTS.TOUCH_START, this._drawingMouseMoveCallback); + element.removeEventListener(EVENTS.TOUCH_END, this._drawingMouseUpCallback); + external.cornerstone.updateImage(element); + } + + /** + * Adds modify loop event listeners. + * + * @private + * @param {Object} element - The viewport element to add event listeners to. + * @modifies {element} + * @returns {undefined} + */ + _activateModify(element) { + state.isToolLocked = true; + element.addEventListener(EVENTS.MOUSE_UP, this._editMouseUpCallback); + element.addEventListener(EVENTS.MOUSE_DRAG, this._editMouseDragCallback); + element.addEventListener(EVENTS.MOUSE_CLICK, this._editMouseUpCallback); + element.addEventListener(EVENTS.TOUCH_END, this._editMouseUpCallback); + element.addEventListener(EVENTS.TOUCH_DRAG, this._editTouchDragCallback); + external.cornerstone.updateImage(element); + } + + /** + * Removes modify loop event listeners. + * + * @private + * @param {Object} element - The viewport element to add event listeners to. + * @modifies {element} + * @returns {undefined} + */ + _deactivateModify(element) { + state.isToolLocked = false; + element.removeEventListener(EVENTS.MOUSE_UP, this._editMouseUpCallback); + element.removeEventListener(EVENTS.MOUSE_DRAG, this._editMouseDragCallback); + element.removeEventListener(EVENTS.MOUSE_CLICK, this._editMouseUpCallback); + element.removeEventListener(EVENTS.TOUCH_END, this._editMouseUpCallback); + element.removeEventListener(EVENTS.TOUCH_DRAG, this._editTouchDragCallback); + external.cornerstone.updateImage(element); + } + passiveCallback(element) { + this._closeToolIfDrawing(element); + } + enabledCallback(element) { + this._closeToolIfDrawing(element); + } + disabledCallback(element) { + this._closeToolIfDrawing(element); + } + _closeToolIfDrawing(element) { + if (this._drawing) { + // Actively drawing but changed mode. + const config = this.configuration; + const lastHandlePlaced = config.currentHandle; + this._endDrawing(element, lastHandlePlaced); + external.cornerstone.updateImage(element); + } + } + + /** + * Fire MEASUREMENT_MODIFIED event on provided element + * @param {any} element which freehand data has been modified + * @param {any} measurementData the measurment data + * @returns {void} + */ + fireModifiedEvent(element, measurementData) { + const eventType = EVENTS.MEASUREMENT_MODIFIED; + const eventData = { + toolName: this.name, + element, + measurementData + }; + triggerEvent(element, eventType, eventData); + } + fireCompletedEvent(element, measurementData) { + const eventType = EVENTS.MEASUREMENT_COMPLETED; + const eventData = { + toolName: this.name, + element, + measurementData + }; + triggerEvent(element, eventType, eventData); + } + + // =================================================================== + // Public Configuration API. . + // =================================================================== + + get spacing() { + return this.configuration.spacing; + } + set spacing(value) { + if (typeof value !== "number") { + throw new Error("Attempting to set freehand spacing to a value other than a number."); + } + this.configuration.spacing = value; + external.cornerstone.updateImage(this.element); + } + get activeHandleRadius() { + return this.configuration.activeHandleRadius; + } + set activeHandleRadius(value) { + if (typeof value !== "number") { + throw new Error("Attempting to set freehand activeHandleRadius to a value other than a number."); + } + this.configuration.activeHandleRadius = value; + external.cornerstone.updateImage(this.element); + } + get completeHandleRadius() { + return this.configuration.completeHandleRadius; + } + set completeHandleRadius(value) { + if (typeof value !== "number") { + throw new Error("Attempting to set freehand completeHandleRadius to a value other than a number."); + } + this.configuration.completeHandleRadius = value; + external.cornerstone.updateImage(this.element); + } + get alwaysShowHandles() { + return this.configuration.alwaysShowHandles; + } + set alwaysShowHandles(value) { + if (typeof value !== "boolean") { + throw new Error("Attempting to set freehand alwaysShowHandles to a value other than a boolean."); + } + this.configuration.alwaysShowHandles = value; + external.cornerstone.updateImage(this.element); + } + get invalidColor() { + return this.configuration.invalidColor; + } + set invalidColor(value) { + /* + It'd be easy to check if the color was e.g. a valid rgba color. However + it'd be difficult to check if the color was a named CSS color without + bloating the library, so we don't. If the canvas can't intepret the color + it'll show up grey. + */ + + this.configuration.invalidColor = value; + external.cornerstone.updateImage(this.element); + } + + /** + * Ends the active drawing loop and removes the polygon. + * + * @public + * @param {Object} element - The element on which the roi is being drawn. + * @returns {null} + */ + cancelDrawing(element) { + if (!this._drawing) { + return; + } + const toolState = getToolState(element, this.name); + const config = this.configuration; + const data = toolState.data[config.currentTool]; + data.active = false; + data.highlight = false; + data.handles.invalidHandlePlacement = false; + + // Reset the current handle + config.currentHandle = 0; + config.currentTool = -1; + data.canComplete = false; + removeToolState(element, this.name, data); + this._deactivateDraw(element); + external.cornerstone.updateImage(element); + } + + /** + * New image event handler. + * + * @public + * @param {Object} evt The event. + * @returns {null} + */ + newImageCallback(evt) { + const config = this.configuration; + if (!(this._drawing && this._activeDrawingToolReference)) { + return; + } + + // Actively drawing but scrolled to different image. + + const element = evt.detail.element; + const data = this._activeDrawingToolReference; + data.active = false; + data.highlight = false; + data.handles.invalidHandlePlacement = false; + + // Connect the end handle to the origin handle + const points = data.handles.points; + points[config.currentHandle - 1].lines.push(points[0]); + + // Reset the current handle + config.currentHandle = 0; + config.currentTool = -1; + data.canComplete = false; + this._deactivateDraw(element); + external.cornerstone.updateImage(element); + } +} +function defaultFreehandConfiguration() { + return { + mouseLocation: { + handles: { + start: { + highlight: false, + active: false + } + } + }, + spacing: 1, + activeHandleRadius: 3, + completeHandleRadius: 6, + completeHandleRadiusTouch: 28, + alwaysShowHandles: false, + invalidColor: "#FFFF00", + currentHandle: 0, + currentTool: -1 + }; +} +function preventPropagation(evt) { + evt.stopImmediatePropagation(); + evt.stopPropagation(); + evt.preventDefault(); +} +;// CONCATENATED MODULE: ./imaging/tools/custom/editMaskTool.js +/** @module imaging/tools/custom/editMaskTool + * @desc This file provides functionalities for + * a custom mask cornestone tool + */ + +// external libraries + +const editMaskTool_external = (cornerstoneTools_default()).external; +const BaseBrushTool = cornerstoneTools_default().importInternal("base/BaseBrushTool"); +const segmentationUtils = cornerstoneTools_default().importInternal("util/segmentationUtils"); +const getCircle = segmentationUtils.getCircle; +const drawBrushPixels = segmentationUtils.drawBrushPixels; +const getModule = (cornerstoneTools_default()).getModule; +const { + configuration, + setters +} = getModule("segmentation"); + +/** + * @public + * @class BrushTool + * @memberof Tools.Brush + * @classdesc Tool for drawing segmentations on an image. + * @extends Tools.Base.BaseBrushTool + */ +class EditMaskTool extends BaseBrushTool { + constructor(props = {}) { + const defaultProps = { + name: "EditMask", + supportedInteractionTypes: ["Mouse"], + configuration: {}, + mixins: ["renderBrushMixin"] + }; + super(props, defaultProps); + this.touchDragCallback = this._paint.bind(this); + this._initializeTool(props.mask, props.initCallback); + } + _initializeTool(mask, callback) { + let enabledElement = cornerstoneTools_default().external.cornerstone.getEnabledElements().filter(e => e.element.id == "axial").pop(); + + // TODO improve performances! + + console.time("...typedToNormal"); + let pixelsNormalArray = Array.prototype.slice.call(mask.data); + console.timeEnd("...typedToNormal"); + console.time("...normalToTyped"); + let pixelData = Uint16Array.from(pixelsNormalArray); + console.timeEnd("...normalToTyped"); + let labelmapIndex = 1; + let segmentsOnLabelmapArray = new Array(mask.sizes[2]).fill([labelmapIndex]); + setters.labelmap3DForElement(enabledElement.element, pixelData.buffer, labelmapIndex, [], segmentsOnLabelmapArray, 0); + cornerstoneTools_default().external.cornerstone.updateImage(enabledElement.element); + if (callback) { + callback(); + } + // SIMPLE WAY --- AS FALLBACK : + + // labelmap3D.labelmaps2D = []; + + // for (let f = 0; f < 110; f++) { + // let pixels = mask.data.slice(f * 512 * 512, (f + 1) * 512 * 512); + // let pixelsNormalArray = Array.prototype.slice.call(pixels); + // // let pixelData = new Uint16Array(); + // let pixelData = Uint16Array.from(pixelsNormalArray); + // labelmap3D.labelmaps2D.push({ + // pixelData: pixelData, + // segmentsOnLabelmap: [labelmapIndex] + // }); + // } + } + activeCallback(element, options) { + switch (options.force) { + case "delete": + this.configuration.alwaysEraseOnClick = true; + break; + case "add": + this.preventCtrl(); + break; + default: + this.configuration.alwaysEraseOnClick = false; + } + } + preventCtrl() { + this.__proto__.__proto__._isCtrlDown = function () { + return false; + }; + } + + /** + * Paints the data to the labelmap. + * + * @protected + * @param {Object} evt The data object associated with the event. + * @returns {void} + */ + _paint(evt) { + const eventData = evt.detail; + const { + rows, + columns + } = eventData.image; + const { + x, + y + } = eventData.currentPoints.image; + if (x < 0 || x > columns || y < 0 || y > rows) { + return; + } + const radius = configuration.radius; + const pointerArray = getCircle(radius, rows, columns, x, y); + const { + labelmap2D, + labelmap3D, + shouldErase + } = this.paintEventData; + + // Draw / Erase the active color. + drawBrushPixels(pointerArray, labelmap2D.pixelData, labelmap3D.activeSegmentIndex, columns, shouldErase); + editMaskTool_external.cornerstone.updateImage(evt.detail.element); + } +} +;// CONCATENATED MODULE: ./imaging/tools/custom/diameterTool.js +/** @module imaging/tools/custom/diameterTool + * @desc This file provides functionalities for + * a custom diameter cornestone tool + */ + +// external libraries + +const BidirectionalTool = (cornerstoneTools_default()).BidirectionalTool; + + +// internal libraries + + +/** + * @public + * @class DiameterTool + * @memberof Tools.Annotation + * @classdesc Create and position an annotation that measures the + * length and width of a region. + * @extends Tools.Base.BaseAnnotationTool + */ + +class DiameterTool extends BidirectionalTool { + constructor(props) { + const defaultProps = { + name: "Diameter", + isBeenModified: false, + lastData: null + }; + super(props, defaultProps); + this.name = "Diameter"; + this.initializeTool(props.dataArray, "cmprAxial", props.seriesId); + } + initializeTool(dataArray, elementId, seriesId) { + let element = document.getElementById(elementId); + (0,lodash.each)(dataArray, singleData => { + let data = { + toolType: "Diameter", + name: singleData.id.toString(), + isCreating: true, + visible: true, + active: false, + invalidated: false, + handles: { + start: { + x: singleData.x1, + y: singleData.y1, + index: 0, + drawnIndependently: false, + allowedOutsideImage: false, + highlight: true, + active: false + }, + end: { + x: singleData.x2, + y: singleData.y2, + index: 1, + drawnIndependently: false, + allowedOutsideImage: false, + highlight: true, + active: false + }, + perpendicularStart: { + x: singleData.x3, + y: singleData.y3, + index: 2, + drawnIndependently: false, + allowedOutsideImage: false, + highlight: true, + active: false, + locked: false + }, + perpendicularEnd: { + x: singleData.x4, + y: singleData.y4, + index: 3, + drawnIndependently: false, + allowedOutsideImage: false, + highlight: true, + active: false + }, + textBox: { + x: singleData.value_max, + y: singleData.value_min, + index: null, + drawnIndependently: true, + allowedOutsideImage: true, + highlight: false, + active: false, + hasMoved: true, + movesIndependently: false, + hasBoundingBox: true, + boundingBox: { + width: 59.6484375, + height: 47, + left: 165.02487562189057, + top: 240.53482587064684 + } + } + }, + longestDiameter: singleData.value_max.toString(), + shortestDiameter: singleData.value_min.toString() + }; + let sliceNumber = singleData.slice; + + // add to master viewport + addToolStateSingleSlice(element, this.name, data, sliceNumber, seriesId); + }); + cornerstoneTools_default().external.cornerstone.updateImage(element); + } + passiveCallback(element) { + element.addEventListener("cornerstonetoolsmeasurementmodified", this.measureOnGoingCallback); + } + measureOnGoingCallback(event) { + if (!this.isBeenModified) { + event.target.addEventListener("mouseup", function (evt) { + this.__proto__.measureEndCallback(evt); + }); + } + this.isBeenModified = true; + this.lastData = event.detail.measurementData; + } + measureEndCallback(event) { + event.element.removeEventListener("mouseup", this.measureEndCallback); + this.isBeenModified = false; + this.lastData = null; + } +} +// EXTERNAL MODULE: ./imaging/loaders/nrrdLoader.ts +var nrrdLoader = __webpack_require__(6934); +// EXTERNAL MODULE: ./imaging/loaders/commonLoader.ts +var commonLoader = __webpack_require__(326); +// EXTERNAL MODULE: ./imaging/imageContours.ts +var imageContours = __webpack_require__(4918); +// EXTERNAL MODULE: ./imaging/imageUtils.ts +var imageUtils = __webpack_require__(8345); +;// CONCATENATED MODULE: ./imaging/imageTools.js +/** @module imaging/imageTools + * @desc This file provides functionalities for + * interacting with cornerstone tools + * DEPRECATION WARNING: these are legacy functions + * that will be removed soon. Use the corresponding + * functions in /tools/main.js instead. + * For this reason, this file will not be translated to TypeScript. + */ + +// external libraries + + + + +// internal libraries + +// import { SeedsTool } from "./tools/custom/seedTool"; + + + + + + + + +/* + * This module provides the following functions to be exported: + * csToolsCreateStack(element) + * addDefaultTools(toolToActivate) + * clearMeasurements() + * addContoursTool(rawContours, maskName) + * addMaskEditingTool(seriesId,mask,setConfig,callback, targetViewport) + * getCurrentMaskData(viewportId) + * addStackStateToElement(seriesId, element) + * addSeedsTool(preLoadSeeds, initViewport) + * addDiameterTool(targetElementId, diameters, seriesId) + * setToolActive(toolName, options, activeViewport, viewports) + * setToolDisabled(toolName, options, activeViewport, viewports) + * setToolEnabled(toolName, options, activeViewport, viewports) + * setToolPassive(toolName, options, activeViewport, viewports) + * getToolState(toolName) + * updateDiameterTool(diameterId, value, seriesId) + * addToolStateSingleSlice(element, toolType, data, slice, seriesId) + * clearToolStateByName(toolName, options) + * clearCornerstoneElements() + * syncToolStack(srcSliceNumber, toolName, viewport, seriesId) + * updateStackToolState(element, imageIndex) + */ + +/** + * Add all default tools, as listed in tools/default.js + * @function addDefaultTools + * @deprecated (OBSOLETE) + */ +const addDefaultTools = function (toolToActivate) { + // for each default tool + (0,lodash.each)(tools_default.DEFAULT_TOOLS, tool => { + // check if already added + if (!isToolMissing(tool.name)) { + return; + } + let configuration = tool.configuration; + let toolClass = (cornerstoneTools_default())[tool.class]; + + // check target viewports and call add tool with options + if (tool.viewports == "all") { + cornerstoneTools_default().addTool(toolClass, { + configuration + }); + } else { + // call add tool for element for each element + (0,lodash.each)(tool.viewports, targetElement => { + cornerstoneTools_default().addToolForElement(targetElement, toolClass, configuration); + }); + } + let elements = cornerstone_default().getEnabledElements(); + + // if sync tool, enable + if (tool.sync) { + const synchronizer = new (cornerstoneTools_default()).Synchronizer("cornerstoneimagerendered", (cornerstoneTools_default())[tool.sync]); + elements.forEach(element => { + synchronizer.add(element.element); + }); + synchronizer.enabled = true; + } + if (tool.defaultActive || tool.name == toolToActivate) { + setToolActive(tool.name, tool.options); + } + }); + + // set wheel scroll active + setToolActive("CustomMouseWheelScroll", { + loop: false, + // default false + allowSkipping: false, + // default true + invert: false + }); +}; + +/** + * Add Diameter tool + * @function addDiameterTool + * @param {String} elementId - The target hmtl element id or its DOM HTMLElement + * @param {Array} diameters - The array of diameter objects. + * @param {String} seriesId - The id of the target serie. + */ +const addDiameterTool = function (elementId, diameters, seriesId) { + if (isToolMissing("Diameter")) { + let element = (0,imageUtils.isElement)(elementId) ? elementId : document.getElementById(elementId); + cornerstoneTools_default().addToolForElement(element, DiameterTool, { + dataArray: diameters, + seriesId: seriesId + }); + setToolPassive("Diameter"); + } +}; + +/** + * Add Contour tool + * @function addContoursTool + * @param {Object} rawContours - The contours object (generated from a segmentation mask). + * @param {String} maskName - The name tag that identify the mask + */ +const addContoursTool = function (rawContours, maskName) { + var pointBatchSize = 2; + console.time("...parsing contours"); + var contoursParsedData = (0,imageContours.parseContours)(rawContours, pointBatchSize, maskName); + console.timeEnd("...parsing contours"); + cornerstoneTools_default().addTool(ContoursTool, { + contoursParsedData, + maskName + }); +}; + +/** + * Add mask editing tool + * @function addMaskEditingTool + * @param {Array} mask - The mask data. + * @param {Function} callback - The tool initialization callback + * @param {String} targetViewport - The target hmtl element id. + */ +const addMaskEditingTool = function (mask, callback, targetViewport) { + let enabledElements = cornerstone_default().getEnabledElements(); + (0,lodash.each)(enabledElements, el => { + if (el.element.id == targetViewport) { + cornerstoneTools_default().addToolForElement(el.element, EditMaskTool, { + mask: mask, + initCallback: callback, + configuration: { + alwaysEraseOnClick: false + } + }); + cornerstoneTools_default().setToolEnabledForElement(el.element, "EditMask", { + mouseButtonMask: 1 + }); + } + }); + let defaultConfig = { + radius: 5, + fillAlpha: 0.5 + }; + setSegmentationConfig(defaultConfig); +}; + +/** + * Modify configuration for cornerstone tools segmentation module + * @function setSegmentationConfig + * @param {Object} config - The custom configuration. + * @example + * Example of custom configuration + * config = { + renderOutline: true, + renderFill: true, + shouldRenderInactiveLabelmaps: true, + radius: 10, + minRadius: 1, + maxRadius: 50, + segmentsPerLabelmap: 65535, + fillAlpha: 0.7, + fillAlphaInactive: 0.1, + outlineAlpha: 0.7, + outlineAlphaInactive: 0.35, + outlineWidth: 3, + storeHistory: true + }; + */ +const setSegmentationConfig = function (config) { + let { + configuration + } = cornerstoneTools_default().getModule("segmentation"); + (0,lodash.extend)(configuration, config); + let enabledElements = cornerstone_default().getEnabledElements(); + (0,lodash.each)(enabledElements, el => { + cornerstone_default().updateImage(el.element); + }); +}; + +/** + * Get mask editing tool current data from state + * @function getCurrentMaskData + * @param {String} viewportId - The target hmtl element id. + * @return {Array} labelmap3D - The mask array + */ +const getCurrentMaskData = function (viewportId) { + const { + getters + } = cornerstoneTools_default().getModule("segmentation"); + let enabledElement = cornerstone_default().getEnabledElements().filter(e => e.element.id == viewportId).pop(); + const { + labelmap3D + } = getters.labelmap2D(enabledElement.element); + return labelmap3D; +}; + +/** + * Add Stack State to a single hmtl element + * @function addStackStateToElement + * @param {String} seriesId - The id of the target serie. + * @param {HTMLElement} element - The target hmtl element. + */ +const addStackStateToElement = function (seriesId, element) { + // Define the Stack object + const stack = (0,commonLoader.getSeriesDataFromLarvitarManager)(seriesId)[element.id]; + // Add the stack tool state to the enabled element + cornerstoneTools_default().addStackStateManager(element, ["stack"]); + cornerstoneTools_default().addToolState(element, "stack", stack); +}; + +/** + * Add seeds tool + * @function addSeedsTool + * @param {Array} preLoadSeeds - The array of seeds to load as initialization. + * @param {String} initViewport - The hmtl element id to be used for tool initialization. + */ +const addSeedsTool = function (preLoadSeeds, initViewport) { + if (isToolMissing("Seeds")) { + let enabledElements = cornerstone_default().getEnabledElements(); + (0,lodash.each)(enabledElements, el => { + let initialize = el.element.id == initViewport; + cornerstoneTools_default().addToolForElement(el.element, SeedsTool, { + preLoadSeeds, + initialize, + initViewport + }); + }); + setToolEnabled("Seeds"); + } +}; + +/** + * Delete all measurements from tools state, for tools that have the "cleaneable" prop set to true in tools/default.js + * @function clearMeasurements + */ +const clearMeasurements = function () { + let enabledElements = cornerstone_default().getEnabledElements(); + let clenableTools = (0,lodash.filter)(tools_default.DEFAULT_TOOLS, "cleanable"); + (0,lodash.each)(enabledElements, el => { + (0,lodash.each)(clenableTools, function (tool) { + cornerstoneTools_default().clearToolState(el.element, tool.name); + }); + }); + (0,lodash.each)(enabledElements, el => { + cornerstone_default().updateImage(el.element); + }); +}; + +/** + * Set Tool "active" on all elements (ie, rendered and manipulable) & refresh cornerstone elements + * @function setToolActive + * @param {String} toolName - The tool name. + * @param {Object} options - The custom options. @default from tools/default.js + * @param {String} activeViewport - The active viewport (if "all", viewports array will be used) + * @param {Array} viewports - The hmtl element id to be used for tool initialization. + * @deprecated (OBSOLETE) + */ +const setToolActive = function (toolName, options, activeViewport, viewports) { + let defaultOpt = tools_default.DEFAULT_TOOLS[toolName]?.options || {}; + (0,lodash.extend)(defaultOpt, options); + cornerstoneTools_default().setToolActive(toolName, defaultOpt); + if (activeViewport == "all") { + (0,lodash.each)(viewports, function (elementId) { + let el = document.getElementById(elementId); + if (el) { + cornerstone_default().updateImage(el); + } + }); + } else { + let el = document.getElementById(activeViewport); + if (el) { + cornerstone_default().updateImage(el); + } + } +}; + +/** + * Set Tool "disabled" on all elements (ie, not rendered) & refresh cornerstone elements + * @function setToolDisabled + * @param {String} toolName - The tool name. + * @param {String} activeViewport - The active viewport (if "all", viewports array will be used) + * @param {Array} viewports - The hmtl element id to be used for tool initialization. + * @deprecated (OBSOLETE) + */ +const setToolDisabled = function (toolName, activeViewport, viewports) { + cornerstoneTools_default().setToolDisabled(toolName); + if (activeViewport == "all") { + (0,lodash.each)(viewports, function (elementId) { + let el = document.getElementById(elementId); + if (el) { + cornerstone_default().updateImage(el); + } + }); + } else { + let el = document.getElementById(activeViewport); + if (el) { + cornerstone_default().updateImage(el); + } + } +}; + +/** + * Set Tool "enabled" on all elements (ie, rendered but not manipulable) & refresh cornerstone elements + * @function setToolEnabled + * @param {String} toolName - The tool name. + * @param {String} activeViewport - The active viewport (if "all", viewports array will be used) + * @param {Array} viewports - The hmtl element id to be used for tool initialization. + * @deprecated (OBSOLETE) + */ +const setToolEnabled = function (toolName, activeViewport, viewports) { + cornerstoneTools_default().setToolEnabled(toolName); + if (activeViewport == "all") { + (0,lodash.each)(viewports, function (elementId) { + let el = document.getElementById(elementId); + if (el) { + cornerstone_default().updateImage(el); + } + }); + } else { + let el = document.getElementById(activeViewport); + if (el) { + cornerstone_default().updateImage(el); + } + } +}; + +/** + * Set Tool "enabled" on all elements (ie, rendered and manipulable passively) & refresh cornerstone elements + * @function setToolPassive + * @param {String} toolName - The tool name. + * @param {String} activeViewport - The active viewport (if "all", viewports array will be used) + * @param {Array} viewports - The hmtl element id to be used for tool initialization. + * @deprecated (OBSOLETE) + */ +const setToolPassive = function (toolName, activeViewport, viewports) { + cornerstoneTools_default().setToolPassive(toolName); + if (activeViewport == "all") { + (0,lodash.each)(viewports, function (elementId) { + let el = document.getElementById(elementId); + if (el) { + cornerstone_default().updateImage(el); + } + }); + } else { + let el = document.getElementById(activeViewport); + if (el) { + cornerstone_default().updateImage(el); + } + } +}; + +/** + * Get tool data for all enabled elements + * @function getToolState + * @param {String} toolName - The tool name. + * @return {Object} - Tool data grouped by element id + */ +const imageTools_getToolState = function (toolName) { + let enabledElements = cornerstone_default().getEnabledElements(); + let toolData = {}; + (0,lodash.each)(enabledElements, el => { + toolData[el.element.id] = cornerstoneTools_default().getToolState(el.element, toolName); + }); + return toolData; +}; + +/** + * Clear tool data for a subset of seeds + * @function clearToolStateByName + * @param {String} toolName - The tool name. + * @param {Object} options - Props used to select the data to delete (at the moment only {name : "targetName"} is implemented) + */ +const clearToolStateByName = function (toolName, options) { + let enabledElements = cornerstone_default().getEnabledElements(); + (0,lodash.each)(enabledElements, el => { + const toolStateManager = el.toolStateManager; + let imageIds = Object.keys(toolStateManager.toolState); + (0,lodash.each)(imageIds, imageId => { + let toolData = toolStateManager.toolState[imageId]; + if (toolData[toolName]) { + (0,lodash.remove)(toolData[toolName].data, singleData => { + return singleData.name == options.name; + }); + } + }); + }); + (0,lodash.each)(enabledElements, el => { + cornerstone_default().updateImage(el.element); + }); +}; + +/** + * Update diameter tool with new value (removing old one) + * @function updateDiameterTool + * @param {String | Number} diameterId - The id that identify the diameter data. + * @param {Object} value - The object representing the new diameter data. + * @param {String} seriesId - The target serie id. + * @param {String} viewportId - The viewport id. + */ +const updateDiameterTool = function (diameterId, value, seriesId, viewportId) { + // clear target diameter + if (!diameterId) { + console.warn("no diameterId, return"); + return; + } + clearToolStateByName("Diameter", { + name: diameterId + }); + // insert new one + let data = { + toolType: "Diameter", + name: diameterId, + isCreating: true, + visible: true, + active: false, + invalidated: false, + handles: { + start: { + x: value.tool.x1, + y: value.tool.y1, + index: 0, + drawnIndependently: false, + allowedOutsideImage: false, + highlight: true, + active: false + }, + end: { + x: value.tool.x2, + y: value.tool.y2, + index: 1, + drawnIndependently: false, + allowedOutsideImage: false, + highlight: true, + active: false + }, + perpendicularStart: { + x: value.tool.x3, + y: value.tool.y3, + index: 2, + drawnIndependently: false, + allowedOutsideImage: false, + highlight: true, + active: false, + locked: false + }, + perpendicularEnd: { + x: value.tool.x4, + y: value.tool.y4, + index: 3, + drawnIndependently: false, + allowedOutsideImage: false, + highlight: true, + active: false + }, + textBox: { + x: value.tool.value_max, + y: value.tool.value_min, + index: null, + drawnIndependently: true, + allowedOutsideImage: true, + highlight: false, + active: false, + hasMoved: true, + movesIndependently: false, + hasBoundingBox: true, + boundingBox: { + width: 59.6484375, + height: 47, + left: 165.02487562189057, + top: 240.53482587064684 + } + } + }, + longestDiameter: value.tool.value_max.toString(), + shortestDiameter: value.tool.value_min.toString() + }; + let sliceNumber = value.tool.slice; + let enabledElement = cornerstone_default().getEnabledElements().filter(el => el.element.id == viewportId).pop(); + + // add to master viewport + addToolStateSingleSlice(enabledElement.element, "Diameter", data, sliceNumber, seriesId); +}; + +/** + * Add tool data for a single target slice + * @function addToolStateSingleSlice + * @param {HTMLElement} element - The target hmtl element. + * @param {String} toolName - The tool name. + * @param {Object | Array} data - The tool data to add (tool-specific) + * @param {Number} slice - The target slice to put the data in. + * @param {String} seriesId - The target serie id. + */ +const addToolStateSingleSlice = function (element, toolName, data, slice, seriesId) { + const enabledElement = cornerstone_default().getEnabledElement(element); + if (!enabledElement.image) { + console.warn("no image"); + return; + } + let targetImageId = (0,nrrdLoader.getImageIdFromSlice)(slice, element.id, seriesId); + if (enabledElement.toolStateManager === undefined) { + console.warn("State Manager undefined"); + return; + } + let toolState = enabledElement.toolStateManager.toolState; + if (toolState.hasOwnProperty(targetImageId) === false) { + toolState[targetImageId] = {}; + } + const imageIdToolState = toolState[targetImageId]; + + // If we don't have tool state for this type of tool, add an empty object + if (imageIdToolState.hasOwnProperty(toolName) === false) { + imageIdToolState[toolName] = { + data: [] + }; + } + const toolData = imageIdToolState[toolName]; + + // if an array is provided, override data + // if (Array.isArray(data)) { + // toolData.data = data; + // } else { + // toolData.data.push(data); + // } + + // This implementation works better + let singledata = typeof data.pop == "function" ? data.pop() : data; + // remove old data for this id (avoid doubling contours) // TODO generalize + if (toolName == "ContoursTool") { + (0,lodash.remove)(toolData.data, entry => entry && entry.id == singledata.id); + } + toolData.data.push(singledata); +}; + +/** + * Clear tool state and disable all cornerstone elements + * @function clearCornerstoneElements + */ +const clearCornerstoneElements = function () { + var enabledElements = cornerstone_default().getEnabledElements(); + var inMemElements = (0,lodash.cloneDeep)(enabledElements); // copy before modifying + (0,lodash.each)(inMemElements, el => { + (0,lodash.each)(tools_default.DEFAULT_TOOLS, function (tool) { + if (tool.cleanable) { + cornerstoneTools_default().clearToolState(el.element, tool.name); + } + }); + cornerstone_default().disable(el.element); + }); +}; + +/** + * Sync the cornerstone tools stack given a slice as data source + * @function syncToolStack + * @param {Number} srcSliceNumber - The slice to be used as data source. + * @param {String} toolName - The name of the tool to sync. + * @param {String} viewport - The target viewport id. + * @param {String} seriesId - The target serie id. + */ +const syncToolStack = function (srcSliceNumber, toolName, viewport, seriesId) { + // get the imageIds array + let seriesData = (0,commonLoader.getSeriesDataFromLarvitarManager)(seriesId); + let imageIds = seriesData[viewport].imageIds; + + // get the tool state of source imageId + let element = document.getElementById(viewport); + let enabledElement = cornerstone_default().getEnabledElement(element); + let srcImageId = (0,nrrdLoader.getImageIdFromSlice)(srcSliceNumber, viewport, seriesId); + let srcImageToolState = enabledElement.toolStateManager.toolState[srcImageId][toolName]; + (0,lodash.each)(Object.keys(imageIds), sliceNumber => { + if (sliceNumber == srcSliceNumber) { + return; + } + (0,lodash.each)(srcImageToolState, data => { + addToolStateSingleSlice(element, toolName, data, sliceNumber, seriesId); + }); + }); +}; + +/** + * Update slice index in cornerstone tools stack state + * @function updateStackToolState + * @param {String} elementId - The html div id used for rendering or its DOM HTMLElement + * @param {Number} imageIndex - The new imageIndex value. + */ +const updateStackToolState = function (elementId, imageIndex) { + let element = (0,imageUtils.isElement)(elementId) ? elementId : document.getElementById(elementId); + if (!element) { + console.error("invalid html element: " + elementId); + return; + } + let enabledElement = cornerstone_default().getEnabledElement(element); + if (!enabledElement.toolStateManager) { + return; + } + let stackState = enabledElement.toolStateManager.toolState["stack"]; + if (!stackState) { + return; + } + // READY for different segmentations data (data[segmentation_label_id]) + stackState.data[0].currentImageIdIndex = imageIndex; +}; + +/** @inner Internal module functions */ + +/** + * Check if a tool has already been added + * @function isToolMissing + * @param {String} toolName - The tool name. + * @param {Array} _viewports - The viewports to check. + */ +const isToolMissing = function (toolName, _viewports) { + let isToolMissing = false; + if (_viewports) { + (0,lodash.each)(_viewports, function (viewport) { + let element = cornerstone_default().getEnabledElement(document.getElementById(viewport)); + let added = cornerstoneTools_default().getToolForElement(element, toolName); + if (added === undefined) { + isToolMissing = true; + } + }); + } else { + let elements = cornerstone_default().getEnabledElements(); + (0,lodash.each)(elements, function (element) { + let added = cornerstoneTools_default().getToolForElement(element, toolName); + if (added === undefined) { + isToolMissing = true; + } + }); + } + return isToolMissing; +}; + +/***/ }), + +/***/ 5791: +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ parse: () => (/* binding */ parse) +/* harmony export */ }); +/* harmony import */ var pako__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9591); +/* harmony import */ var pako__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(pako__WEBPACK_IMPORTED_MODULE_0__); + + +/** + * This is the mapping from the NRRD datatype as written in the NRRD header + * to the JS typed array equivalent. + */ +const NRRD_TYPES_TO_TYPEDARRAY = { + "signed char": Int8Array, + int8: Int8Array, + int8_t: Int8Array, + uchar: Uint8Array, + "unsigned char": Uint8Array, + uint8: Uint8Array, + uint8_t: Uint8Array, + short: Int16Array, + "short int": Int16Array, + "signed short": Int16Array, + "signed short int": Int16Array, + int16: Int16Array, + int16_t: Int16Array, + ushort: Uint16Array, + "unsigned short": Uint16Array, + "unsigned short int": Uint16Array, + uint16: Uint16Array, + uint16_t: Uint16Array, + int: Int32Array, + "signed int": Int32Array, + int32: Int32Array, + int32_t: Int32Array, + uint: Uint32Array, + "unsigned int": Uint32Array, + uint32: Uint32Array, + uint32_t: Uint32Array, + // OK for Node/V8/Chrome but not Firefox + // longlong: BigInt64Array, + // "long long": BigInt64Array, + // "long long int": BigInt64Array, + // "signed long long": BigInt64Array, + // "signed long long int": BigInt64Array, + // int64: BigInt64Array, + // int64_t: BigInt64Array, + // ulonglong: BigUint64Array, + // "unsigned long long": BigUint64Array, + // "unsigned long long int": BigUint64Array, + // uint64: BigUint64Array, + // uint64_t: BigUint64Array, + float: Float32Array, + double: Float64Array +}; +const NRRD_TYPES_TO_VIEW_GET = { + "signed char": "getInt8", + int8: "getInt8", + int8_t: "getInt8", + uchar: "getUint8", + "unsigned char": "getUint8", + uint8: "getUint8", + uint8_t: "getUint8", + short: "getInt16", + "short int": "getInt16", + "signed short": "getInt16", + "signed short int": "getInt16", + int16: "getInt16", + int16_t: "getInt16", + ushort: "getUint16", + "unsigned short": "getUint16", + "unsigned short int": "getUint16", + uint16: "getUint16", + uint16_t: "getUint16", + int: "getInt32", + "signed int": "getInt32", + int32: "getInt32", + int32_t: "getInt32", + uint: "getUint32", + "unsigned int": "getUint32", + uint32: "getUint32", + uint32_t: "getUint32", + // OK for Node/V8/Chrome but not Firefox + // longlong: null, + // "long long": null, + // "long long int": null, + // "signed long long": null, + // "signed long long int": null, + // int64: null, + // int64_t: null, + // ulonglong: null, + // "unsigned long long": null, + // "unsigned long long int": null, + // uint64: null, + // uint64_t: null, + float: "getFloat32", + double: "getFloat64" +}; +const SPACE_TO_SPACEDIMENSIONS = { + "right-anterior-superior": 3, + ras: 3, + "left-anterior-superior": 3, + las: 3, + "left-posterior-superior": 3, + lps: 3, + "right-anterior-superior-time": 4, + rast: 4, + "left-anterior-superior-time": 4, + last: 4, + "left-posterior-superior-time": 4, + lpst: 4, + "scanner-xyz": 3, + "scanner-xyz-time": 4, + "3d-right-handed": 3, + "3d-left-handed": 3, + "3d-right-handed-time": 4, + "3d-left-handed-time": 4 +}; + +// in NRRD, some "kinds" have to respect a certain size. For example, the kind +// "quaternion" has to be of size 4 (xyzw). +// When the value is 'null', then no enforcement is made. +// Note: the fields have been turned to lowercase here +const KIND_TO_SIZE = { + domain: null, + space: null, + time: null, + list: null, + point: null, + vector: null, + "covariant-vector": null, + normal: null, + stub: 1, + scalar: 1, + complex: 2, + "2-vector": 2, + "3-color": 3, + "rgb-color": 3, + "hsv-color": 3, + "xyz-color": 3, + "4-color": 4, + "rgba-color": 4, + "3-vector": 3, + "3-gradient": 3, + "3-normal": 3, + "4-vector": 4, + quaternion: 4, + "2d-symmetric-matrix": 3, + "2d-masked-symmetric-matrix": 4, + "2d-matrix": 4, + "2d-masked-matrix": 4, + "3d-symmetric-matrix": 6, + "3d-masked-symmetric-matrix": 7, + "3d-matrix": 9, + "3d-masked-matrix": 10, + "???": null +}; - /** - * Returns true if two points are closer than this.configuration.spacing. - * - * @private - * @param {Object} element The element on which the roi is being drawn. - * @param {Object} p1 The first point, in pixel space. - * @param {Object} p2 The second point, in pixel space. - * @returns {boolean} True if the distance is smaller than the - * allowed canvas spacing. - */ - _isDistanceSmallerThanSpacing(element, p1, p2) { - return this._compareDistanceToSpacing(element, p1, p2, "<"); +/** + * Parse a buffer of a NRRD file. + * Throws an exception if the file is not a proper NRRD file. + * @instance + * @function parse + * @param {ArrayBuffer} nrrdBuffer - the NRRD file buffer + * @param {Object} options - the option object + * @param {boolean} options.headerOnly - Parses only the header if true, parses header and data if false (default: false) + * @return {Object} NRRD header and data such as {header: Object, data: TypedArray } + */ +const parse = function (nrrdBuffer, options) { + let magicControl = "NRRD000"; + let magicTest = String.fromCharCode.apply(null, new Uint8Array(nrrdBuffer, 0, magicControl.length)); + if (magicControl !== magicTest) { + throw new Error("This file is not a NRRD file"); } - - /** - * Returns true if two points are farther than this.configuration.spacing. - * - * @private - * @param {Object} element The element on which the roi is being drawn. - * @param {Object} p1 The first point, in pixel space. - * @param {Object} p2 The second point, in pixel space. - * @returns {boolean} True if the distance is smaller than the - * allowed canvas spacing. - */ - _isDistanceLargerThanSpacing(element, p1, p2) { - return this._compareDistanceToSpacing(element, p1, p2, ">"); + let { + header, + dataByteOffset + } = parseHeader(nrrdBuffer); + if ("headerOnly" in options && options.headerOnly) { + return { + header: header, + data: null + }; } + let data = parseData(nrrdBuffer, header, dataByteOffset); + return { + header: header, + data: data + }; +}; - /** - * Compares the distance between two points to this.configuration.spacing. - * - * @private - * @param {Object} element The element on which the roi is being drawn. - * @param {Object} p1 The first point, in pixel space. - * @param {Object} p2 The second point, in pixel space. - * @param {string} comparison The comparison to make. - * @param {number} spacing The allowed canvas spacing - * @returns {boolean} True if the distance is smaller than the - * allowed canvas spacing. - */ - _compareDistanceToSpacing(element, p1, p2, comparison = ">", spacing = this.configuration.spacing) { - if (comparison === ">") { - return external.cornerstoneMath.point.distance(p1, p2) > spacing; +/** + * @private + * Parses the header + */ +function parseHeader(nrrdBuffer) { + let byteArrayHeader = []; + let dataStartPosition = null; + let view = new DataView(nrrdBuffer); + for (let i = 0; i < nrrdBuffer.byteLength; i++) { + byteArrayHeader.push(String.fromCharCode(view.getUint8(i))); + if (i > 0 && byteArrayHeader[i - 1] === "\n" && byteArrayHeader[i] === "\n") { + dataStartPosition = i + 1; + break; } - return external.cornerstoneMath.point.distance(p1, p2) < spacing; - } - - /** - * Adds drawing loop event listeners. - * - * @private - * @param {Object} element - The viewport element to add event listeners to. - * @param {string} interactionType - The interactionType used for the loop. - * @modifies {element} - * @returns {undefined} - */ - _activateDraw(element, interactionType = "Mouse") { - this._drawing = true; - this._drawingInteractionType = interactionType; - state.isMultiPartToolActive = true; - // hideToolCursor(this.element); - - // Polygonal Mode - element.addEventListener(EVENTS.MOUSE_DOWN, this._drawingMouseDownCallback); - element.addEventListener(EVENTS.MOUSE_MOVE, this._drawingMouseMoveCallback); - element.addEventListener(EVENTS.MOUSE_DOUBLE_CLICK, this._drawingMouseDoubleClickCallback); - - // Drag/Pencil Mode - element.addEventListener(EVENTS.MOUSE_DRAG, this._drawingMouseDragCallback); - element.addEventListener(EVENTS.MOUSE_UP, this._drawingMouseUpCallback); - - // Touch - element.addEventListener(EVENTS.TOUCH_START, this._drawingMouseMoveCallback); - element.addEventListener(EVENTS.TOUCH_START, this._drawingTouchStartCallback); - element.addEventListener(EVENTS.TOUCH_DRAG, this._drawingTouchDragCallback); - element.addEventListener(EVENTS.TOUCH_END, this._drawingMouseUpCallback); - element.addEventListener(EVENTS.DOUBLE_TAP, this._drawingDoubleTapClickCallback); - external.cornerstone.updateImage(element); - } - - /** - * Removes drawing loop event listeners. - * - * @private - * @param {Object} element - The viewport element to add event listeners to. - * @modifies {element} - * @returns {undefined} - */ - _deactivateDraw(element) { - this._drawing = false; - state.isMultiPartToolActive = false; - this._activeDrawingToolReference = null; - this._drawingInteractionType = null; - // setToolCursor(this.element, this.svgCursor); - - element.removeEventListener(EVENTS.MOUSE_DOWN, this._drawingMouseDownCallback); - element.removeEventListener(EVENTS.MOUSE_MOVE, this._drawingMouseMoveCallback); - element.removeEventListener(EVENTS.MOUSE_DOUBLE_CLICK, this._drawingMouseDoubleClickCallback); - element.removeEventListener(EVENTS.MOUSE_DRAG, this._drawingMouseDragCallback); - element.removeEventListener(EVENTS.MOUSE_UP, this._drawingMouseUpCallback); - - // Touch - element.removeEventListener(EVENTS.TOUCH_START, this._drawingTouchStartCallback); - element.removeEventListener(EVENTS.TOUCH_DRAG, this._drawingTouchDragCallback); - element.removeEventListener(EVENTS.TOUCH_START, this._drawingMouseMoveCallback); - element.removeEventListener(EVENTS.TOUCH_END, this._drawingMouseUpCallback); - external.cornerstone.updateImage(element); } - - /** - * Adds modify loop event listeners. - * - * @private - * @param {Object} element - The viewport element to add event listeners to. - * @modifies {element} - * @returns {undefined} - */ - _activateModify(element) { - state.isToolLocked = true; - element.addEventListener(EVENTS.MOUSE_UP, this._editMouseUpCallback); - element.addEventListener(EVENTS.MOUSE_DRAG, this._editMouseDragCallback); - element.addEventListener(EVENTS.MOUSE_CLICK, this._editMouseUpCallback); - element.addEventListener(EVENTS.TOUCH_END, this._editMouseUpCallback); - element.addEventListener(EVENTS.TOUCH_DRAG, this._editTouchDragCallback); - external.cornerstone.updateImage(element); + if (dataStartPosition === null) { + throw new Error("The NRRD header is corrupted."); } + let comments = []; + let headerLines = byteArrayHeader.join("").trim().split(/\r\n|\n/).map(l => l.trim()); + let preMap = headerLines.slice(1).filter(s => { + // removing empty lines + return s.length > 0; + }).filter(s => { + // removing comments + if (s[0] === "#") { + comments.push(s.slice(1).trim()); + } + return s[0] !== "#"; + }).map(s => { + let keyVal = s.split(":"); + return { + key: keyVal[0].trim(), + val: keyVal[1].trim() + }; + }); + let nrrdHeader = {}; + preMap.forEach(field => { + nrrdHeader[field.key] = field.val; + }); - /** - * Removes modify loop event listeners. - * - * @private - * @param {Object} element - The viewport element to add event listeners to. - * @modifies {element} - * @returns {undefined} - */ - _deactivateModify(element) { - state.isToolLocked = false; - element.removeEventListener(EVENTS.MOUSE_UP, this._editMouseUpCallback); - element.removeEventListener(EVENTS.MOUSE_DRAG, this._editMouseDragCallback); - element.removeEventListener(EVENTS.MOUSE_CLICK, this._editMouseUpCallback); - element.removeEventListener(EVENTS.TOUCH_END, this._editMouseUpCallback); - element.removeEventListener(EVENTS.TOUCH_DRAG, this._editTouchDragCallback); - external.cornerstone.updateImage(element); + // parsing each fields of the header + if (nrrdHeader["sizes"]) { + nrrdHeader["sizes"] = nrrdHeader.sizes.split(/\s+/).map(n => parseInt(n)); } - passiveCallback(element) { - this._closeToolIfDrawing(element); + if (nrrdHeader["space dimension"]) { + nrrdHeader["space dimension"] = parseInt(nrrdHeader["space dimension"]); } - enabledCallback(element) { - this._closeToolIfDrawing(element); + if (nrrdHeader["space"]) { + nrrdHeader["space dimension"] = SPACE_TO_SPACEDIMENSIONS[nrrdHeader["space"].toLowerCase()]; } - disabledCallback(element) { - this._closeToolIfDrawing(element); + if (nrrdHeader["dimension"]) { + nrrdHeader["dimension"] = parseInt(nrrdHeader["dimension"]); } - _closeToolIfDrawing(element) { - if (this._drawing) { - // Actively drawing but changed mode. - const config = this.configuration; - const lastHandlePlaced = config.currentHandle; - this._endDrawing(element, lastHandlePlaced); - external.cornerstone.updateImage(element); + if (nrrdHeader["space directions"]) { + nrrdHeader["space directions"] = nrrdHeader["space directions"].split(/\s+/).map(triple => { + if (triple.trim() === "none") { + return null; + } + return triple.slice(1, triple.length - 1).split(",").map(n => parseFloat(n)); + }); + if (nrrdHeader["space directions"].length !== nrrdHeader["dimension"]) { + throw new Error('"space direction" property has to contain as many elements as dimensions. Non-spatial dimesnsions must be refered as "none". See http://teem.sourceforge.net/nrrd/format.html#spacedirections for more info.'); } } - - /** - * Fire MEASUREMENT_MODIFIED event on provided element - * @param {any} element which freehand data has been modified - * @param {any} measurementData the measurment data - * @returns {void} - */ - fireModifiedEvent(element, measurementData) { - const eventType = EVENTS.MEASUREMENT_MODIFIED; - const eventData = { - toolName: this.name, - element, - measurementData - }; - triggerEvent(element, eventType, eventData); + if (nrrdHeader["space units"]) { + nrrdHeader["space units"] = nrrdHeader["space units"].split(/\s+/); } - fireCompletedEvent(element, measurementData) { - const eventType = EVENTS.MEASUREMENT_COMPLETED; - const eventData = { - toolName: this.name, - element, - measurementData - }; - triggerEvent(element, eventType, eventData); + if (nrrdHeader["space origin"]) { + nrrdHeader["space origin"] = nrrdHeader["space origin"].slice(1, nrrdHeader["space origin"].length - 1).split(",").map(n => parseFloat(n)); } - - // =================================================================== - // Public Configuration API. . - // =================================================================== - - get spacing() { - return this.configuration.spacing; + if (nrrdHeader["measurement frame"]) { + nrrdHeader["measurement frame"] = nrrdHeader["measurement frame"].split(/\s+/).map(triple => { + if (triple.trim() === "none") { + return null; + } + return triple.slice(1, triple.length - 1).split(",").map(n => parseFloat(n)); + }); } - set spacing(value) { - if (typeof value !== "number") { - throw new Error("Attempting to set freehand spacing to a value other than a number."); + if (nrrdHeader["kinds"]) { + nrrdHeader["kinds"] = nrrdHeader["kinds"].split(/\s+/); + if (nrrdHeader["kinds"].length !== nrrdHeader["sizes"].length) { + throw new Error(`The "kinds" property is expected to have has many elements as the "size" property.`); } - this.configuration.spacing = value; - external.cornerstone.updateImage(this.element); + nrrdHeader["kinds"].forEach((k, i) => { + let expectedLength = KIND_TO_SIZE[k.toLowerCase()]; + let foundLength = nrrdHeader["sizes"][i]; + if (expectedLength !== null && expectedLength !== foundLength) { + throw new Error(`The kind "${k}" expect a size of ${expectedLength} but ${foundLength} found`); + } + }); } - get activeHandleRadius() { - return this.configuration.activeHandleRadius; + if (nrrdHeader["min"]) { + nrrdHeader["min"] = parseFloat(nrrdHeader["min"]); } - set activeHandleRadius(value) { - if (typeof value !== "number") { - throw new Error("Attempting to set freehand activeHandleRadius to a value other than a number."); - } - this.configuration.activeHandleRadius = value; - external.cornerstone.updateImage(this.element); + if (nrrdHeader["max"]) { + nrrdHeader["max"] = parseFloat(nrrdHeader["max"]); } - get completeHandleRadius() { - return this.configuration.completeHandleRadius; + if (nrrdHeader["old min"]) { + nrrdHeader["old min"] = parseFloat(nrrdHeader["old min"]); } - set completeHandleRadius(value) { - if (typeof value !== "number") { - throw new Error("Attempting to set freehand completeHandleRadius to a value other than a number."); - } - this.configuration.completeHandleRadius = value; - external.cornerstone.updateImage(this.element); + if (nrrdHeader["old max"]) { + nrrdHeader["old max"] = parseFloat(nrrdHeader["old max"]); } - get alwaysShowHandles() { - return this.configuration.alwaysShowHandles; + if (nrrdHeader["spacings"]) { + nrrdHeader["spacings"] = nrrdHeader["spacings"].split(/\s+/).map(n => parseFloat(n)); } - set alwaysShowHandles(value) { - if (typeof value !== "boolean") { - throw new Error("Attempting to set freehand alwaysShowHandles to a value other than a boolean."); - } - this.configuration.alwaysShowHandles = value; - external.cornerstone.updateImage(this.element); + if (nrrdHeader["thicknesses"]) { + nrrdHeader["thicknesses"] = nrrdHeader["thicknesses"].split(/\s+/).map(n => parseFloat(n)); } - get invalidColor() { - return this.configuration.invalidColor; + if (nrrdHeader["axis mins"]) { + nrrdHeader["axis mins"] = nrrdHeader["axis mins"].split(/\s+/).map(n => parseFloat(n)); } - set invalidColor(value) { - /* - It'd be easy to check if the color was e.g. a valid rgba color. However - it'd be difficult to check if the color was a named CSS color without - bloating the library, so we don't. If the canvas can't intepret the color - it'll show up grey. - */ - - this.configuration.invalidColor = value; - external.cornerstone.updateImage(this.element); + if (nrrdHeader["axismins"]) { + nrrdHeader["axismins"] = nrrdHeader["axismins"].split(/\s+/).map(n => parseFloat(n)); } - - /** - * Ends the active drawing loop and removes the polygon. - * - * @public - * @param {Object} element - The element on which the roi is being drawn. - * @returns {null} - */ - cancelDrawing(element) { - if (!this._drawing) { - return; - } - const toolState = getToolState(element, this.name); - const config = this.configuration; - const data = toolState.data[config.currentTool]; - data.active = false; - data.highlight = false; - data.handles.invalidHandlePlacement = false; - - // Reset the current handle - config.currentHandle = 0; - config.currentTool = -1; - data.canComplete = false; - removeToolState(element, this.name, data); - this._deactivateDraw(element); - external.cornerstone.updateImage(element); + if (nrrdHeader["axis maxs"]) { + nrrdHeader["axis maxs"] = nrrdHeader["axis maxs"].split(/\s+/).map(n => parseFloat(n)); + } + if (nrrdHeader["axismaxs"]) { + nrrdHeader["axismaxs"] = nrrdHeader["axismaxs"].split(/\s+/).map(n => parseFloat(n)); + } + if (nrrdHeader["centers"]) { + nrrdHeader["centers"] = nrrdHeader["centers"].split(/\s+/).map(mode => { + if (mode === "cell" || mode === "node") { + return mode; + } else { + return null; + } + }); + } + if (nrrdHeader["labels"]) { + nrrdHeader["labels"] = nrrdHeader["labels"].split(/\s+/); } - /** - * New image event handler. - * - * @public - * @param {Object} evt The event. - * @returns {null} - */ - newImageCallback(evt) { - const config = this.configuration; - if (!(this._drawing && this._activeDrawingToolReference)) { - return; - } - - // Actively drawing but scrolled to different image. - - const element = evt.detail.element; - const data = this._activeDrawingToolReference; - data.active = false; - data.highlight = false; - data.handles.invalidHandlePlacement = false; + // some additional metadata that are not part of the header will be added here + nrrdHeader.extra = {}; - // Connect the end handle to the origin handle - const points = data.handles.points; - points[config.currentHandle - 1].lines.push(points[0]); + // adding the comments from lines starting with # + nrrdHeader.extra.comments = comments; - // Reset the current handle - config.currentHandle = 0; - config.currentTool = -1; - data.canComplete = false; - this._deactivateDraw(element); - external.cornerstone.updateImage(element); + // having the stride can be handy. + nrrdHeader.extra.stride = [1]; + for (let i = 1; i < nrrdHeader.sizes.length; i++) { + nrrdHeader.extra.stride.push(nrrdHeader.extra.stride[i - 1] * nrrdHeader.sizes[i - 1]); } -} -function defaultFreehandConfiguration() { return { - mouseLocation: { - handles: { - start: { - highlight: false, - active: false - } - } - }, - spacing: 1, - activeHandleRadius: 3, - completeHandleRadius: 6, - completeHandleRadiusTouch: 28, - alwaysShowHandles: false, - invalidColor: "#FFFF00", - currentHandle: 0, - currentTool: -1 + header: nrrdHeader, + dataByteOffset: dataStartPosition }; } -function preventPropagation(evt) { - evt.stopImmediatePropagation(); - evt.stopPropagation(); - evt.preventDefault(); -} - -/***/ }), - -/***/ "./imaging/tools/custom/diameterTool.js": -/*!**********************************************!*\ - !*** ./imaging/tools/custom/diameterTool.js ***! - \**********************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ DiameterTool: () => (/* binding */ DiameterTool) -/* harmony export */ }); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! cornerstone-tools */ "./node_modules/cornerstone-tools/dist/cornerstoneTools.js"); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash */ "./node_modules/lodash/lodash.js"); -/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(lodash__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var _imageTools__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../imageTools */ "./imaging/imageTools.js"); -/** @module imaging/tools/custom/diameterTool - * @desc This file provides functionalities for - * a custom diameter cornestone tool - */ - -// external libraries - -const BidirectionalTool = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().BidirectionalTool); - - -// internal libraries - /** - * @public - * @class DiameterTool - * @memberof Tools.Annotation - * @classdesc Create and position an annotation that measures the - * length and width of a region. - * @extends Tools.Base.BaseAnnotationTool + * @private + * Parses the data */ - -class DiameterTool extends BidirectionalTool { - constructor(props) { - const defaultProps = { - name: "Diameter", - isBeenModified: false, - lastData: null - }; - super(props, defaultProps); - this.name = "Diameter"; - this.initializeTool(props.dataArray, "cmprAxial", props.seriesId); - } - initializeTool(dataArray, elementId, seriesId) { - let element = document.getElementById(elementId); - (0,lodash__WEBPACK_IMPORTED_MODULE_1__.each)(dataArray, singleData => { - let data = { - toolType: "Diameter", - name: singleData.id.toString(), - isCreating: true, - visible: true, - active: false, - invalidated: false, - handles: { - start: { - x: singleData.x1, - y: singleData.y1, - index: 0, - drawnIndependently: false, - allowedOutsideImage: false, - highlight: true, - active: false - }, - end: { - x: singleData.x2, - y: singleData.y2, - index: 1, - drawnIndependently: false, - allowedOutsideImage: false, - highlight: true, - active: false - }, - perpendicularStart: { - x: singleData.x3, - y: singleData.y3, - index: 2, - drawnIndependently: false, - allowedOutsideImage: false, - highlight: true, - active: false, - locked: false - }, - perpendicularEnd: { - x: singleData.x4, - y: singleData.y4, - index: 3, - drawnIndependently: false, - allowedOutsideImage: false, - highlight: true, - active: false - }, - textBox: { - x: singleData.value_max, - y: singleData.value_min, - index: null, - drawnIndependently: true, - allowedOutsideImage: true, - highlight: false, - active: false, - hasMoved: true, - movesIndependently: false, - hasBoundingBox: true, - boundingBox: { - width: 59.6484375, - height: 47, - left: 165.02487562189057, - top: 240.53482587064684 - } - } - }, - longestDiameter: singleData.value_max.toString(), - shortestDiameter: singleData.value_min.toString() - }; - let sliceNumber = singleData.slice; - - // add to master viewport - (0,_imageTools__WEBPACK_IMPORTED_MODULE_2__.addToolStateSingleSlice)(element, this.name, data, sliceNumber, seriesId); +function parseData(nrrdBuffer, header, dataByteOffset) { + let dataBuffer = null; + let arrayType = NRRD_TYPES_TO_TYPEDARRAY[header.type]; + let nbElementsFromHeader = header.sizes.reduce((prev, curr) => prev * curr); + let min = +Infinity; + let max = -Infinity; + let data = null; + let isTextEncoded = header.encoding === "ascii" || header.encoding === "txt" || header.encoding === "text"; + if (header.encoding === "raw") { + dataBuffer = new Uint8Array(nrrdBuffer).slice(dataByteOffset).buffer; + } else if (isTextEncoded) { + let numbers = String.fromCharCode.apply(null, new Uint8Array(nrrdBuffer, dataByteOffset)).split(/\r\n|\n|\s/).map(s => s.trim()).filter(s => s !== "").map(s => { + let numValue = parseFloat(s); + min = Math.min(min, numValue); + max = Math.max(max, numValue); + return numValue; }); - cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().external.cornerstone.updateImage(element); - } - passiveCallback(element) { - element.addEventListener("cornerstonetoolsmeasurementmodified", this.measureOnGoingCallback); + data = new arrayType(numbers); + } else if (header.encoding === "gzip" || header.encoding === "gz") { + dataBuffer = pako__WEBPACK_IMPORTED_MODULE_0___default().inflate(new Uint8Array(nrrdBuffer).slice(dataByteOffset)).buffer; + } else { + throw new Error('Only "raw", "ascii" and "gzip" encoding are supported.'); } - measureOnGoingCallback(event) { - if (!this.isBeenModified) { - event.target.addEventListener("mouseup", function (evt) { - this.__proto__.measureEndCallback(evt); - }); + if (isTextEncoded) { + if (nbElementsFromHeader !== data.length) { + throw new Error("Unconsistency in data buffer length"); + } + } else { + let nbElementsFromBufferAndType = dataBuffer.byteLength / arrayType.BYTES_PER_ELEMENT; + if (nbElementsFromHeader !== nbElementsFromBufferAndType) { + throw new Error("Unconsistency in data buffer length"); + } + data = new arrayType(nbElementsFromHeader); + let dataView = new DataView(dataBuffer); + let viewMethod = NRRD_TYPES_TO_VIEW_GET[header.type]; + let littleEndian = header.endian === "little" ? true : false; + for (let i = 0; i < nbElementsFromHeader; i++) { + data[i] = dataView[viewMethod](i * arrayType.BYTES_PER_ELEMENT, littleEndian); + min = Math.min(min, data[i]); + max = Math.max(max, data[i]); } - this.isBeenModified = true; - this.lastData = event.detail.measurementData; - } - measureEndCallback(event) { - event.element.removeEventListener("mouseup", this.measureEndCallback); - this.isBeenModified = false; - this.lastData = null; } + header.extra.min = min; + header.extra.max = max; + return data; } /***/ }), -/***/ "./imaging/tools/custom/editMaskTool.js": -/*!**********************************************!*\ - !*** ./imaging/tools/custom/editMaskTool.js ***! - \**********************************************/ +/***/ 7819: /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ EditMaskTool: () => (/* binding */ EditMaskTool) +/* harmony export */ "default": () => (/* binding */ BorderMagnifyTool) /* harmony export */ }); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! cornerstone-tools */ "./node_modules/cornerstone-tools/dist/cornerstoneTools.js"); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__); -/** @module imaging/tools/custom/editMaskTool - * @desc This file provides functionalities for - * a custom mask cornestone tool - */ - -// external libraries +/* harmony import */ var cornerstone_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7371); +/* harmony import */ var cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cornerstone_core__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4030); +/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(cornerstone_tools__WEBPACK_IMPORTED_MODULE_1__); -const external = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().external); -const BaseBrushTool = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().importInternal("base/BaseBrushTool"); -const segmentationUtils = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().importInternal("util/segmentationUtils"); -const getCircle = segmentationUtils.getCircle; -const drawBrushPixels = segmentationUtils.drawBrushPixels; -const getModule = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().getModule); -const { - configuration, - setters -} = getModule("segmentation"); -/** - * @public - * @class BrushTool - * @memberof Tools.Brush - * @classdesc Tool for drawing segmentations on an image. - * @extends Tools.Base.BaseBrushTool - */ -class EditMaskTool extends BaseBrushTool { +const MagnifyTool = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().MagnifyTool); +const drawTextBox = cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().importInternal("drawing/drawTextBox"); +const textStyle = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().textStyle); +const toolColors = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_1___default().toolColors); +class BorderMagnifyTool extends MagnifyTool { constructor(props = {}) { - const defaultProps = { - name: "EditMask", - supportedInteractionTypes: ["Mouse"], - configuration: {}, - mixins: ["renderBrushMixin"] - }; - super(props, defaultProps); - this.touchDragCallback = this._paint.bind(this); - this._initializeTool(props.mask, props.initCallback); - } - _initializeTool(mask, callback) { - let enabledElement = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().external.cornerstone.getEnabledElements().filter(e => e.element.id == "axial").pop(); + super(props); - // TODO improve performances! + // Additional configuration options + this.configuration.showBorders = props.showBorders || true; // Default to true + this.configuration.showInfo = props.showInfo || true; // Default to true + this.configuration.borderColor = props.borderColor; // Default border color is green - console.time("...typedToNormal"); - let pixelsNormalArray = Array.prototype.slice.call(mask.data); - console.timeEnd("...typedToNormal"); - console.time("...normalToTyped"); - let pixelData = Uint16Array.from(pixelsNormalArray); - console.timeEnd("...normalToTyped"); - let labelmapIndex = 1; - let segmentsOnLabelmapArray = new Array(mask.sizes[2]).fill([labelmapIndex]); - setters.labelmap3DForElement(enabledElement.element, pixelData.buffer, labelmapIndex, [], segmentsOnLabelmapArray, 0); - cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().external.cornerstone.updateImage(enabledElement.element); - if (callback) { - callback(); - } - // SIMPLE WAY --- AS FALLBACK : + // Add global keydown event listener + window.addEventListener('keydown', this.handleKeyDown.bind(this)); + } - // labelmap3D.labelmaps2D = []; + /** + * Event handler for the "keydown" event to toggle the visibility of borders and info on "B" key press. + * @param {KeyboardEvent} event + * @returns {void} + */ + handleKeyDown(event) { + if (event.key === 'M' || event.key === 'm') { + // Toggle the visibility of borders + this.configuration.showBorders = !this.configuration.showBorders; - // for (let f = 0; f < 110; f++) { - // let pixels = mask.data.slice(f * 512 * 512, (f + 1) * 512 * 512); - // let pixelsNormalArray = Array.prototype.slice.call(pixels); - // // let pixelData = new Uint16Array(); - // let pixelData = Uint16Array.from(pixelsNormalArray); - // labelmap3D.labelmaps2D.push({ - // pixelData: pixelData, - // segmentsOnLabelmap: [labelmapIndex] - // }); - // } - } - activeCallback(element, options) { - switch (options.force) { - case "delete": - this.configuration.alwaysEraseOnClick = true; - break; - case "add": - this.preventCtrl(); - break; - default: - this.configuration.alwaysEraseOnClick = false; + // Toggle the visibility of zoom/ROI info + this.configuration.showInfo = !this.configuration.showInfo; + + // Redraw the magnification tool + this._drawMagnificationTool(); } } - preventCtrl() { - this.__proto__.__proto__._isCtrlDown = function () { - return false; - }; - } /** - * Paints the data to the labelmap. - * - * @protected - * @param {Object} evt The data object associated with the event. + * Overrides the _drawMagnificationTool method to add configurable borders and display zoom/ROI dimensions. + * @param {*} evt * @returns {void} */ - _paint(evt) { - const eventData = evt.detail; - const { - rows, - columns - } = eventData.image; - const { - x, - y - } = eventData.currentPoints.image; - if (x < 0 || x > columns || y < 0 || y > rows) { - return; - } - const radius = configuration.radius; - const pointerArray = getCircle(radius, rows, columns, x, y); - const { - labelmap2D, - labelmap3D, - shouldErase - } = this.paintEventData; + _drawMagnificationTool(evt) { + // Call the parent method to draw the magnification tool + super._drawMagnificationTool(evt); + // Query for the magnify canvas using the correct class name + const magnifyCanvas = evt ? evt.detail.element.querySelector('.magnifyTool') : null; + if (magnifyCanvas) { + const context = magnifyCanvas.getContext('2d'); - // Draw / Erase the active color. - drawBrushPixels(pointerArray, labelmap2D.pixelData, labelmap3D.activeSegmentIndex, columns, shouldErase); - external.cornerstone.updateImage(evt.detail.element); + // Check if the user wants to show borders + if (this.configuration.showBorders) { + // Add configurable borders + context.strokeStyle = this.configuration.borderColor || toolColors.getColorIfActive({ + active: true + }); + context.lineWidth = 4; + context.strokeRect(0, 0, magnifyCanvas.width, magnifyCanvas.height); + } + + // Check if the user wants to show info + if (this.configuration.showInfo) { + // Get the zoom level + const viewport = cornerstone_core__WEBPACK_IMPORTED_MODULE_0___default().getViewport(evt.detail.element); + const zoomLevel = viewport.scale.toFixed(2); // Adjust the precision as needed + + // Get ROI dimensions + const roiWidth = magnifyCanvas.width; + const roiHeight = magnifyCanvas.height; + const roiArea = (roiWidth * roiHeight).toFixed(2); // Area of the ROI + + // Display the zoom level and ROI dimensions + //context.fillStyle = this.configuration.borderColor; // Adjust text color as needed + //context.font = 'bold 14px Arial'; + //context.fillText(`Zoom: x${zoomLevel}`, 10, 20); + //context.fillText(`ROI Area: ${roiWidth} x ${roiHeight} square pixels`, 10, 40); + const text = `Zoom: x${zoomLevel}`; + const str = `ROI: ${roiWidth}px x ${roiHeight}px`; + const fontHeight = textStyle.getFontSize(); + const color = this.configuration.borderColor || toolColors.getColorIfActive({ + active: true + }); + // Draw text 5px away from cursor + const textCoords = { + x: 5, + y: 2 + }; + drawTextBox(context, str, textCoords.x, textCoords.y + fontHeight + 5, color); + drawTextBox(context, text, textCoords.x, textCoords.y, color); + } + } } } /***/ }), -/***/ "./imaging/tools/custom/ellipticalRoiOverlayTool.js": -/*!**********************************************************!*\ - !*** ./imaging/tools/custom/ellipticalRoiOverlayTool.js ***! - \**********************************************************/ +/***/ 3211: /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; @@ -3108,7 +3048,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (/* binding */ EllipticalRoiTool) /* harmony export */ }); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! cornerstone-tools */ "./node_modules/cornerstone-tools/dist/cornerstoneTools.js"); +/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4030); /* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__); const external = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().external); @@ -3502,19 +3442,155 @@ function _getEllipseImageCoordinates(startHandle, endHandle) { /***/ }), -/***/ "./imaging/tools/custom/polygonSegmentationMixin.js": -/*!**********************************************************!*\ - !*** ./imaging/tools/custom/polygonSegmentationMixin.js ***! - \**********************************************************/ +/***/ 8990: /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; +// ESM COMPAT FLAG __webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) -/* harmony export */ }); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! cornerstone-tools */ "./node_modules/cornerstone-tools/dist/cornerstoneTools.js"); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__); + +// EXPORTS +__webpack_require__.d(__webpack_exports__, { + "default": () => (/* binding */ PolylineScissorsTool) +}); + +// EXTERNAL MODULE: ./node_modules/cornerstone-tools/dist/cornerstoneTools.js +var cornerstoneTools = __webpack_require__(4030); +var cornerstoneTools_default = /*#__PURE__*/__webpack_require__.n(cornerstoneTools); +;// CONCATENATED MODULE: ./imaging/tools/strategies/eraseFreehand.js +/** @module imaging/strategies/eraseFreehand + * @desc This file provides functionalities for + * erasing pixels + */ + +// external libraries + +const { + getBoundingBoxAroundPolygon, + eraseInsideShape, + eraseOutsideShape +} = cornerstoneTools_default().importInternal("util/segmentationUtils"); +const isPointInPolygon = cornerstoneTools_default().importInternal("util/isPointInPolygon"); +const getLogger = cornerstoneTools_default().importInternal("util/getLogger"); +const logger = getLogger("util:segmentation:operations:eraseInsideFreehand"); + +/** + * Erase all pixels labeled with the activeSegmentIndex, + * in the region defined by evt.operationData.points. + * @param {} evt The Cornerstone event. + * @param {} operationData An object containing the `pixelData` to + * modify, the `segmentIndex` and the `points` array. + * @returns {null} + */ +function eraseFreehand(evt, operationData, inside = true) { + const { + points, + segmentationMixinType + } = operationData; + if (segmentationMixinType !== `freehandSegmentationMixin`) { + logger.error(`eraseInsideFreehand operation requires freehandSegmentationMixin operationData, recieved ${segmentationMixinType}`); + return; + } + const { + image + } = evt.detail; + const vertices = points.map(a => [a.x, a.y]); + const [topLeft, bottomRight] = getBoundingBoxAroundPolygon(vertices, image); + inside ? eraseInsideShape(evt, operationData, point => isPointInPolygon([point.x, point.y], vertices), topLeft, bottomRight) : eraseOutsideShape(evt, operationData, point => isPointInPolygon([point.x, point.y], vertices), topLeft, bottomRight); +} + +/** + * Erase all pixels inside/outside the region defined by `operationData.points`. + * @param {} evt The Cornerstone event. + * @param {} operationData An object containing the `pixelData` to + * modify, the `segmentIndex` and the `points` array. + * @returns {null} + */ +function eraseInsideFreehand(evt, operationData) { + eraseFreehand(evt, operationData, true); +} + +/** + * Erase all pixels outside the region defined by `operationData.points`. + * @param {} evt The Cornerstone event. + * @param {} operationData An object containing the `pixelData` to + * modify, the `segmentIndex` and the `points` array. + * @returns {null} + */ +function eraseOutsideFreehand(evt, operationData) { + eraseFreehand(evt, operationData, false); +} +;// CONCATENATED MODULE: ./imaging/tools/strategies/fillFreehand.js +/** @module imaging/strategies/fillFreehand + * @desc This file provides functionalities for + * filling pixels + */ + +// external libraries + +const { + getBoundingBoxAroundPolygon: fillFreehand_getBoundingBoxAroundPolygon, + fillInsideShape, + fillOutsideShape +} = cornerstoneTools_default().importInternal("util/segmentationUtils"); +const fillFreehand_isPointInPolygon = cornerstoneTools_default().importInternal("util/isPointInPolygon"); +const fillFreehand_getLogger = cornerstoneTools_default().importInternal("util/getLogger"); +const fillFreehand_logger = fillFreehand_getLogger("util:segmentation:operations:fillInsideFreehand"); + +/** + * Fill all pixels inside/outside the region defined by + * `operationData.points` with the `activeSegmentIndex` value. + * @param {} evt The Cornerstone event. + * @param {} operationData An object containing the `pixelData` to + * modify, the `segmentIndex` and the `points` array. + * @returns {null} + */ +function fillFreehand(evt, operationData, inside = true) { + const { + points, + segmentationMixinType + } = operationData; + if (segmentationMixinType !== `freehandSegmentationMixin`) { + fillFreehand_logger.error(`eraseInsideFreehand operation requires freehandSegmentationMixin operationData, recieved ${segmentationMixinType}`); + return; + } + + // Obtain the bounding box of the entire drawing so that + // we can subset our search. Outside of the bounding box, + // everything is outside of the polygon. + const { + image + } = evt.detail; + const vertices = points.map(a => [a.x, a.y]); + const [topLeft, bottomRight] = fillFreehand_getBoundingBoxAroundPolygon(vertices, image); + inside ? fillInsideShape(evt, operationData, point => fillFreehand_isPointInPolygon([point.x, point.y], vertices), topLeft, bottomRight) : fillOutsideShape(evt, operationData, point => fillFreehand_isPointInPolygon([point.x, point.y], vertices), topLeft, bottomRight); +} + +/** + * Fill all pixels inside/outside the region defined by `operationData.points`. + * @param {} evt The Cornerstone event. + * @param {} operationData An object containing the `pixelData` to + * modify, the `segmentIndex` and the `points` array. + * @returns {null} + */ +function fillInsideFreehand(evt, operationData) { + fillFreehand(evt, operationData, true); +} + +/** + * Fill all pixels outside the region defined by `operationData.points`. + * @param {} evt The Cornerstone event. + * @param {} operationData An object containing the `pixelData` to + * modify, the `segmentIndex` and the `points` array. + * @returns {null} + */ +function fillOutsideFreehand(evt, operationData) { + fillFreehand(evt, operationData, false); +} +;// CONCATENATED MODULE: ./imaging/tools/strategies/index.js + + +;// CONCATENATED MODULE: ./imaging/tools/custom/polygonSegmentationMixin.js /** @module imaging/tools/polygonSegmentationMixin * @desc This file ovverides `freehandSegmentationMixin`'s * `renderToolData` method @@ -3522,17 +3598,17 @@ __webpack_require__.r(__webpack_exports__); // external libraries -const external = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().external); -const draw = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().importInternal("drawing/draw"); -const drawJoinedLines = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().importInternal("drawing/drawJoinedLines"); -const getNewContext = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().importInternal("drawing/getNewContext"); +const external = (cornerstoneTools_default()).external; +const draw = cornerstoneTools_default().importInternal("drawing/draw"); +const drawJoinedLines = cornerstoneTools_default().importInternal("drawing/drawJoinedLines"); +const getNewContext = cornerstoneTools_default().importInternal("drawing/getNewContext"); const { getDiffBetweenPixelData -} = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().importInternal("util/segmentationUtils"); +} = cornerstoneTools_default().importInternal("util/segmentationUtils"); const { getters, setters -} = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().getModule("segmentation"); +} = cornerstoneTools_default().getModule("segmentation"); /** * Global var, identify when first point has already been placed @@ -3739,7 +3815,7 @@ function _addPoint(evt) { * @mixin polygonSegmentationMixin - segmentation operations for polyline * @memberof Mixins */ -/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({ +/* harmony default export */ const polygonSegmentationMixin = ({ mouseClickCallback: _checkIfDrawing, initializeMixin: _resetHandles, renderToolData, @@ -3747,24 +3823,7 @@ function _addPoint(evt) { _addPoint, _applyStrategy }); - -/***/ }), - -/***/ "./imaging/tools/custom/polylineScissorsTool.js": -/*!******************************************************!*\ - !*** ./imaging/tools/custom/polylineScissorsTool.js ***! - \******************************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "default": () => (/* binding */ PolylineScissorsTool) -/* harmony export */ }); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! cornerstone-tools */ "./node_modules/cornerstone-tools/dist/cornerstoneTools.js"); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var _strategies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../strategies */ "./imaging/tools/strategies/index.js"); -/* harmony import */ var _polygonSegmentationMixin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./polygonSegmentationMixin */ "./imaging/tools/custom/polygonSegmentationMixin.js"); +;// CONCATENATED MODULE: ./imaging/tools/custom/polylineScissorsTool.js /** @module imaging/tools/custom/polygonScissorsTool * @desc This file provides functionalities for * a custom polyline scissors cornestone tool @@ -3772,17 +3831,17 @@ __webpack_require__.r(__webpack_exports__); // external libraries -const BaseTool = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().importInternal("base/BaseTool"); +const BaseTool = cornerstoneTools_default().importInternal("base/BaseTool"); const { rectangleRoiCursor -} = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().importInternal("tools/cursors"); +} = cornerstoneTools_default().importInternal("tools/cursors"); // internal libraries // cannot import strategies in other way 🤷 // Register custom mixin -cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().register("mixin", "polygonSegmentationMixin", _polygonSegmentationMixin__WEBPACK_IMPORTED_MODULE_2__["default"]); +cornerstoneTools_default().register("mixin", "polygonSegmentationMixin", polygonSegmentationMixin); /** * @public @@ -3797,10 +3856,10 @@ class PolylineScissorsTool extends BaseTool { const defaultProps = { name: "PolylineScissors", strategies: { - FILL_INSIDE: _strategies__WEBPACK_IMPORTED_MODULE_1__.fillInsideFreehand, - FILL_OUTSIDE: _strategies__WEBPACK_IMPORTED_MODULE_1__.fillOutsideFreehand, - ERASE_OUTSIDE: _strategies__WEBPACK_IMPORTED_MODULE_1__.eraseOutsideFreehand, - ERASE_INSIDE: _strategies__WEBPACK_IMPORTED_MODULE_1__.eraseInsideFreehand + FILL_INSIDE: fillInsideFreehand, + FILL_OUTSIDE: fillOutsideFreehand, + ERASE_OUTSIDE: eraseOutsideFreehand, + ERASE_INSIDE: eraseInsideFreehand }, cursors: { FILL_INSIDE: rectangleRoiCursor, @@ -3819,10 +3878,7 @@ class PolylineScissorsTool extends BaseTool { /***/ }), -/***/ "./imaging/tools/custom/rectangleRoiOverlayTool.js": -/*!*********************************************************!*\ - !*** ./imaging/tools/custom/rectangleRoiOverlayTool.js ***! - \*********************************************************/ +/***/ 1828: /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; @@ -3830,7 +3886,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (/* binding */ RectangleRoiOverlayTool) /* harmony export */ }); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! cornerstone-tools */ "./node_modules/cornerstone-tools/dist/cornerstoneTools.js"); +/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4030); /* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__); const external = (cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().external); @@ -4256,10 +4312,7 @@ function _createTextBoxContent(context, isColorImage, { /***/ }), -/***/ "./imaging/tools/custom/thresholdsBrushTool.js": -/*!*****************************************************!*\ - !*** ./imaging/tools/custom/thresholdsBrushTool.js ***! - \*****************************************************/ +/***/ 7073: /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; @@ -4267,7 +4320,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (/* binding */ ThresholdsBrushTool) /* harmony export */ }); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! cornerstone-tools */ "./node_modules/cornerstone-tools/dist/cornerstoneTools.js"); +/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4030); /* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__); /** @module imaging/tools/custom/thresholdBrushTool * @desc This file provides functionalities for @@ -4409,10 +4462,7 @@ function getCircleWithThreshold(image, radius, thresholds, xCoord = 0, yCoord = /***/ }), -/***/ "./imaging/tools/custom/utils/loadHandlerManager.js": -/*!**********************************************************!*\ - !*** ./imaging/tools/custom/utils/loadHandlerManager.js ***! - \**********************************************************/ +/***/ 9003: /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; @@ -4420,7 +4470,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) /* harmony export */ }); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! cornerstone-tools */ "./node_modules/cornerstone-tools/dist/cornerstoneTools.js"); +/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4030); /* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__); // external libraries @@ -4493,193 +4543,7 @@ const loadHandlerManager = { /***/ }), -/***/ "./imaging/tools/strategies/eraseFreehand.js": -/*!***************************************************!*\ - !*** ./imaging/tools/strategies/eraseFreehand.js ***! - \***************************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ eraseInsideFreehand: () => (/* binding */ eraseInsideFreehand), -/* harmony export */ eraseOutsideFreehand: () => (/* binding */ eraseOutsideFreehand) -/* harmony export */ }); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! cornerstone-tools */ "./node_modules/cornerstone-tools/dist/cornerstoneTools.js"); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__); -/** @module imaging/strategies/eraseFreehand - * @desc This file provides functionalities for - * erasing pixels - */ - -// external libraries - -const { - getBoundingBoxAroundPolygon, - eraseInsideShape, - eraseOutsideShape -} = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().importInternal("util/segmentationUtils"); -const isPointInPolygon = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().importInternal("util/isPointInPolygon"); -const getLogger = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().importInternal("util/getLogger"); -const logger = getLogger("util:segmentation:operations:eraseInsideFreehand"); - -/** - * Erase all pixels labeled with the activeSegmentIndex, - * in the region defined by evt.operationData.points. - * @param {} evt The Cornerstone event. - * @param {} operationData An object containing the `pixelData` to - * modify, the `segmentIndex` and the `points` array. - * @returns {null} - */ -function eraseFreehand(evt, operationData, inside = true) { - const { - points, - segmentationMixinType - } = operationData; - if (segmentationMixinType !== `freehandSegmentationMixin`) { - logger.error(`eraseInsideFreehand operation requires freehandSegmentationMixin operationData, recieved ${segmentationMixinType}`); - return; - } - const { - image - } = evt.detail; - const vertices = points.map(a => [a.x, a.y]); - const [topLeft, bottomRight] = getBoundingBoxAroundPolygon(vertices, image); - inside ? eraseInsideShape(evt, operationData, point => isPointInPolygon([point.x, point.y], vertices), topLeft, bottomRight) : eraseOutsideShape(evt, operationData, point => isPointInPolygon([point.x, point.y], vertices), topLeft, bottomRight); -} - -/** - * Erase all pixels inside/outside the region defined by `operationData.points`. - * @param {} evt The Cornerstone event. - * @param {} operationData An object containing the `pixelData` to - * modify, the `segmentIndex` and the `points` array. - * @returns {null} - */ -function eraseInsideFreehand(evt, operationData) { - eraseFreehand(evt, operationData, true); -} - -/** - * Erase all pixels outside the region defined by `operationData.points`. - * @param {} evt The Cornerstone event. - * @param {} operationData An object containing the `pixelData` to - * modify, the `segmentIndex` and the `points` array. - * @returns {null} - */ -function eraseOutsideFreehand(evt, operationData) { - eraseFreehand(evt, operationData, false); -} - -/***/ }), - -/***/ "./imaging/tools/strategies/fillFreehand.js": -/*!**************************************************!*\ - !*** ./imaging/tools/strategies/fillFreehand.js ***! - \**************************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ fillInsideFreehand: () => (/* binding */ fillInsideFreehand), -/* harmony export */ fillOutsideFreehand: () => (/* binding */ fillOutsideFreehand) -/* harmony export */ }); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! cornerstone-tools */ "./node_modules/cornerstone-tools/dist/cornerstoneTools.js"); -/* harmony import */ var cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cornerstone_tools__WEBPACK_IMPORTED_MODULE_0__); -/** @module imaging/strategies/fillFreehand - * @desc This file provides functionalities for - * filling pixels - */ - -// external libraries - -const { - getBoundingBoxAroundPolygon, - fillInsideShape, - fillOutsideShape -} = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().importInternal("util/segmentationUtils"); -const isPointInPolygon = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().importInternal("util/isPointInPolygon"); -const getLogger = cornerstone_tools__WEBPACK_IMPORTED_MODULE_0___default().importInternal("util/getLogger"); -const logger = getLogger("util:segmentation:operations:fillInsideFreehand"); - -/** - * Fill all pixels inside/outside the region defined by - * `operationData.points` with the `activeSegmentIndex` value. - * @param {} evt The Cornerstone event. - * @param {} operationData An object containing the `pixelData` to - * modify, the `segmentIndex` and the `points` array. - * @returns {null} - */ -function fillFreehand(evt, operationData, inside = true) { - const { - points, - segmentationMixinType - } = operationData; - if (segmentationMixinType !== `freehandSegmentationMixin`) { - logger.error(`eraseInsideFreehand operation requires freehandSegmentationMixin operationData, recieved ${segmentationMixinType}`); - return; - } - - // Obtain the bounding box of the entire drawing so that - // we can subset our search. Outside of the bounding box, - // everything is outside of the polygon. - const { - image - } = evt.detail; - const vertices = points.map(a => [a.x, a.y]); - const [topLeft, bottomRight] = getBoundingBoxAroundPolygon(vertices, image); - inside ? fillInsideShape(evt, operationData, point => isPointInPolygon([point.x, point.y], vertices), topLeft, bottomRight) : fillOutsideShape(evt, operationData, point => isPointInPolygon([point.x, point.y], vertices), topLeft, bottomRight); -} - -/** - * Fill all pixels inside/outside the region defined by `operationData.points`. - * @param {} evt The Cornerstone event. - * @param {} operationData An object containing the `pixelData` to - * modify, the `segmentIndex` and the `points` array. - * @returns {null} - */ -function fillInsideFreehand(evt, operationData) { - fillFreehand(evt, operationData, true); -} - -/** - * Fill all pixels outside the region defined by `operationData.points`. - * @param {} evt The Cornerstone event. - * @param {} operationData An object containing the `pixelData` to - * modify, the `segmentIndex` and the `points` array. - * @returns {null} - */ -function fillOutsideFreehand(evt, operationData) { - fillFreehand(evt, operationData, false); -} - -/***/ }), - -/***/ "./imaging/tools/strategies/index.js": -/*!*******************************************!*\ - !*** ./imaging/tools/strategies/index.js ***! - \*******************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ eraseInsideFreehand: () => (/* reexport safe */ _eraseFreehand__WEBPACK_IMPORTED_MODULE_0__.eraseInsideFreehand), -/* harmony export */ eraseOutsideFreehand: () => (/* reexport safe */ _eraseFreehand__WEBPACK_IMPORTED_MODULE_0__.eraseOutsideFreehand), -/* harmony export */ fillInsideFreehand: () => (/* reexport safe */ _fillFreehand__WEBPACK_IMPORTED_MODULE_1__.fillInsideFreehand), -/* harmony export */ fillOutsideFreehand: () => (/* reexport safe */ _fillFreehand__WEBPACK_IMPORTED_MODULE_1__.fillOutsideFreehand) -/* harmony export */ }); -/* harmony import */ var _eraseFreehand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./eraseFreehand */ "./imaging/tools/strategies/eraseFreehand.js"); -/* harmony import */ var _fillFreehand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./fillFreehand */ "./imaging/tools/strategies/fillFreehand.js"); - - - -/***/ }), - -/***/ "./node_modules/cornerstone-core/dist/cornerstone.js": -/*!***********************************************************!*\ - !*** ./node_modules/cornerstone-core/dist/cornerstone.js ***! - \***********************************************************/ +/***/ 7371: /***/ ((module) => { /*! cornerstone-core - 2.6.1 - 2021-11-19 | (c) 2016 Chris Hafey | https://github.com/cornerstonejs/cornerstone */ @@ -13764,10 +13628,7 @@ var vertexShader = 'attribute vec2 a_position;' + 'attribute vec2 a_texCoord;' + /***/ }), -/***/ "./node_modules/cornerstone-file-image-loader/dist/cornerstoneFileImageLoader.min.js": -/*!*******************************************************************************************!*\ - !*** ./node_modules/cornerstone-file-image-loader/dist/cornerstoneFileImageLoader.min.js ***! - \*******************************************************************************************/ +/***/ 2869: /***/ ((module) => { /*! cornerstone-file-image-loader - 0.3.0 - 2020-04-16 | (c) 2016 Chris Hafey | https://github.com/webnamics/cornerstoneFileImageLoader#readme */ @@ -13776,10 +13637,7 @@ var vertexShader = 'attribute vec2 a_position;' + 'attribute vec2 a_texCoord;' + /***/ }), -/***/ "./node_modules/cornerstone-math/dist/cornerstoneMath.min.js": -/*!*******************************************************************!*\ - !*** ./node_modules/cornerstone-math/dist/cornerstoneMath.min.js ***! - \*******************************************************************/ +/***/ 352: /***/ ((module) => { /*! cornerstone-math - 0.1.10 - 2022-06-09 | (c) 2017 Chris Hafey | https://github.com/cornerstonejs/cornerstoneMath */ @@ -13788,10 +13646,7 @@ var vertexShader = 'attribute vec2 a_position;' + 'attribute vec2 a_texCoord;' + /***/ }), -/***/ "./node_modules/cornerstone-tools/dist/cornerstoneTools.js": -/*!*****************************************************************!*\ - !*** ./node_modules/cornerstone-tools/dist/cornerstoneTools.js ***! - \*****************************************************************/ +/***/ 4030: /***/ ((module) => { /*! cornerstone-tools - 6.0.10 - 2023-07-21 | (c) 2017 Chris Hafey | https://github.com/cornerstonejs/cornerstoneTools */ @@ -52233,10 +52088,7 @@ __nested_webpack_require_1640476__.r(__nested_webpack_exports__); /***/ }), -/***/ "./node_modules/cornerstone-wado-image-loader/dist/cornerstoneWADOImageLoader.bundle.min.js": -/*!**************************************************************************************************!*\ - !*** ./node_modules/cornerstone-wado-image-loader/dist/cornerstoneWADOImageLoader.bundle.min.js ***! - \**************************************************************************************************/ +/***/ 2424: /***/ (function(module) { !function(A,I){ true?module.exports=I():0}(this,(function(){return(()=>{var A={6089:(A,I,g)=>{A.exports=g(7945)},4559:(A,I,g)=>{A.exports=g(9842)},6059:(A,I,g)=>{A.exports=g(3798)},9387:(A,I,g)=>{A.exports=g(2149)},7423:(A,I,g)=>{A.exports=g(6970)},2633:(A,I,g)=>{A.exports=g(2343)},6028:(A,I,g)=>{A.exports=g(671)},187:(A,I,g)=>{A.exports=g(7283)},1655:(A,I,g)=>{A.exports=g(2882)},1246:(A,I,g)=>{A.exports=g(8730)},2165:(A,I,g)=>{A.exports=g(569)},337:(A,I,g)=>{A.exports=g(3751)},2770:(A,I,g)=>{A.exports=g(1380)},1882:(A,I,g)=>{A.exports=g(6601)},7878:(A,I,g)=>{A.exports=g(8783)},1929:(A,I,g)=>{A.exports=g(3498)},2250:(A,I,g)=>{A.exports=g(4731)},1407:(A,I,g)=>{A.exports=g(3355)},6051:(A,I,g)=>{A.exports=g(4640)},6275:(A,I,g)=>{A.exports=g(846)},7830:(A,I,g)=>{A.exports=g(7686)},3091:(A,I,g)=>{A.exports=g(5681)},8355:(A,I,g)=>{A.exports=g(9863)},1082:(A,I,g)=>{A.exports=g(5723)},2475:(A,I,g)=>{A.exports=g(8887)},9719:(A,I,g)=>{A.exports=g(9009)},5534:(A,I,g)=>{A.exports=g(5213)},4215:(A,I,g)=>{A.exports=g(156)},6496:(A,I,g)=>{A.exports=g(7754)},7458:A=>{var I=function(A){"use strict";var I,g=Object.prototype,B=g.hasOwnProperty,Q="function"==typeof Symbol?Symbol:{},C=Q.iterator||"@@iterator",E=Q.asyncIterator||"@@asyncIterator",i=Q.toStringTag||"@@toStringTag";function o(A,I,g){return Object.defineProperty(A,I,{value:g,enumerable:!0,configurable:!0,writable:!0}),A[I]}try{o({},"")}catch(A){o=function(A,I,g){return A[I]=g}}function t(A,I,g,B){var Q=I&&I.prototype instanceof n?I:n,C=Object.create(Q.prototype),E=new M(B||[]);return C._invoke=function(A,I,g){var B=D;return function(Q,C){if(B===r)throw new Error("Generator is already running");if(B===e){if("throw"===Q)throw C;return L()}for(g.method=Q,g.arg=C;;){var E=g.delegate;if(E){var i=k(E,g);if(i){if(i===w)continue;return i}}if("next"===g.method)g.sent=g._sent=g.arg;else if("throw"===g.method){if(B===D)throw B=e,g.arg;g.dispatchException(g.arg)}else"return"===g.method&&g.abrupt("return",g.arg);B=r;var o=a(A,I,g);if("normal"===o.type){if(B=g.done?e:s,o.arg===w)continue;return{value:o.arg,done:g.done}}"throw"===o.type&&(B=e,g.method="throw",g.arg=o.arg)}}}(A,g,E),C}function a(A,I,g){try{return{type:"normal",arg:A.call(I,g)}}catch(A){return{type:"throw",arg:A}}}A.wrap=t;var D="suspendedStart",s="suspendedYield",r="executing",e="completed",w={};function n(){}function c(){}function h(){}var y={};o(y,C,(function(){return this}));var G=Object.getPrototypeOf,F=G&&G(G(H([])));F&&F!==g&&B.call(F,C)&&(y=F);var N=h.prototype=n.prototype=Object.create(y);function R(A){["next","throw","return"].forEach((function(I){o(A,I,(function(A){return this._invoke(I,A)}))}))}function S(A,I){function g(Q,C,E,i){var o=a(A[Q],A,C);if("throw"!==o.type){var t=o.arg,D=t.value;return D&&"object"==typeof D&&B.call(D,"__await")?I.resolve(D.__await).then((function(A){g("next",A,E,i)}),(function(A){g("throw",A,E,i)})):I.resolve(D).then((function(A){t.value=A,E(t)}),(function(A){return g("throw",A,E,i)}))}i(o.arg)}var Q;this._invoke=function(A,B){function C(){return new I((function(I,Q){g(A,B,I,Q)}))}return Q=Q?Q.then(C,C):C()}}function k(A,g){var B=A.iterator[g.method];if(B===I){if(g.delegate=null,"throw"===g.method){if(A.iterator.return&&(g.method="return",g.arg=I,k(A,g),"throw"===g.method))return w;g.method="throw",g.arg=new TypeError("The iterator does not provide a 'throw' method")}return w}var Q=a(B,A.iterator,g.arg);if("throw"===Q.type)return g.method="throw",g.arg=Q.arg,g.delegate=null,w;var C=Q.arg;return C?C.done?(g[A.resultName]=C.value,g.next=A.nextLoc,"return"!==g.method&&(g.method="next",g.arg=I),g.delegate=null,w):C:(g.method="throw",g.arg=new TypeError("iterator result is not an object"),g.delegate=null,w)}function U(A){var I={tryLoc:A[0]};1 in A&&(I.catchLoc=A[1]),2 in A&&(I.finallyLoc=A[2],I.afterLoc=A[3]),this.tryEntries.push(I)}function J(A){var I=A.completion||{};I.type="normal",delete I.arg,A.completion=I}function M(A){this.tryEntries=[{tryLoc:"root"}],A.forEach(U,this),this.reset(!0)}function H(A){if(A){var g=A[C];if(g)return g.call(A);if("function"==typeof A.next)return A;if(!isNaN(A.length)){var Q=-1,E=function g(){for(;++Q=0;--C){var E=this.tryEntries[C],i=E.completion;if("root"===E.tryLoc)return Q("end");if(E.tryLoc<=this.prev){var o=B.call(E,"catchLoc"),t=B.call(E,"finallyLoc");if(o&&t){if(this.prev=0;--g){var Q=this.tryEntries[g];if(Q.tryLoc<=this.prev&&B.call(Q,"finallyLoc")&&this.prev=0;--I){var g=this.tryEntries[I];if(g.finallyLoc===A)return this.complete(g.completion,g.afterLoc),J(g),w}},catch:function(A){for(var I=this.tryEntries.length-1;I>=0;--I){var g=this.tryEntries[I];if(g.tryLoc===A){var B=g.completion;if("throw"===B.type){var Q=B.arg;J(g)}return Q}}throw new Error("illegal catch attempt")},delegateYield:function(A,g,B){return this.delegate={iterator:H(A),resultName:g,nextLoc:B},"next"===this.method&&(this.arg=I),w}},A}(A.exports);try{regeneratorRuntime=I}catch(A){"object"==typeof globalThis?globalThis.regeneratorRuntime=I:Function("r","regeneratorRuntime = r")(I)}},2043:(A,I,g)=>{A.exports=g(7458)},5109:(A,I,g)=>{g(8109),g(2166);var B=g(9081);A.exports=B.Array.from},1316:(A,I,g)=>{g(9506);var B=g(9081);A.exports=B.Array.isArray},3641:(A,I,g)=>{g(3075);var B=g(2018);A.exports=B("Array").concat},646:(A,I,g)=>{g(8345);var B=g(2018);A.exports=B("Array").filter},3147:(A,I,g)=>{g(7670);var B=g(2018);A.exports=B("Array").includes},5197:(A,I,g)=>{g(194);var B=g(2018);A.exports=B("Array").indexOf},474:(A,I,g)=>{g(2478);var B=g(2018);A.exports=B("Array").map},1001:(A,I,g)=>{g(5313);var B=g(2018);A.exports=B("Array").slice},5587:(A,I,g)=>{g(4596);var B=g(2018);A.exports=B("Array").splice},3386:(A,I,g)=>{g(2228),g(8109);var B=g(5018);A.exports=B},7543:(A,I,g)=>{var B=g(3641),Q=Array.prototype;A.exports=function(A){var I=A.concat;return A===Q||A instanceof Array&&I===Q.concat?B:I}},3918:(A,I,g)=>{var B=g(646),Q=Array.prototype;A.exports=function(A){var I=A.filter;return A===Q||A instanceof Array&&I===Q.filter?B:I}},790:(A,I,g)=>{var B=g(6270),Q=RegExp.prototype;A.exports=function(A){return(A===Q||A instanceof RegExp)&&!("flags"in A)?B(A):A.flags}},9109:(A,I,g)=>{var B=g(3147),Q=g(5520),C=Array.prototype,E=String.prototype;A.exports=function(A){var I=A.includes;return A===C||A instanceof Array&&I===C.includes?B:"string"==typeof A||A===E||A instanceof String&&I===E.includes?Q:I}},4825:(A,I,g)=>{var B=g(5197),Q=Array.prototype;A.exports=function(A){var I=A.indexOf;return A===Q||A instanceof Array&&I===Q.indexOf?B:I}},238:(A,I,g)=>{var B=g(474),Q=Array.prototype;A.exports=function(A){var I=A.map;return A===Q||A instanceof Array&&I===Q.map?B:I}},1285:(A,I,g)=>{var B=g(1001),Q=Array.prototype;A.exports=function(A){var I=A.slice;return A===Q||A instanceof Array&&I===Q.slice?B:I}},7681:(A,I,g)=>{var B=g(5587),Q=Array.prototype;A.exports=function(A){var I=A.splice;return A===Q||A instanceof Array&&I===Q.splice?B:I}},8949:(A,I,g)=>{var B=g(9119),Q=String.prototype;A.exports=function(A){var I=A.trim;return"string"==typeof A||A===Q||A instanceof String&&I===Q.trim?B:I}},2529:(A,I,g)=>{g(8524);var B=g(9081);A.exports=B.Object.assign},6854:(A,I,g)=>{g(6764);var B=g(9081).Object,Q=A.exports=function(A,I,g){return B.defineProperty(A,I,g)};B.defineProperty.sham&&(Q.sham=!0)},1553:(A,I,g)=>{g(9398);var B=g(9081);A.exports=B.Object.entries},2744:(A,I,g)=>{g(554);var B=g(9081).Object,Q=A.exports=function(A,I){return B.getOwnPropertyDescriptor(A,I)};B.getOwnPropertyDescriptor.sham&&(Q.sham=!0)},6018:(A,I,g)=>{g(4267);var B=g(9081);A.exports=B.Object.getOwnPropertyDescriptors},5350:(A,I,g)=>{g(8318);var B=g(9081);A.exports=B.Object.getOwnPropertySymbols},8116:(A,I,g)=>{g(8390);var B=g(9081);A.exports=B.Object.keys},3140:(A,I,g)=>{g(1171);var B=g(9081);A.exports=B.Object.values},7281:(A,I,g)=>{g(6454),g(2228),g(2575),g(3658),g(4465),g(8359),g(5203),g(8109);var B=g(9081);A.exports=B.Promise},6270:(A,I,g)=>{g(8714);var B=g(4980);A.exports=function(A){return B.call(A)}},5520:(A,I,g)=>{g(688);var B=g(2018);A.exports=B("String").includes},9119:(A,I,g)=>{g(8217);var B=g(2018);A.exports=B("String").trim},1442:(A,I,g)=>{g(3075),g(2575),g(8318),g(5762),g(2181),g(8848),g(3719),g(2362),g(346),g(888),g(909),g(1992),g(8512),g(6165),g(6044),g(4676),g(263),g(3596),g(1227),g(6268);var B=g(9081);A.exports=B.Symbol},1190:(A,I,g)=>{g(2228),g(2575),g(8109),g(2362);var B=g(3379);A.exports=B.f("iterator")},4640:(A,I,g)=>{var B=g(7945);A.exports=B},846:(A,I,g)=>{var B=g(8010);A.exports=B},7686:(A,I,g)=>{var B=g(7922);A.exports=B},5681:(A,I,g)=>{var B=g(5838);A.exports=B},9863:(A,I,g)=>{var B=g(671);A.exports=B},5723:(A,I,g)=>{var B=g(8531);A.exports=B},8887:(A,I,g)=>{var B=g(6601);A.exports=B},9009:(A,I,g)=>{var B=g(8783);A.exports=B},5213:(A,I,g)=>{var B=g(4731);g(1152),g(8561),g(5543),g(8302),A.exports=B},156:(A,I,g)=>{var B=g(3355);g(8812),g(9085),g(7384),g(4719),g(2994),g(9431),g(4803),A.exports=B},7754:(A,I,g)=>{var B=g(6888);A.exports=B},9077:A=>{A.exports=function(A){if("function"!=typeof A)throw TypeError(String(A)+" is not a function");return A}},9154:(A,I,g)=>{var B=g(3359);A.exports=function(A){if(!B(A)&&null!==A)throw TypeError("Can't set "+String(A)+" as a prototype");return A}},7710:A=>{A.exports=function(){}},8792:A=>{A.exports=function(A,I,g){if(!(A instanceof I))throw TypeError("Incorrect "+(g?g+" ":"")+"invocation");return A}},1912:(A,I,g)=>{var B=g(3359);A.exports=function(A){if(!B(A))throw TypeError(String(A)+" is not an object");return A}},4746:(A,I,g)=>{"use strict";var B=g(7673),Q=g(2058),C=g(7647),E=g(6475),i=g(3746),o=g(409),t=g(5945),a=g(5018);A.exports=function(A){var I,g,D,s,r,e,w=Q(A),n="function"==typeof this?this:Array,c=arguments.length,h=c>1?arguments[1]:void 0,y=void 0!==h,G=a(w),F=0;if(y&&(h=B(h,c>2?arguments[2]:void 0,2)),null==G||n==Array&&E(G))for(g=new n(I=i(w.length));I>F;F++)e=y?h(w[F],F):w[F],o(g,F,e);else for(r=(s=t(w,G)).next,g=new n;!(D=r.call(s)).done;F++)e=y?C(s,h,[D.value,F],!0):D.value,o(g,F,e);return g.length=F,g}},7075:(A,I,g)=>{var B=g(3861),Q=g(3746),C=g(3055),E=function(A){return function(I,g,E){var i,o=B(I),t=Q(o.length),a=C(E,t);if(A&&g!=g){for(;t>a;)if((i=o[a++])!=i)return!0}else for(;t>a;a++)if((A||a in o)&&o[a]===g)return A||a||0;return!A&&-1}};A.exports={includes:E(!0),indexOf:E(!1)}},4206:(A,I,g)=>{var B=g(7673),Q=g(1251),C=g(2058),E=g(3746),i=g(8136),o=[].push,t=function(A){var I=1==A,g=2==A,t=3==A,a=4==A,D=6==A,s=7==A,r=5==A||D;return function(e,w,n,c){for(var h,y,G=C(e),F=Q(G),N=B(w,n,3),R=E(F.length),S=0,k=c||i,U=I?k(e,R):g||s?k(e,0):void 0;R>S;S++)if((r||S in F)&&(y=N(h=F[S],S,G),A))if(I)U[S]=y;else if(y)switch(A){case 3:return!0;case 5:return h;case 6:return S;case 2:o.call(U,h)}else switch(A){case 4:return!1;case 7:o.call(U,h)}return D?-1:t||a?a:U}};A.exports={forEach:t(0),map:t(1),filter:t(2),some:t(3),every:t(4),find:t(5),findIndex:t(6),filterReject:t(7)}},2143:(A,I,g)=>{var B=g(6530),Q=g(5712),C=g(8233),E=Q("species");A.exports=function(A){return C>=51||!B((function(){var I=[];return(I.constructor={})[E]=function(){return{foo:1}},1!==I[A](Boolean).foo}))}},6628:(A,I,g)=>{"use strict";var B=g(6530);A.exports=function(A,I){var g=[][A];return!!g&&B((function(){g.call(null,I||function(){throw 1},1)}))}},3400:(A,I,g)=>{var B=g(3359),Q=g(1054),C=g(5712)("species");A.exports=function(A){var I;return Q(A)&&("function"!=typeof(I=A.constructor)||I!==Array&&!Q(I.prototype)?B(I)&&null===(I=I[C])&&(I=void 0):I=void 0),void 0===I?Array:I}},8136:(A,I,g)=>{var B=g(3400);A.exports=function(A,I){return new(B(A))(0===I?0:I)}},7647:(A,I,g)=>{var B=g(1912),Q=g(309);A.exports=function(A,I,g,C){try{return C?I(B(g)[0],g[1]):I(g)}catch(I){Q(A,"throw",I)}}},8908:(A,I,g)=>{var B=g(5712)("iterator"),Q=!1;try{var C=0,E={next:function(){return{done:!!C++}},return:function(){Q=!0}};E[B]=function(){return this},Array.from(E,(function(){throw 2}))}catch(A){}A.exports=function(A,I){if(!I&&!Q)return!1;var g=!1;try{var C={};C[B]=function(){return{next:function(){return{done:g=!0}}}},A(C)}catch(A){}return g}},1570:A=>{var I={}.toString;A.exports=function(A){return I.call(A).slice(8,-1)}},7499:(A,I,g)=>{var B=g(8889),Q=g(1570),C=g(5712)("toStringTag"),E="Arguments"==Q(function(){return arguments}());A.exports=B?Q:function(A){var I,g,B;return void 0===A?"Undefined":null===A?"Null":"string"==typeof(g=function(A,I){try{return A[I]}catch(A){}}(I=Object(A),C))?g:E?Q(I):"Object"==(B=Q(I))&&"function"==typeof I.callee?"Arguments":B}},1722:(A,I,g)=>{var B=g(5712)("match");A.exports=function(A){var I=/./;try{"/./"[A](I)}catch(g){try{return I[B]=!1,"/./"[A](I)}catch(A){}}return!1}},872:(A,I,g)=>{var B=g(6530);A.exports=!B((function(){function A(){}return A.prototype.constructor=null,Object.getPrototypeOf(new A)!==A.prototype}))},8611:(A,I,g)=>{"use strict";var B=g(7300).IteratorPrototype,Q=g(2491),C=g(5595),E=g(6738),i=g(9418),o=function(){return this};A.exports=function(A,I,g){var t=I+" Iterator";return A.prototype=Q(B,{next:C(1,g)}),E(A,t,!1,!0),i[t]=o,A}},8518:(A,I,g)=>{var B=g(7001),Q=g(7550),C=g(5595);A.exports=B?function(A,I,g){return Q.f(A,I,C(1,g))}:function(A,I,g){return A[I]=g,A}},5595:A=>{A.exports=function(A,I){return{enumerable:!(1&A),configurable:!(2&A),writable:!(4&A),value:I}}},409:(A,I,g)=>{"use strict";var B=g(5623),Q=g(7550),C=g(5595);A.exports=function(A,I,g){var E=B(I);E in A?Q.f(A,E,C(0,g)):A[E]=g}},8277:(A,I,g)=>{"use strict";var B=g(390),Q=g(8611),C=g(2629),E=g(5255),i=g(6738),o=g(8518),t=g(9791),a=g(5712),D=g(8367),s=g(9418),r=g(7300),e=r.IteratorPrototype,w=r.BUGGY_SAFARI_ITERATORS,n=a("iterator"),c="keys",h="values",y="entries",G=function(){return this};A.exports=function(A,I,g,a,r,F,N){Q(g,I,a);var R,S,k,U=function(A){if(A===r&&Y)return Y;if(!w&&A in H)return H[A];switch(A){case c:case h:case y:return function(){return new g(this,A)}}return function(){return new g(this)}},J=I+" Iterator",M=!1,H=A.prototype,L=H[n]||H["@@iterator"]||r&&H[r],Y=!w&&L||U(r),l="Array"==I&&H.entries||L;if(l&&(R=C(l.call(new A)),e!==Object.prototype&&R.next&&(D||C(R)===e||(E?E(R,e):"function"!=typeof R[n]&&o(R,n,G)),i(R,J,!0,!0),D&&(s[J]=G))),r==h&&L&&L.name!==h&&(M=!0,Y=function(){return L.call(this)}),D&&!N||H[n]===Y||o(H,n,Y),s[I]=Y,r)if(S={values:U(h),keys:F?Y:U(c),entries:U(y)},N)for(k in S)(w||M||!(k in H))&&t(H,k,S[k]);else B({target:I,proto:!0,forced:w||M},S);return S}},1412:(A,I,g)=>{var B=g(9081),Q=g(6690),C=g(3379),E=g(7550).f;A.exports=function(A){var I=B.Symbol||(B.Symbol={});Q(I,A)||E(I,A,{value:C.f(A)})}},7001:(A,I,g)=>{var B=g(6530);A.exports=!B((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},231:(A,I,g)=>{var B=g(2868),Q=g(3359),C=B.document,E=Q(C)&&Q(C.createElement);A.exports=function(A){return E?C.createElement(A):{}}},315:A=>{A.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},6610:A=>{A.exports="object"==typeof window},6405:(A,I,g)=>{var B=g(6900),Q=g(2868);A.exports=/ipad|iphone|ipod/i.test(B)&&void 0!==Q.Pebble},6540:(A,I,g)=>{var B=g(6900);A.exports=/(?:ipad|iphone|ipod).*applewebkit/i.test(B)},6521:(A,I,g)=>{var B=g(1570),Q=g(2868);A.exports="process"==B(Q.process)},4010:(A,I,g)=>{var B=g(6900);A.exports=/web0s(?!.*chrome)/i.test(B)},6900:(A,I,g)=>{var B=g(8104);A.exports=B("navigator","userAgent")||""},8233:(A,I,g)=>{var B,Q,C=g(2868),E=g(6900),i=C.process,o=C.Deno,t=i&&i.versions||o&&o.version,a=t&&t.v8;a?Q=(B=a.split("."))[0]<4?1:B[0]+B[1]:E&&(!(B=E.match(/Edge\/(\d+)/))||B[1]>=74)&&(B=E.match(/Chrome\/(\d+)/))&&(Q=B[1]),A.exports=Q&&+Q},2018:(A,I,g)=>{var B=g(9081);A.exports=function(A){return B[A+"Prototype"]}},2712:A=>{A.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},390:(A,I,g)=>{"use strict";var B=g(2868),Q=g(5904).f,C=g(1011),E=g(9081),i=g(7673),o=g(8518),t=g(6690),a=function(A){var I=function(I,g,B){if(this instanceof A){switch(arguments.length){case 0:return new A;case 1:return new A(I);case 2:return new A(I,g)}return new A(I,g,B)}return A.apply(this,arguments)};return I.prototype=A.prototype,I};A.exports=function(A,I){var g,D,s,r,e,w,n,c,h=A.target,y=A.global,G=A.stat,F=A.proto,N=y?B:G?B[h]:(B[h]||{}).prototype,R=y?E:E[h]||o(E,h,{})[h],S=R.prototype;for(s in I)g=!C(y?s:h+(G?".":"#")+s,A.forced)&&N&&t(N,s),e=R[s],g&&(w=A.noTargetGet?(c=Q(N,s))&&c.value:N[s]),r=g&&w?w:I[s],g&&typeof e==typeof r||(n=A.bind&&g?i(r,B):A.wrap&&g?a(r):F&&"function"==typeof r?i(Function.call,r):r,(A.sham||r&&r.sham||e&&e.sham)&&o(n,"sham",!0),o(R,s,n),F&&(t(E,D=h+"Prototype")||o(E,D,{}),o(E[D],s,r),A.real&&S&&!S[s]&&o(S,s,r)))}},6530:A=>{A.exports=function(A){try{return!!A()}catch(A){return!0}}},7673:(A,I,g)=>{var B=g(9077);A.exports=function(A,I,g){if(B(A),void 0===I)return A;switch(g){case 0:return function(){return A.call(I)};case 1:return function(g){return A.call(I,g)};case 2:return function(g,B){return A.call(I,g,B)};case 3:return function(g,B,Q){return A.call(I,g,B,Q)}}return function(){return A.apply(I,arguments)}}},8104:(A,I,g)=>{var B=g(9081),Q=g(2868),C=function(A){return"function"==typeof A?A:void 0};A.exports=function(A,I){return arguments.length<2?C(B[A])||C(Q[A]):B[A]&&B[A][I]||Q[A]&&Q[A][I]}},5018:(A,I,g)=>{var B=g(7499),Q=g(9418),C=g(5712)("iterator");A.exports=function(A){if(null!=A)return A[C]||A["@@iterator"]||Q[B(A)]}},5945:(A,I,g)=>{var B=g(1912),Q=g(5018);A.exports=function(A,I){var g=arguments.length<2?Q(A):I;if("function"!=typeof g)throw TypeError(String(A)+" is not iterable");return B(g.call(A))}},2868:(A,I,g)=>{var B=function(A){return A&&A.Math==Math&&A};A.exports=B("object"==typeof globalThis&&globalThis)||B("object"==typeof window&&window)||B("object"==typeof self&&self)||B("object"==typeof g.g&&g.g)||function(){return this}()||Function("return this")()},6690:(A,I,g)=>{var B=g(2058),Q={}.hasOwnProperty;A.exports=Object.hasOwn||function(A,I){return Q.call(B(A),I)}},7048:A=>{A.exports={}},8471:(A,I,g)=>{var B=g(2868);A.exports=function(A,I){var g=B.console;g&&g.error&&(1===arguments.length?g.error(A):g.error(A,I))}},5644:(A,I,g)=>{var B=g(8104);A.exports=B("document","documentElement")},640:(A,I,g)=>{var B=g(7001),Q=g(6530),C=g(231);A.exports=!B&&!Q((function(){return 7!=Object.defineProperty(C("div"),"a",{get:function(){return 7}}).a}))},1251:(A,I,g)=>{var B=g(6530),Q=g(1570),C="".split;A.exports=B((function(){return!Object("z").propertyIsEnumerable(0)}))?function(A){return"String"==Q(A)?C.call(A,""):Object(A)}:Object},1494:(A,I,g)=>{var B=g(2656),Q=Function.toString;"function"!=typeof B.inspectSource&&(B.inspectSource=function(A){return Q.call(A)}),A.exports=B.inspectSource},5875:(A,I,g)=>{var B,Q,C,E=g(2297),i=g(2868),o=g(3359),t=g(8518),a=g(6690),D=g(2656),s=g(5039),r=g(7048),e="Object already initialized",w=i.WeakMap;if(E||D.state){var n=D.state||(D.state=new w),c=n.get,h=n.has,y=n.set;B=function(A,I){if(h.call(n,A))throw new TypeError(e);return I.facade=A,y.call(n,A,I),I},Q=function(A){return c.call(n,A)||{}},C=function(A){return h.call(n,A)}}else{var G=s("state");r[G]=!0,B=function(A,I){if(a(A,G))throw new TypeError(e);return I.facade=A,t(A,G,I),I},Q=function(A){return a(A,G)?A[G]:{}},C=function(A){return a(A,G)}}A.exports={set:B,get:Q,has:C,enforce:function(A){return C(A)?Q(A):B(A,{})},getterFor:function(A){return function(I){var g;if(!o(I)||(g=Q(I)).type!==A)throw TypeError("Incompatible receiver, "+A+" required");return g}}}},6475:(A,I,g)=>{var B=g(5712),Q=g(9418),C=B("iterator"),E=Array.prototype;A.exports=function(A){return void 0!==A&&(Q.Array===A||E[C]===A)}},1054:(A,I,g)=>{var B=g(1570);A.exports=Array.isArray||function(A){return"Array"==B(A)}},1011:(A,I,g)=>{var B=g(6530),Q=/#|\.prototype\./,C=function(A,I){var g=i[E(A)];return g==t||g!=o&&("function"==typeof I?B(I):!!I)},E=C.normalize=function(A){return String(A).replace(Q,".").toLowerCase()},i=C.data={},o=C.NATIVE="N",t=C.POLYFILL="P";A.exports=C},3359:A=>{A.exports=function(A){return"object"==typeof A?null!==A:"function"==typeof A}},8367:A=>{A.exports=!0},6858:(A,I,g)=>{var B=g(3359),Q=g(1570),C=g(5712)("match");A.exports=function(A){var I;return B(A)&&(void 0!==(I=A[C])?!!I:"RegExp"==Q(A))}},1149:(A,I,g)=>{var B=g(8104),Q=g(6046);A.exports=Q?function(A){return"symbol"==typeof A}:function(A){var I=B("Symbol");return"function"==typeof I&&Object(A)instanceof I}},3906:(A,I,g)=>{var B=g(1912),Q=g(6475),C=g(3746),E=g(7673),i=g(5945),o=g(5018),t=g(309),a=function(A,I){this.stopped=A,this.result=I};A.exports=function(A,I,g){var D,s,r,e,w,n,c,h=g&&g.that,y=!(!g||!g.AS_ENTRIES),G=!(!g||!g.IS_ITERATOR),F=!(!g||!g.INTERRUPTED),N=E(I,h,1+y+F),R=function(A){return D&&t(D,"normal",A),new a(!0,A)},S=function(A){return y?(B(A),F?N(A[0],A[1],R):N(A[0],A[1])):F?N(A,R):N(A)};if(G)D=A;else{if("function"!=typeof(s=o(A)))throw TypeError("Target is not iterable");if(Q(s)){for(r=0,e=C(A.length);e>r;r++)if((w=S(A[r]))&&w instanceof a)return w;return new a(!1)}D=i(A,s)}for(n=D.next;!(c=n.call(D)).done;){try{w=S(c.value)}catch(A){t(D,"throw",A)}if("object"==typeof w&&w&&w instanceof a)return w}return new a(!1)}},309:(A,I,g)=>{var B=g(1912);A.exports=function(A,I,g){var Q,C;B(A);try{if(void 0===(Q=A.return)){if("throw"===I)throw g;return g}Q=Q.call(A)}catch(A){C=!0,Q=A}if("throw"===I)throw g;if(C)throw Q;return B(Q),g}},7300:(A,I,g)=>{"use strict";var B,Q,C,E=g(6530),i=g(2629),o=g(8518),t=g(6690),a=g(5712),D=g(8367),s=a("iterator"),r=!1;[].keys&&("next"in(C=[].keys())?(Q=i(i(C)))!==Object.prototype&&(B=Q):r=!0);var e=null==B||E((function(){var A={};return B[s].call(A)!==A}));e&&(B={}),D&&!e||t(B,s)||o(B,s,(function(){return this})),A.exports={IteratorPrototype:B,BUGGY_SAFARI_ITERATORS:r}},9418:A=>{A.exports={}},8054:(A,I,g)=>{var B,Q,C,E,i,o,t,a,D=g(2868),s=g(5904).f,r=g(6569).set,e=g(6540),w=g(6405),n=g(4010),c=g(6521),h=D.MutationObserver||D.WebKitMutationObserver,y=D.document,G=D.process,F=D.Promise,N=s(D,"queueMicrotask"),R=N&&N.value;R||(B=function(){var A,I;for(c&&(A=G.domain)&&A.exit();Q;){I=Q.fn,Q=Q.next;try{I()}catch(A){throw Q?E():C=void 0,A}}C=void 0,A&&A.enter()},e||c||n||!h||!y?!w&&F&&F.resolve?((t=F.resolve(void 0)).constructor=F,a=t.then,E=function(){a.call(t,B)}):E=c?function(){G.nextTick(B)}:function(){r.call(D,B)}:(i=!0,o=y.createTextNode(""),new h(B).observe(o,{characterData:!0}),E=function(){o.data=i=!i})),A.exports=R||function(A){var I={fn:A,next:void 0};C&&(C.next=I),Q||(Q=I,E()),C=I}},3104:(A,I,g)=>{var B=g(2868);A.exports=B.Promise},3216:(A,I,g)=>{var B=g(8233),Q=g(6530);A.exports=!!Object.getOwnPropertySymbols&&!Q((function(){var A=Symbol();return!String(A)||!(Object(A)instanceof Symbol)||!Symbol.sham&&B&&B<41}))},2297:(A,I,g)=>{var B=g(2868),Q=g(1494),C=B.WeakMap;A.exports="function"==typeof C&&/native code/.test(Q(C))},4130:(A,I,g)=>{"use strict";var B=g(9077),Q=function(A){var I,g;this.promise=new A((function(A,B){if(void 0!==I||void 0!==g)throw TypeError("Bad Promise constructor");I=A,g=B})),this.resolve=B(I),this.reject=B(g)};A.exports.f=function(A){return new Q(A)}},9611:(A,I,g)=>{var B=g(6858);A.exports=function(A){if(B(A))throw TypeError("The method doesn't accept regular expressions");return A}},3210:(A,I,g)=>{"use strict";var B=g(7001),Q=g(6530),C=g(461),E=g(3764),i=g(1156),o=g(2058),t=g(1251),a=Object.assign,D=Object.defineProperty;A.exports=!a||Q((function(){if(B&&1!==a({b:1},a(D({},"a",{enumerable:!0,get:function(){D(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var A={},I={},g=Symbol(),Q="abcdefghijklmnopqrst";return A[g]=7,Q.split("").forEach((function(A){I[A]=A})),7!=a({},A)[g]||C(a({},I)).join("")!=Q}))?function(A,I){for(var g=o(A),Q=arguments.length,a=1,D=E.f,s=i.f;Q>a;)for(var r,e=t(arguments[a++]),w=D?C(e).concat(D(e)):C(e),n=w.length,c=0;n>c;)r=w[c++],B&&!s.call(e,r)||(g[r]=e[r]);return g}:a},2491:(A,I,g)=>{var B,Q=g(1912),C=g(4686),E=g(2712),i=g(7048),o=g(5644),t=g(231),a=g(5039),D=a("IE_PROTO"),s=function(){},r=function(A){return"