├── .eslintignore ├── src ├── version.js ├── util │ ├── roundToDecimal.js │ ├── isMouseButtonEnabled.js │ ├── pointInsideBoundingBox.js │ ├── clip.js │ ├── drawCircle.js │ ├── lineSegDistance.js │ ├── copyPoints.js │ ├── convertToVector3.js │ ├── freehand │ │ ├── ClickedLineData.js │ │ ├── freeHandArea.js │ │ ├── FreehandHandleData.js │ │ ├── keysHeld.js │ │ ├── dragObject.js │ │ ├── dropObject.js │ │ └── calculateFreehandStatistics.js │ ├── pointInEllipse.js │ ├── triggerEvent.js │ ├── drawEllipse.js │ ├── makeUnselectable.js │ ├── setContextToDisplayFontSize.js │ ├── calculateEllipseStatistics.js │ ├── drawLinkedTextBox.js │ ├── getRGBPixels.js │ ├── getLuminance.js │ ├── drawArrow.js │ ├── drawLink.js │ ├── calculateSUV.js │ ├── scroll.js │ ├── drawTextBox.js │ └── getMaxSimultaneousRequests.js ├── .gitattributes ├── stackTools │ ├── stackRenderers.js │ ├── stackScrollKeyboard.js │ ├── scrollIndicator.js │ └── fusionRenderer.js ├── orientation │ ├── index.js │ ├── invertOrientationString.js │ └── getOrientationString.js ├── stateManagement │ ├── toolCoordinates.js │ ├── toolStyle.js │ ├── textStyle.js │ ├── loadHandlerManager.js │ ├── toolColors.js │ ├── appState.js │ ├── timeSeriesSpecificStateManager.js │ ├── toolState.js │ ├── frameOfReferenceStateManager.js │ ├── stackSpecificStateManager.js │ └── imageIdSpecificStateManager.js ├── referenceLines │ ├── index.js │ ├── calculateReferenceLine.js │ ├── referenceLinesTool.js │ └── renderActiveReferenceLine.js ├── synchronization │ ├── updateImageSynchronizer.js │ ├── panZoomSynchronizer.js │ ├── wwwcSynchronizer.js │ ├── stackImageIndexSynchronizer.js │ ├── stackScrollSynchronizer.js │ └── stackImagePositionSynchronizer.js ├── externalModules.js ├── measurementManager │ ├── lineSampleMeasurement.js │ └── measurementManager.js ├── imageTools │ ├── doubleTapZoom.js │ ├── doubleTapTool.js │ ├── mouseWheelTool.js │ ├── touchPinchTool.js │ ├── displayTool.js │ ├── rotateTouch.js │ ├── imageStats.js │ ├── keyboardTool.js │ ├── panMultiTouch.js │ ├── simpleTouchTool.js │ ├── simpleMouseButtonTool.js │ ├── eraser.js │ ├── saveAs.js │ ├── touchDragTool.js │ ├── multiTouchDragTool.js │ ├── pan.js │ └── orientationMarkers.js ├── manipulators │ ├── anyHandlesOutsideImage.js │ ├── handleActivator.js │ ├── getHandleNearImagePoint.js │ ├── drawHandles.js │ ├── moveHandle.js │ ├── moveAllHandles.js │ ├── touchMoveAllHandles.js │ └── moveNewHandle.js ├── paintingTools │ ├── getCircle.js │ ├── drawBrush.js │ └── brush.js ├── events.js ├── inputSources │ ├── keyboardInput.js │ ├── preventGhostClick.js │ └── mouseWheelInput.js ├── timeSeriesTools │ ├── incrementTimePoint.js │ └── timeSeriesPlayer.js └── toolOptions.js ├── .babelrc ├── docs ├── README.md ├── assets │ └── CNAME ├── contributing.md ├── essentials │ ├── image-tools.md │ ├── stack-tools.md │ └── tool-states.md ├── custom-tools │ ├── simple-tools.md │ └── anatomy-of-a-tool.md ├── advanced │ └── layered-image-stacks.md ├── book.json ├── SUMMARY.md ├── installation.md └── guides │ └── migrating-major-versions.md ├── examples ├── 00-tool-images │ ├── pan.gif │ ├── angle.gif │ ├── length.gif │ ├── probe.gif │ ├── wwwc.gif │ ├── zoom.gif │ ├── rectangle-roi.gif │ ├── stack-scroll.gif │ ├── elliptical-roi.gif │ └── reference-lines.gif ├── cornerstone.min.css ├── dialogPolyfill.css ├── exampleNumberMetaDataProvider.js ├── exampleMetaDataProvider.js ├── exampleTextImageLoader.js ├── petctMetaDataProvider.js ├── saveAs │ └── index.html ├── loadHandlers │ └── index.html ├── imageStats │ └── index.html └── scaleOverlayTool │ └── index.html ├── config ├── webpack │ ├── index.js │ ├── webpack-dev.js │ ├── webpack-prod.js │ ├── merge.js │ ├── plugins │ │ └── banner.js │ └── webpack-base.js └── karma │ ├── karma-firefox.js │ ├── karma-extend.js │ ├── karma-watch.js │ ├── karma-chrome.js │ ├── karma-coverage.js │ ├── karma-coveralls.js │ └── karma-base.js ├── .gitignore ├── .travis.yml ├── .npmignore ├── test ├── coverage_test.js ├── util │ ├── freehand │ │ ├── freeHandArea_test.js │ │ ├── calculateFreehandStatistics_test.js │ │ └── pointInFreehand_test.js │ └── copyPoints_test.js └── requestPool │ └── requestPoolManager_test.js ├── .jsdocrc ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── LICENSE └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | config/** 2 | -------------------------------------------------------------------------------- /src/version.js: -------------------------------------------------------------------------------- 1 | export default '2.3.7'; 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } 4 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | {% include "./SUMMARY.md" %} 2 | -------------------------------------------------------------------------------- /docs/assets/CNAME: -------------------------------------------------------------------------------- 1 | tools.cornerstonejs.org 2 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ... 4 | -------------------------------------------------------------------------------- /docs/essentials/image-tools.md: -------------------------------------------------------------------------------- 1 | # Image Tools 2 | 3 | ... 4 | -------------------------------------------------------------------------------- /docs/custom-tools/simple-tools.md: -------------------------------------------------------------------------------- 1 | # Simple Tools 2 | 3 | ... 4 | -------------------------------------------------------------------------------- /docs/advanced/layered-image-stacks.md: -------------------------------------------------------------------------------- 1 | # Layered Image Stacks 2 | 3 | ... 4 | -------------------------------------------------------------------------------- /docs/custom-tools/anatomy-of-a-tool.md: -------------------------------------------------------------------------------- 1 | # Anatomy of a Tool 2 | 3 | ... 4 | -------------------------------------------------------------------------------- /examples/00-tool-images/pan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianliuzhu/cornerstoneToolsExample/HEAD/examples/00-tool-images/pan.gif -------------------------------------------------------------------------------- /examples/00-tool-images/angle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianliuzhu/cornerstoneToolsExample/HEAD/examples/00-tool-images/angle.gif -------------------------------------------------------------------------------- /examples/00-tool-images/length.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianliuzhu/cornerstoneToolsExample/HEAD/examples/00-tool-images/length.gif -------------------------------------------------------------------------------- /examples/00-tool-images/probe.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianliuzhu/cornerstoneToolsExample/HEAD/examples/00-tool-images/probe.gif -------------------------------------------------------------------------------- /examples/00-tool-images/wwwc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianliuzhu/cornerstoneToolsExample/HEAD/examples/00-tool-images/wwwc.gif -------------------------------------------------------------------------------- /examples/00-tool-images/zoom.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianliuzhu/cornerstoneToolsExample/HEAD/examples/00-tool-images/zoom.gif -------------------------------------------------------------------------------- /config/webpack/index.js: -------------------------------------------------------------------------------- 1 | const env = process.env.ENV || 'dev'; 2 | const config = require(`./webpack-${env}`); 3 | 4 | module.exports = config; -------------------------------------------------------------------------------- /examples/00-tool-images/rectangle-roi.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianliuzhu/cornerstoneToolsExample/HEAD/examples/00-tool-images/rectangle-roi.gif -------------------------------------------------------------------------------- /examples/00-tool-images/stack-scroll.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianliuzhu/cornerstoneToolsExample/HEAD/examples/00-tool-images/stack-scroll.gif -------------------------------------------------------------------------------- /examples/00-tool-images/elliptical-roi.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianliuzhu/cornerstoneToolsExample/HEAD/examples/00-tool-images/elliptical-roi.gif -------------------------------------------------------------------------------- /examples/00-tool-images/reference-lines.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bianliuzhu/cornerstoneToolsExample/HEAD/examples/00-tool-images/reference-lines.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Directorties 2 | node_modules 3 | coverage/ 4 | dist/ 5 | docs/_book/ 6 | 7 | ## Files 8 | docs/api.md 9 | 10 | ## File Types 11 | .idea 12 | -------------------------------------------------------------------------------- /src/util/roundToDecimal.js: -------------------------------------------------------------------------------- 1 | export default function (value, precision) { 2 | const multiplier = Math.pow(10, precision); 3 | 4 | 5 | return (Math.round(value * multiplier) / multiplier); 6 | } 7 | -------------------------------------------------------------------------------- /src/.gitattributes: -------------------------------------------------------------------------------- 1 | # Enforce Unix newlines 2 | *.css text eol=lf 3 | *.html text eol=lf 4 | *.js text eol=lf 5 | *.json text eol=lf 6 | *.less text eol=lf 7 | *.md text eol=lf 8 | *.yml text eol=lf 9 | -------------------------------------------------------------------------------- /src/stackTools/stackRenderers.js: -------------------------------------------------------------------------------- 1 | import FusionRenderer from './fusionRenderer.js'; 2 | 3 | const stackRenderers = {}; 4 | 5 | stackRenderers.FusionRenderer = FusionRenderer; 6 | 7 | export default stackRenderers; 8 | -------------------------------------------------------------------------------- /src/util/isMouseButtonEnabled.js: -------------------------------------------------------------------------------- 1 | /* eslint no-bitwise:0 */ 2 | 3 | export default function (which, mouseButtonMask) { 4 | const mouseButton = (1 << (which - 1)); 5 | 6 | 7 | return ((mouseButtonMask & mouseButton) !== 0); 8 | } 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "lts/*" 4 | addons: 5 | chrome: stable 6 | cache: 7 | yarn: true 8 | directories: 9 | - node_modules 10 | 11 | after_success: 12 | cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js -------------------------------------------------------------------------------- /config/karma/karma-firefox.js: -------------------------------------------------------------------------------- 1 | const extendConfiguration = require('./karma-extend.js'); 2 | 3 | module.exports = function (config) { 4 | 'use strict'; 5 | config.set(extendConfiguration({ 6 | singleRun: true, 7 | browsers: ['Firefox'] 8 | })); 9 | }; 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | ## All Non-Dist Directories 2 | .github/ 3 | build/ 4 | config/ 5 | docs/ 6 | examples/ 7 | src/ 8 | test/ 9 | 10 | ## Root Files 11 | .babelrc 12 | .eslintignore 13 | .gitignore 14 | .jsdocrc 15 | .npmignore 16 | .travis.yml 17 | changelog.md 18 | karma.conf.js 19 | -------------------------------------------------------------------------------- /src/util/pointInsideBoundingBox.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | 3 | export default function (handle, coords) { 4 | if (!handle.boundingBox) { 5 | return; 6 | } 7 | 8 | return external.cornerstoneMath.point.insideRect(coords, handle.boundingBox); 9 | } 10 | -------------------------------------------------------------------------------- /src/orientation/index.js: -------------------------------------------------------------------------------- 1 | import getOrientationString from './getOrientationString.js'; 2 | import invertOrientationString from './invertOrientationString.js'; 3 | 4 | const orientation = { 5 | getOrientationString, 6 | invertOrientationString 7 | }; 8 | 9 | export default orientation; 10 | -------------------------------------------------------------------------------- /examples/cornerstone.min.css: -------------------------------------------------------------------------------- 1 | /*! cornerstone - v0.10.3 - 2017-02-24 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstone */.cornerstone-enabled-image{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default} -------------------------------------------------------------------------------- /test/coverage_test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { expect } from 'chai'; 3 | 4 | import * as cornerstoneTools from '../src/index'; 5 | 6 | describe('A test that pulls in all modules', function () { 7 | it('pulls in all modules', function () { 8 | expect(cornerstoneTools).to.exist; 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/stateManagement/toolCoordinates.js: -------------------------------------------------------------------------------- 1 | let coordsData; 2 | 3 | function setCoords (eventData) { 4 | coordsData = eventData.currentPoints.canvas; 5 | } 6 | 7 | function getCoords () { 8 | return coordsData; 9 | } 10 | 11 | const toolCoordinates = { 12 | setCoords, 13 | getCoords 14 | }; 15 | 16 | export default toolCoordinates; 17 | -------------------------------------------------------------------------------- /src/referenceLines/index.js: -------------------------------------------------------------------------------- 1 | import calculateReferenceLine from './calculateReferenceLine.js'; 2 | import tool from './referenceLinesTool.js'; 3 | import renderActiveReferenceLine from './renderActiveReferenceLine.js'; 4 | 5 | const referenceLines = { 6 | calculateReferenceLine, 7 | tool, 8 | renderActiveReferenceLine 9 | }; 10 | 11 | export default referenceLines; 12 | -------------------------------------------------------------------------------- /src/util/clip.js: -------------------------------------------------------------------------------- 1 | export default function clip (val, low, high) { 2 | // Clip a value to an upper and lower bound. 3 | return Math.min(Math.max(low, val), high); 4 | } 5 | 6 | export function clipToBox (point, box) { 7 | // Clip an {x, y} point to a box of size {width, height} 8 | point.x = clip(point.x, 0, box.width); 9 | point.y = clip(point.y, 0, box.height); 10 | } 11 | -------------------------------------------------------------------------------- /src/util/drawCircle.js: -------------------------------------------------------------------------------- 1 | import { path } from './drawing.js'; 2 | 3 | /** 4 | * @deprecated Use drawing.js:drawCircle() 5 | */ 6 | export default function (context, start, color, lineWidth) { 7 | const handleRadius = 6; 8 | 9 | path(context, { color, 10 | lineWidth }, (context) => { 11 | context.arc(start.x, start.y, handleRadius, 0, 2 * Math.PI); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /config/webpack/webpack-dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const merge = require('./merge'); 3 | const baseConfig = require('./webpack-base'); 4 | 5 | const devConfig = { 6 | devServer: { 7 | hot: true, 8 | publicPath: '/dist/' 9 | }, 10 | plugins: [ 11 | new webpack.HotModuleReplacementPlugin({}) 12 | ] 13 | }; 14 | 15 | module.exports = merge(baseConfig, devConfig); -------------------------------------------------------------------------------- /config/karma/karma-extend.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('./karma-base.js'); 2 | 3 | module.exports = function (extendedConfig) { 4 | 'use strict'; 5 | // Overrides the base configuration for karma with the given properties 6 | for (var i in baseConfig) { 7 | if (typeof extendedConfig[i] === 'undefined') { 8 | extendedConfig[i] = baseConfig[i]; 9 | } 10 | } 11 | return extendedConfig; 12 | }; 13 | -------------------------------------------------------------------------------- /config/karma/karma-watch.js: -------------------------------------------------------------------------------- 1 | const extendConfiguration = require('./karma-extend.js'); 2 | 3 | module.exports = function (config) { 4 | 'use strict'; 5 | config.set(extendConfiguration({ 6 | browsers: ['ChromeHeadlessNoSandbox'], 7 | customLaunchers: { 8 | ChromeHeadlessNoSandbox: { 9 | base: 'ChromeHeadless', 10 | flags: ['--no-sandbox'] 11 | } 12 | } 13 | })); 14 | }; 15 | -------------------------------------------------------------------------------- /.jsdocrc: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "include": "./src" 4 | }, 5 | "opts": { 6 | "template": "node_modules/docdash", 7 | "encoding": "utf8", 8 | "destination": "documentation/", 9 | "recurse": true, 10 | "verbose": true 11 | }, 12 | "templates": { 13 | "default": { 14 | "includeDate": false 15 | } 16 | }, 17 | "docdash": { 18 | "static": true, 19 | "sort": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/orientation/invertOrientationString.js: -------------------------------------------------------------------------------- 1 | export default function (string) { 2 | let inverted = string.replace('H', 'f'); 3 | 4 | inverted = inverted.replace('F', 'h'); 5 | inverted = inverted.replace('R', 'l'); 6 | inverted = inverted.replace('L', 'r'); 7 | inverted = inverted.replace('A', 'p'); 8 | inverted = inverted.replace('P', 'a'); 9 | inverted = inverted.toUpperCase(); 10 | 11 | return inverted; 12 | } 13 | -------------------------------------------------------------------------------- /src/util/lineSegDistance.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | 3 | export default function (element, start, end, coords) { 4 | const cornerstone = external.cornerstone; 5 | 6 | const lineSegment = { 7 | start: cornerstone.pixelToCanvas(element, start), 8 | end: cornerstone.pixelToCanvas(element, end) 9 | }; 10 | 11 | return external.cornerstoneMath.lineSegment.distanceToPoint(lineSegment, coords); 12 | } 13 | -------------------------------------------------------------------------------- /config/karma/karma-chrome.js: -------------------------------------------------------------------------------- 1 | const extendConfiguration = require('./karma-extend.js'); 2 | 3 | module.exports = function (config) { 4 | 'use strict'; 5 | config.set(extendConfiguration({ 6 | singleRun: true, 7 | browsers: ['ChromeHeadlessNoSandbox'], 8 | customLaunchers: { 9 | ChromeHeadlessNoSandbox: { 10 | base: 'ChromeHeadless', 11 | flags: ['--no-sandbox'] 12 | } 13 | } 14 | })); 15 | }; 16 | -------------------------------------------------------------------------------- /src/synchronization/updateImageSynchronizer.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | 3 | // This function causes the target image to be drawn immediately 4 | export default function (synchronizer, sourceElement, targetElement) { 5 | 6 | // Ignore the case where the source and target are the same enabled element 7 | if (targetElement === sourceElement) { 8 | return; 9 | } 10 | 11 | external.cornerstone.updateImage(targetElement); 12 | } 13 | -------------------------------------------------------------------------------- /config/karma/karma-coverage.js: -------------------------------------------------------------------------------- 1 | const extendConfiguration = require('./karma-extend.js'); 2 | 3 | module.exports = function (config) { 4 | 'use strict'; 5 | config.set(extendConfiguration({ 6 | singleRun: true, 7 | reporters: ['progress', 'coverage'], 8 | browsers: ['ChromeHeadlessNoSandbox'], 9 | customLaunchers: { 10 | ChromeHeadlessNoSandbox: { 11 | base: 'ChromeHeadless', 12 | flags: ['--no-sandbox'] 13 | } 14 | } 15 | })); 16 | }; 17 | -------------------------------------------------------------------------------- /config/webpack/webpack-prod.js: -------------------------------------------------------------------------------- 1 | const merge = require('./merge'); 2 | const baseConfig = require('./webpack-base'); 3 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 4 | 5 | const prodConfig = { 6 | output: { 7 | filename: '[name].min.js' 8 | }, 9 | mode: "production", 10 | optimization: { 11 | minimizer: [ 12 | new UglifyJSPlugin({ 13 | sourceMap: true 14 | }) 15 | ] 16 | }, 17 | }; 18 | 19 | module.exports = merge(baseConfig, prodConfig); -------------------------------------------------------------------------------- /src/util/copyPoints.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | 3 | export default function (points) { 4 | const page = external.cornerstoneMath.point.copy(points.page); 5 | const image = external.cornerstoneMath.point.copy(points.image); 6 | const client = external.cornerstoneMath.point.copy(points.client); 7 | const canvas = external.cornerstoneMath.point.copy(points.canvas); 8 | 9 | return { 10 | page, 11 | image, 12 | client, 13 | canvas 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /config/karma/karma-coveralls.js: -------------------------------------------------------------------------------- 1 | const extendConfiguration = require('./karma-extend.js'); 2 | 3 | module.exports = function (config) { 4 | 'use strict'; 5 | config.set(extendConfiguration({ 6 | singleRun: true, 7 | reporters: ['progress', 'coverage', 'coveralls'], 8 | browsers: ['ChromeHeadlessNoSandbox'], 9 | customLaunchers: { 10 | ChromeHeadlessNoSandbox: { 11 | base: 'ChromeHeadless', 12 | flags: ['--no-sandbox'] 13 | } 14 | } 15 | })); 16 | }; 17 | -------------------------------------------------------------------------------- /src/stateManagement/toolStyle.js: -------------------------------------------------------------------------------- 1 | let defaultWidth = 1, 2 | activeWidth = 2; 3 | 4 | function setToolWidth (width) { 5 | defaultWidth = width; 6 | } 7 | 8 | function getToolWidth () { 9 | return defaultWidth; 10 | } 11 | 12 | function setActiveWidth (width) { 13 | activeWidth = width; 14 | } 15 | 16 | function getActiveWidth () { 17 | return activeWidth; 18 | } 19 | 20 | const toolStyle = { 21 | setToolWidth, 22 | getToolWidth, 23 | setActiveWidth, 24 | getActiveWidth 25 | }; 26 | 27 | export default toolStyle; 28 | -------------------------------------------------------------------------------- /src/externalModules.js: -------------------------------------------------------------------------------- 1 | let cornerstone = window.cornerstone; 2 | let cornerstoneMath = window.cornerstoneMath; 3 | let Hammer = window.Hammer; 4 | 5 | export default { 6 | set cornerstone (cs) { 7 | cornerstone = cs; 8 | }, 9 | get cornerstone () { 10 | return cornerstone; 11 | }, 12 | set cornerstoneMath (cm) { 13 | cornerstoneMath = cm; 14 | }, 15 | get cornerstoneMath () { 16 | return cornerstoneMath; 17 | }, 18 | set Hammer (module) { 19 | Hammer = module; 20 | }, 21 | get Hammer () { 22 | return Hammer; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/measurementManager/lineSampleMeasurement.js: -------------------------------------------------------------------------------- 1 | import EVENTS from '../events.js'; 2 | import external from '../externalModules.js'; 3 | import triggerEvent from '../util/triggerEvent.js'; 4 | 5 | // This object manages a collection of measurements 6 | export default function () { 7 | const cornerstone = external.cornerstone; 8 | const that = this; 9 | 10 | that.samples = []; 11 | 12 | this.set = function (samples) { 13 | that.samples = samples; 14 | // Fire event 15 | triggerEvent(cornerstone.events, EVENTS.LINE_SAMPLE_UPDATED); 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /config/webpack/merge.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | // Merge two objects 4 | // Instead of merging array objects index by index (n-th source 5 | // item with n-th object item) it concatenates both arrays 6 | module.exports = function(object, source) { 7 | const clone = _.cloneDeep(object); 8 | const merged = _.mergeWith(clone, source, function(objValue, srcValue, key, object, source, stack) { 9 | if(objValue && srcValue && _.isArray(objValue) && _.isArray(srcValue)) { 10 | return _.concat(objValue, srcValue); 11 | } 12 | }); 13 | 14 | return merged; 15 | } -------------------------------------------------------------------------------- /src/util/convertToVector3.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | 3 | /** 4 | * Convert an Array to a cornerstoneMath.Vector3 5 | * 6 | * @param {Array|cornerstoneMath.Vector3} arrayOrVector3 Input array or Vector3 7 | * @return {cornerstoneMath.Vector3} 8 | */ 9 | export default function convertToVector3 (arrayOrVector3) { 10 | const cornerstoneMath = external.cornerstoneMath; 11 | 12 | if (arrayOrVector3 instanceof cornerstoneMath.Vector3) { 13 | return arrayOrVector3; 14 | } 15 | 16 | return new cornerstoneMath.Vector3(arrayOrVector3[0], arrayOrVector3[1], arrayOrVector3[2]); 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Prerequisites 2 | 3 | * [ ] Are you running the latest version? 4 | * [ ] Are you reporting to the correct repository? 5 | * [ ] Did you perform a cursory search? 6 | 7 | For more information, see the `CONTRIBUTING` guide. 8 | 9 | ### Description 10 | 11 | [Description of the bug or feature] 12 | 13 | ### Steps to Reproduce 14 | 15 | 1. [First Step] 16 | 2. [Second Step] 17 | 3. [and so on...] 18 | 19 | **Expected behavior:** [What you expected to happen] 20 | 21 | **Actual behavior:** [What actually happened] 22 | 23 | 24 | ### Codepen With Reproduction of Issue: 25 | 26 | - [Basic Template](https://codepen.io/dannyrb/pen/OQQwza) 27 | -------------------------------------------------------------------------------- /src/imageTools/doubleTapZoom.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | import doubleTapTool from './doubleTapTool.js'; 3 | 4 | function fitToWindowStrategy (eventData) { 5 | external.cornerstone.fitToWindow(eventData.element); 6 | } 7 | 8 | function doubleTapCallback (e) { 9 | const eventData = e.detail; 10 | 11 | doubleTapZoom.strategy(eventData); 12 | 13 | e.preventDefault(); 14 | e.stopPropagation(); 15 | } 16 | 17 | const doubleTapZoom = doubleTapTool(doubleTapCallback); 18 | 19 | doubleTapZoom.strategies = { 20 | default: fitToWindowStrategy 21 | }; 22 | 23 | doubleTapZoom.strategy = fitToWindowStrategy; 24 | 25 | export default doubleTapZoom; 26 | -------------------------------------------------------------------------------- /src/imageTools/doubleTapTool.js: -------------------------------------------------------------------------------- 1 | import EVENTS from '../events.js'; 2 | 3 | export default function (doubleTapCallback) { 4 | return { 5 | activate (element) { 6 | element.removeEventListener(EVENTS.DOUBLE_TAP, doubleTapCallback); 7 | element.addEventListener(EVENTS.DOUBLE_TAP, doubleTapCallback); 8 | }, 9 | disable (element) { 10 | element.removeEventListener(EVENTS.DOUBLE_TAP, doubleTapCallback); 11 | }, 12 | enable (element) { 13 | element.removeEventListener(EVENTS.DOUBLE_TAP, doubleTapCallback); 14 | }, 15 | deactivate (element) { 16 | element.removeEventListener(EVENTS.DOUBLE_TAP, doubleTapCallback); 17 | } 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/imageTools/mouseWheelTool.js: -------------------------------------------------------------------------------- 1 | import EVENTS from '../events.js'; 2 | 3 | export default function (mouseWheelCallback) { 4 | return { 5 | activate (element) { 6 | element.removeEventListener(EVENTS.MOUSE_WHEEL, mouseWheelCallback); 7 | element.addEventListener(EVENTS.MOUSE_WHEEL, mouseWheelCallback); 8 | }, 9 | disable (element) { 10 | element.removeEventListener(EVENTS.MOUSE_WHEEL, mouseWheelCallback); 11 | }, 12 | enable (element) { 13 | element.removeEventListener(EVENTS.MOUSE_WHEEL, mouseWheelCallback); 14 | }, 15 | deactivate (element) { 16 | element.removeEventListener(EVENTS.MOUSE_WHEEL, mouseWheelCallback); 17 | } 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/imageTools/touchPinchTool.js: -------------------------------------------------------------------------------- 1 | import EVENTS from '../events.js'; 2 | 3 | export default function (touchPinchCallback) { 4 | return { 5 | activate (element) { 6 | element.removeEventListener(EVENTS.TOUCH_PINCH, touchPinchCallback); 7 | element.addEventListener(EVENTS.TOUCH_PINCH, touchPinchCallback); 8 | }, 9 | disable (element) { 10 | element.removeEventListener(EVENTS.TOUCH_PINCH, touchPinchCallback); 11 | }, 12 | enable (element) { 13 | element.removeEventListener(EVENTS.TOUCH_PINCH, touchPinchCallback); 14 | }, 15 | deactivate (element) { 16 | element.removeEventListener(EVENTS.TOUCH_PINCH, touchPinchCallback); 17 | } 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/stackTools/stackScrollKeyboard.js: -------------------------------------------------------------------------------- 1 | import scroll from '../util/scroll.js'; 2 | import keyboardTool from '../imageTools/keyboardTool.js'; 3 | 4 | const keys = { 5 | UP: 38, 6 | DOWN: 40 7 | }; 8 | 9 | function keyDownCallback (e) { 10 | const eventData = e.detail; 11 | const keyCode = eventData.keyCode; 12 | 13 | if (keyCode !== keys.UP && keyCode !== keys.DOWN) { 14 | return; 15 | } 16 | 17 | let images = 1; 18 | 19 | if (keyCode === keys.DOWN) { 20 | images = -1; 21 | } 22 | 23 | scroll(eventData.element, images); 24 | } 25 | 26 | // Module/private exports 27 | const stackScrollKeyboard = keyboardTool(keyDownCallback); 28 | 29 | export default stackScrollKeyboard; 30 | -------------------------------------------------------------------------------- /src/manipulators/anyHandlesOutsideImage.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | 3 | export default function (renderData, handles) { 4 | const image = renderData.image; 5 | const imageRect = { 6 | left: 0, 7 | top: 0, 8 | width: image.width, 9 | height: image.height 10 | }; 11 | 12 | let handleOutsideImage = false; 13 | 14 | Object.keys(handles).forEach(function (name) { 15 | const handle = handles[name]; 16 | 17 | if (handle.allowedOutsideImage === true) { 18 | return; 19 | } 20 | 21 | if (external.cornerstoneMath.point.insideRect(handle, imageRect) === false) { 22 | handleOutsideImage = true; 23 | } 24 | }); 25 | 26 | return handleOutsideImage; 27 | } 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * **Please check if the PR fulfills these requirements** 2 | - [ ] The commit message follows our guidelines 3 | - [ ] Tests for the changes have been added (for bug fixes / features) 4 | - [ ] Docs have been added / updated (for bug fixes / features) 5 | 6 | 7 | * **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 8 | 9 | 10 | 11 | * **What is the current behavior?** (You can also link to an open issue here) 12 | 13 | 14 | 15 | * **What is the new behavior (if this is a feature change)?** 16 | 17 | 18 | 19 | * **Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?) 20 | 21 | 22 | 23 | * **Other information**: 24 | -------------------------------------------------------------------------------- /src/imageTools/displayTool.js: -------------------------------------------------------------------------------- 1 | 2 | import EVENTS from '../events.js'; 3 | import external from '../externalModules.js'; 4 | 5 | export default function (onImageRendered) { 6 | let configuration = {}; 7 | 8 | return { 9 | disable (element) { 10 | element.removeEventListener(EVENTS.IMAGE_RENDERED, onImageRendered); 11 | }, 12 | enable (element) { 13 | element.removeEventListener(EVENTS.IMAGE_RENDERED, onImageRendered); 14 | element.addEventListener(EVENTS.IMAGE_RENDERED, onImageRendered); 15 | external.cornerstone.updateImage(element); 16 | }, 17 | getConfiguration () { 18 | return configuration; 19 | }, 20 | setConfiguration (config) { 21 | configuration = config; 22 | } 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/util/freehand/ClickedLineData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} ClickedLineData 3 | * @property {Number} toolIndex ID of the tool that the line corresponds to. 4 | * @property {Object} handleIndexArray An array of the handle indicies that correspond to the line segment. 5 | */ 6 | export class ClickedLineData { 7 | 8 | /** 9 | * Constructs an object containing information about the clicked line. 10 | * 11 | * @param {Number} toolIndex - The ID of the tool the line corresponds to. 12 | * @param {Object} handleIndexArray - An array of the handle indicies that correspond to the line segment. 13 | */ 14 | constructor (toolIndex, handleIndexArray) { 15 | this.toolIndex = toolIndex; 16 | this.handleIndexArray = handleIndexArray; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/util/pointInEllipse.js: -------------------------------------------------------------------------------- 1 | export default function (ellipse, location) { 2 | const xRadius = ellipse.width / 2; 3 | const yRadius = ellipse.height / 2; 4 | 5 | if (xRadius <= 0.0 || yRadius <= 0.0) { 6 | return false; 7 | } 8 | 9 | const center = { 10 | x: ellipse.left + xRadius, 11 | y: ellipse.top + yRadius 12 | }; 13 | 14 | /* This is a more general form of the circle equation 15 | * 16 | * X^2/a^2 + Y^2/b^2 <= 1 17 | */ 18 | 19 | const normalized = { 20 | x: location.x - center.x, 21 | y: location.y - center.y 22 | }; 23 | 24 | const inEllipse = ((normalized.x * normalized.x) / (xRadius * xRadius)) + ((normalized.y * normalized.y) / (yRadius * yRadius)) <= 1.0; 25 | 26 | 27 | return inEllipse; 28 | } 29 | -------------------------------------------------------------------------------- /src/imageTools/rotateTouch.js: -------------------------------------------------------------------------------- 1 | import EVENTS from '../events.js'; 2 | import external from '../externalModules.js'; 3 | 4 | function touchRotateCallback (e) { 5 | const eventData = e.detail; 6 | 7 | eventData.viewport.rotation += eventData.rotation; 8 | external.cornerstone.setViewport(eventData.element, eventData.viewport); 9 | 10 | return false; 11 | } 12 | 13 | function disable (element) { 14 | element.removeEventListener(EVENTS.TOUCH_ROTATE, touchRotateCallback); 15 | } 16 | 17 | function activate (element) { 18 | element.removeEventListener(EVENTS.TOUCH_ROTATE, touchRotateCallback); 19 | element.addEventListener(EVENTS.TOUCH_ROTATE, touchRotateCallback); 20 | } 21 | 22 | const rotateTouch = { 23 | activate, 24 | disable 25 | }; 26 | 27 | export default rotateTouch; 28 | -------------------------------------------------------------------------------- /docs/book.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "cornerstone-tools", 3 | "gitbook": ">3.0.0", 4 | "plugins": [ 5 | "edit-link", 6 | "theme-cornerstone", 7 | "-fontsettings", 8 | "github", 9 | "ga", 10 | "sitemap" 11 | ], 12 | "pluginsConfig": { 13 | "edit-link": { 14 | "base": "https://github.com/cornerstonejs/cornerstoneTools/edit/master/docs", 15 | "label": "Edit This Page" 16 | }, 17 | "github": { 18 | "url": "https://github.com/cornerstonejs/cornerstoneTools" 19 | }, 20 | "ga": { 21 | "token": "UA-110573590-1" 22 | }, 23 | "sitemap": { 24 | "hostname": "https://tools.cornerstonejs.org" 25 | } 26 | }, 27 | "links": { 28 | "sharing": { 29 | "facebook": false, 30 | "twitter": false 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/util/freehand/freeHandArea.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Calculates the area of a freehand tool polygon. 4 | * 5 | * @param {Object} dataHandles - data object associated with the tool. 6 | * @param {Object} scaling - Area scaling of image. 7 | * @return {Number} - The area of the polygon. 8 | */ 9 | export default function (dataHandles, scaling) { 10 | let freeHandArea = 0; 11 | let j = dataHandles.length - 1; // The last vertex is the previous one to the first 12 | 13 | scaling = scaling || 1; // If scaling is falsy, set scaling to 1 14 | 15 | for (let i = 0; i < dataHandles.length; i++) { 16 | freeHandArea += (dataHandles[j].x + dataHandles[i].x) * (dataHandles[j].y - dataHandles[i].y); 17 | j = i; // Here j is previous vertex to i 18 | } 19 | 20 | return Math.abs(freeHandArea * scaling / 2.0); 21 | } 22 | -------------------------------------------------------------------------------- /src/imageTools/imageStats.js: -------------------------------------------------------------------------------- 1 | import displayTool from './displayTool.js'; 2 | import drawTextBox from '../util/drawTextBox.js'; 3 | import { getNewContext } from '../util/drawing.js'; 4 | 5 | function onImageRendered (e) { 6 | const eventData = e.detail; 7 | const image = eventData.image; 8 | const stats = image.stats; 9 | 10 | const context = getNewContext(eventData.canvasContext.canvas); 11 | 12 | const textLines = []; 13 | 14 | Object.keys(stats).forEach(function (key) { 15 | const text = `${key} : ${stats[key]}`; 16 | 17 | textLines.push(text); 18 | }); 19 | 20 | drawTextBox(context, textLines, 0, 0, 'orange'); 21 | 22 | textLines.forEach(function (text) { 23 | console.log(text); 24 | }); 25 | } 26 | 27 | const imageStats = displayTool(onImageRendered); 28 | 29 | export default imageStats; 30 | -------------------------------------------------------------------------------- /src/paintingTools/getCircle.js: -------------------------------------------------------------------------------- 1 | export default function getCircle (radius, rows, columns, xCoord = 0, yCoord = 0) { 2 | const x0 = Math.round(xCoord); 3 | const y0 = Math.round(yCoord); 4 | 5 | if (radius === 1) { 6 | return [[x0, y0]]; 7 | } 8 | 9 | const circleArray = []; 10 | let index = 0; 11 | 12 | for(let y = -radius; y <= radius; y++) { 13 | const yCoord = y0 + y; 14 | 15 | if (yCoord > rows || yCoord < 0) { 16 | continue; 17 | } 18 | 19 | for(let x = -radius; x <= radius; x++) { 20 | const xCoord = x0 + x; 21 | 22 | if (xCoord > columns || xCoord < 0) { 23 | continue; 24 | } 25 | 26 | if (x * x + y * y < radius * radius) { 27 | circleArray[index++] = [x0 + x, y0 + y]; 28 | } 29 | } 30 | } 31 | 32 | return circleArray; 33 | } 34 | -------------------------------------------------------------------------------- /examples/dialogPolyfill.css: -------------------------------------------------------------------------------- 1 | dialog { 2 | position: absolute; 3 | left: 0; right: 0; 4 | width: -moz-fit-content; 5 | width: -webkit-fit-content; 6 | width: fit-content; 7 | height: -moz-fit-content; 8 | height: -webkit-fit-content; 9 | height: fit-content; 10 | margin: auto; 11 | border: solid; 12 | padding: 1em; 13 | background: white; 14 | color: black; 15 | display: none; 16 | } 17 | 18 | dialog[open] { 19 | display: block; 20 | } 21 | 22 | dialog + .backdrop { 23 | position: fixed; 24 | top: 0; right: 0; bottom: 0; left: 0; 25 | background: rgba(0,0,0,0.1); 26 | } 27 | 28 | /* for small devices, modal dialogs go full-screen */ 29 | @media screen and (max-width: 540px) { 30 | dialog[_polyfill_modal] { /* TODO: implement */ 31 | top: 0; 32 | width: auto; 33 | margin: 1em; 34 | } 35 | } -------------------------------------------------------------------------------- /src/stateManagement/textStyle.js: -------------------------------------------------------------------------------- 1 | let defaultFontSize = 15, 2 | defaultFont = `${defaultFontSize}px Arial`, 3 | defaultBackgroundColor = 'transparent'; 4 | 5 | function setFont (font) { 6 | defaultFont = font; 7 | } 8 | 9 | function getFont () { 10 | return defaultFont; 11 | } 12 | 13 | function setFontSize (fontSize) { 14 | defaultFontSize = fontSize; 15 | } 16 | 17 | function getFontSize () { 18 | return defaultFontSize; 19 | } 20 | 21 | function setBackgroundColor (backgroundColor) { 22 | defaultBackgroundColor = backgroundColor; 23 | } 24 | 25 | function getBackgroundColor () { 26 | return defaultBackgroundColor; 27 | } 28 | 29 | const textStyle = { 30 | setFont, 31 | getFont, 32 | setFontSize, 33 | getFontSize, 34 | setBackgroundColor, 35 | getBackgroundColor 36 | }; 37 | 38 | export default textStyle; 39 | -------------------------------------------------------------------------------- /src/referenceLines/calculateReferenceLine.js: -------------------------------------------------------------------------------- 1 | import { planePlaneIntersection, projectPatientPointToImagePlane } from '../util/pointProjector.js'; 2 | 3 | // Calculates a reference line between two planes by projecting the top left hand corner and bottom right hand corner 4 | // Of the reference image onto the target image. Ideally we would calculate the intersection between the planes but 5 | // That requires a bit more math and this works fine for most cases 6 | export default function (targetImagePlane, referenceImagePlane) { 7 | const points = planePlaneIntersection(targetImagePlane, referenceImagePlane); 8 | 9 | if (!points) { 10 | return; 11 | } 12 | 13 | return { 14 | start: projectPatientPointToImagePlane(points.start, targetImagePlane), 15 | end: projectPatientPointToImagePlane(points.end, targetImagePlane) 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /config/webpack/plugins/banner.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const rootPath = process.cwd(); 4 | const pkgPath = path.join(rootPath, "package"); 5 | const pkg = require(pkgPath); 6 | 7 | const getCurrentDate = () => { 8 | const today = new Date(); 9 | const year = today.getFullYear(); 10 | const month = ('0' + (today.getMonth() + 1)).slice(-2); 11 | const date = ('0' + today.getDate()).slice(-2); 12 | 13 | return `${year}-${month}-${date}`; 14 | } 15 | 16 | const getBanner = () => { 17 | return `/*! ${pkg.name} - ${pkg.version} - ` + 18 | `${getCurrentDate()} ` + 19 | `| (c) 2017 Chris Hafey | ${pkg.homepage} */` 20 | } 21 | 22 | module.exports = () => { 23 | return new webpack.BannerPlugin({ 24 | banner: getBanner(), 25 | entryOnly: true, 26 | raw: true 27 | }); 28 | } -------------------------------------------------------------------------------- /src/imageTools/keyboardTool.js: -------------------------------------------------------------------------------- 1 | import EVENTS from '../events.js'; 2 | 3 | export default function (keyDownCallback) { 4 | let configuration = {}; 5 | 6 | return { 7 | activate (element) { 8 | element.removeEventListener(EVENTS.KEY_DOWN, keyDownCallback); 9 | element.addEventListener(EVENTS.KEY_DOWN, keyDownCallback); 10 | }, 11 | disable (element) { 12 | element.removeEventListener(EVENTS.KEY_DOWN, keyDownCallback); 13 | }, 14 | enable (element) { 15 | element.removeEventListener(EVENTS.KEY_DOWN, keyDownCallback); 16 | }, 17 | deactivate (element) { 18 | element.removeEventListener(EVENTS.KEY_DOWN, keyDownCallback); 19 | }, 20 | getConfiguration () { 21 | return configuration; 22 | }, 23 | setConfiguration (config) { 24 | configuration = config; 25 | } 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/util/triggerEvent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Trigger a CustomEvent 3 | * 4 | * @param {EventTarget} el The element or EventTarget to trigger the event upon 5 | * @param {String} type The event type name 6 | * @param {Object|null} detail=null The event data to be sent 7 | * @returns {Boolean} The return value is false if at least one event listener called preventDefault(). Otherwise it returns true. 8 | */ 9 | export default function triggerEvent (el, type, detail = null) { 10 | let event; 11 | 12 | // This check is needed to polyfill CustomEvent on IE11- 13 | if (typeof window.CustomEvent === 'function') { 14 | event = new CustomEvent(type, { 15 | detail, 16 | cancelable: true 17 | }); 18 | } else { 19 | event = document.createEvent('CustomEvent'); 20 | event.initCustomEvent(type, true, true, detail); 21 | } 22 | 23 | return el.dispatchEvent(event); 24 | } 25 | -------------------------------------------------------------------------------- /src/util/drawEllipse.js: -------------------------------------------------------------------------------- 1 | import { path } from './drawing.js'; 2 | 3 | // http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas 4 | 5 | /** 6 | * @deprecated Use drawing.js:drawEllipse() 7 | */ 8 | export default function (context, x, y, w, h) { 9 | const kappa = 0.5522848, 10 | ox = (w / 2) * kappa, // Control point offset horizontal 11 | oy = (h / 2) * kappa, // Control point offset vertical 12 | xe = x + w, // X-end 13 | ye = y + h, // Y-end 14 | xm = x + w / 2, // X-middle 15 | ym = y + h / 2; // Y-middle 16 | 17 | path(context, {}, (context) => { 18 | context.moveTo(x, ym); 19 | context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); 20 | context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); 21 | context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); 22 | context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/util/makeUnselectable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A helper function to make an element (and its content) being non selectable. 3 | * @param element {HTMLElement} The element to make unselectable 4 | * @param ignorePointerEvents {Boolean} true to make this element also ignore events 5 | * (e.g. mouse click), false otherwise 6 | * @returns {void} 7 | */ 8 | export default function (element, ignorePointerEvents) { 9 | element.style.webkitUserSelect = 'none'; 10 | element.style.webkitTouchCallout = 'none'; 11 | element.style.mozUserSelect = 'none'; 12 | element.style.msUserSelect = 'none'; 13 | element.style.oUserSelect = 'none'; 14 | element.style.khtmlUserSelect = 'none'; 15 | element.style.userSelect = 'none'; 16 | 17 | element.unselectable = 'on'; 18 | element.oncontextmenu = () => (false); 19 | 20 | if (ignorePointerEvents === true) { 21 | element.style.pointerEvents = 'none'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/stateManagement/loadHandlerManager.js: -------------------------------------------------------------------------------- 1 | let defaultStartLoadHandler; 2 | let defaultEndLoadHandler; 3 | let defaultErrorLoadingHandler; 4 | 5 | function setStartLoadHandler (handler) { 6 | defaultStartLoadHandler = handler; 7 | } 8 | 9 | function getStartLoadHandler () { 10 | return defaultStartLoadHandler; 11 | } 12 | 13 | function setEndLoadHandler (handler) { 14 | defaultEndLoadHandler = handler; 15 | } 16 | 17 | function getEndLoadHandler () { 18 | return defaultEndLoadHandler; 19 | } 20 | 21 | function setErrorLoadingHandler (handler) { 22 | defaultErrorLoadingHandler = handler; 23 | } 24 | 25 | function getErrorLoadingHandler () { 26 | return defaultErrorLoadingHandler; 27 | } 28 | 29 | const loadHandlerManager = { 30 | setStartLoadHandler, 31 | getStartLoadHandler, 32 | setEndLoadHandler, 33 | getEndLoadHandler, 34 | setErrorLoadingHandler, 35 | getErrorLoadingHandler 36 | }; 37 | 38 | export default loadHandlerManager; 39 | -------------------------------------------------------------------------------- /src/stateManagement/toolColors.js: -------------------------------------------------------------------------------- 1 | 2 | let defaultColor = 'white', 3 | activeColor = 'greenyellow', 4 | fillColor = 'transparent'; 5 | 6 | function setFillColor (color) { 7 | fillColor = color; 8 | } 9 | 10 | function getFillColor () { 11 | return fillColor; 12 | } 13 | 14 | function setToolColor (color) { 15 | defaultColor = color; 16 | } 17 | 18 | function getToolColor () { 19 | return defaultColor; 20 | } 21 | 22 | function setActiveColor (color) { 23 | activeColor = color; 24 | } 25 | 26 | function getActiveColor () { 27 | return activeColor; 28 | } 29 | 30 | function getColorIfActive (data) { 31 | if (data.color) { 32 | return data.color; 33 | } 34 | 35 | return data.active ? activeColor : defaultColor; 36 | } 37 | 38 | const toolColors = { 39 | setFillColor, 40 | getFillColor, 41 | setToolColor, 42 | getToolColor, 43 | setActiveColor, 44 | getActiveColor, 45 | getColorIfActive 46 | }; 47 | 48 | export default toolColors; 49 | -------------------------------------------------------------------------------- /examples/exampleNumberMetaDataProvider.js: -------------------------------------------------------------------------------- 1 | (function (cornerstone) { 2 | 3 | "use strict"; 4 | 5 | function metaDataProvider(type, imageId) { 6 | if(type === 'imagePlaneModule') { 7 | if (imageId.startsWith('example-n')) { 8 | var tokens = imageId.substring(12).split(':'); 9 | var n = Number(tokens[0]); 10 | var z = window.isNaN(n)?0:n; 11 | return { 12 | frameOfReferenceUID: '1.2.3.4.5', 13 | rows: 256, 14 | columns: 256, 15 | rowCosines: [0, 1, 0], 16 | columnCosines: [0, 0, -1], 17 | imagePositionPatient: [-9.4, -92.5, z], 18 | columnPixelSpacing: 0.78, 19 | rowPixelSpacing: 0.78 20 | }; 21 | } 22 | } 23 | } 24 | 25 | cornerstone.metaData.addProvider(metaDataProvider); 26 | 27 | }(cornerstone)); -------------------------------------------------------------------------------- /src/imageTools/panMultiTouch.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | import multiTouchDragTool from './multiTouchDragTool.js'; 3 | 4 | function touchPanCallback (e) { 5 | const eventData = e.detail; 6 | const config = panMultiTouch.getConfiguration(); 7 | 8 | if (config && config.testPointers(eventData)) { 9 | eventData.viewport.translation.x += (eventData.deltaPoints.page.x / eventData.viewport.scale); 10 | eventData.viewport.translation.y += (eventData.deltaPoints.page.y / eventData.viewport.scale); 11 | external.cornerstone.setViewport(eventData.element, eventData.viewport); 12 | 13 | e.preventDefault(); 14 | e.stopPropagation(); 15 | } 16 | } 17 | 18 | const configuration = { 19 | testPointers (eventData) { 20 | return (eventData.numPointers >= 2); 21 | } 22 | }; 23 | 24 | const panMultiTouch = multiTouchDragTool(touchPanCallback); 25 | 26 | panMultiTouch.setConfiguration(configuration); 27 | 28 | export default panMultiTouch; 29 | -------------------------------------------------------------------------------- /src/util/freehand/FreehandHandleData.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @typedef {Object} FreehandHandleData 4 | * @property {Number} x The x position. 5 | * @property {Number} y The y position. 6 | * @property {Boolean} highlight Whether the handle should be rendered as the highlighted color. 7 | * @property {Boolean} active Whether the handle is active. 8 | * @property {Object} lines An array of lines associated with the handle. 9 | */ 10 | export class FreehandHandleData { 11 | 12 | /** 13 | * Constructs a a single handle for the freehand tool 14 | * 15 | * @param {Object} position - The position of the handle. 16 | * @param {Boolean} highlight - whether the handle should be rendered as the highlighted color. 17 | * @param {Boolean} active - whether the handle is active. 18 | */ 19 | constructor (position, highlight = true, active = true) { 20 | this.x = position.x; 21 | this.y = position.y; 22 | this.highlight = highlight; 23 | this.active = active; 24 | this.lines = []; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/util/setContextToDisplayFontSize.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | 3 | /** 4 | * Sets the canvas context transformation matrix so it is scaled to show text 5 | * more cleanly even if the image is scaled up. See 6 | * https://github.com/cornerstonejs/cornerstoneTools/wiki/DrawingText 7 | * for more information 8 | * 9 | * @param enabledElement 10 | * @param context 11 | * @param fontSize 12 | * @returns {{fontSize: number, lineHeight: number, fontScale: number}} 13 | */ 14 | export default function (enabledElement, context, fontSize) { 15 | const fontScale = 0.1; 16 | 17 | external.cornerstone.setToPixelCoordinateSystem(enabledElement, context, fontScale); 18 | // Return the font size to use 19 | const scaledFontSize = fontSize / enabledElement.viewport.scale / fontScale; 20 | // TODO: actually calculate this? 21 | const lineHeight = fontSize / enabledElement.viewport.scale / fontScale; 22 | 23 | 24 | return { 25 | fontSize: scaledFontSize, 26 | lineHeight, 27 | fontScale 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/manipulators/handleActivator.js: -------------------------------------------------------------------------------- 1 | import getHandleNearImagePoint from './getHandleNearImagePoint.js'; 2 | 3 | function getActiveHandle (handles) { 4 | let activeHandle; 5 | 6 | Object.keys(handles).forEach(function (name) { 7 | const handle = handles[name]; 8 | 9 | if (handle.active === true) { 10 | activeHandle = handle; 11 | 12 | return; 13 | } 14 | }); 15 | 16 | return activeHandle; 17 | } 18 | 19 | export default function (element, handles, canvasPoint, distanceThreshold) { 20 | if (!distanceThreshold) { 21 | distanceThreshold = 6; 22 | } 23 | 24 | const activeHandle = getActiveHandle(handles); 25 | const nearbyHandle = getHandleNearImagePoint(element, handles, canvasPoint, distanceThreshold); 26 | 27 | if (activeHandle !== nearbyHandle) { 28 | if (nearbyHandle !== undefined) { 29 | nearbyHandle.active = true; 30 | } 31 | 32 | if (activeHandle !== undefined) { 33 | activeHandle.active = false; 34 | } 35 | 36 | return true; 37 | } 38 | 39 | return false; 40 | } 41 | -------------------------------------------------------------------------------- /src/util/calculateEllipseStatistics.js: -------------------------------------------------------------------------------- 1 | import pointInEllipse from './pointInEllipse.js'; 2 | 3 | export default function (sp, ellipse) { 4 | // TODO: Get a real statistics library here that supports large counts 5 | 6 | let sum = 0; 7 | let sumSquared = 0; 8 | let count = 0; 9 | let index = 0; 10 | 11 | for (let y = ellipse.top; y < ellipse.top + ellipse.height; y++) { 12 | for (let x = ellipse.left; x < ellipse.left + ellipse.width; x++) { 13 | const point = { 14 | x, 15 | y 16 | }; 17 | 18 | if (pointInEllipse(ellipse, point)) { 19 | sum += sp[index]; 20 | sumSquared += sp[index] * sp[index]; 21 | count++; 22 | } 23 | 24 | index++; 25 | } 26 | } 27 | 28 | if (count === 0) { 29 | return { 30 | count, 31 | mean: 0.0, 32 | variance: 0.0, 33 | stdDev: 0.0 34 | }; 35 | } 36 | 37 | const mean = sum / count; 38 | const variance = sumSquared / count - mean * mean; 39 | 40 | return { 41 | count, 42 | mean, 43 | variance, 44 | stdDev: Math.sqrt(variance) 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/imageTools/simpleTouchTool.js: -------------------------------------------------------------------------------- 1 | import EVENTS from '../events.js'; 2 | import { setToolOptions } from '../toolOptions.js'; 3 | 4 | export default function (touchStartCallback, toolType) { 5 | if (!toolType) { 6 | throw new Error('simpleTouchTool: toolType is required'); 7 | } 8 | 9 | let configuration = {}; 10 | 11 | return { 12 | activate (element, options = {}) { 13 | setToolOptions(toolType, element, options); 14 | 15 | element.removeEventListener(EVENTS.TOUCH_START, touchStartCallback); 16 | element.addEventListener(EVENTS.TOUCH_START, touchStartCallback); 17 | }, 18 | disable (element) { 19 | element.removeEventListener(EVENTS.TOUCH_START, touchStartCallback); 20 | }, 21 | enable (element) { 22 | element.removeEventListener(EVENTS.TOUCH_START, touchStartCallback); 23 | }, 24 | deactivate (element) { 25 | element.removeEventListener(EVENTS.TOUCH_START, touchStartCallback); 26 | }, 27 | getConfiguration () { 28 | return configuration; 29 | }, 30 | setConfiguration (config) { 31 | configuration = config; 32 | } 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Chris Hafey (chafey@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/manipulators/getHandleNearImagePoint.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | import pointInsideBoundingBox from '../util/pointInsideBoundingBox.js'; 3 | 4 | export default function (element, handles, coords, distanceThreshold) { 5 | let nearbyHandle; 6 | 7 | if (!handles) { 8 | return; 9 | } 10 | 11 | Object.keys(handles).forEach(function (name) { 12 | const handle = handles[name]; 13 | 14 | if (handle.hasOwnProperty('pointNearHandle')) { 15 | if (handle.pointNearHandle(element, handle, coords)) { 16 | nearbyHandle = handle; 17 | 18 | return; 19 | } 20 | } else if (handle.hasBoundingBox === true) { 21 | if (pointInsideBoundingBox(handle, coords)) { 22 | nearbyHandle = handle; 23 | 24 | return; 25 | } 26 | } else { 27 | const handleCanvas = external.cornerstone.pixelToCanvas(element, handle); 28 | const distance = external.cornerstoneMath.point.distance(handleCanvas, coords); 29 | 30 | if (distance <= distanceThreshold) { 31 | nearbyHandle = handle; 32 | 33 | return; 34 | } 35 | } 36 | }); 37 | 38 | return nearbyHandle; 39 | } 40 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Cornerstone Tools 2 | 3 | > Cornerstone Tools is a library built on top of cornerstone that provides a set of common tools needed in medical imaging to work with images and stacks of images. 4 | 5 | While this documentation grows, the [old GitHub wiki](https://github.com/cornerstonejs/cornerstoneTools/wiki) may be a better source. 6 | 7 | **[Release Notes](https://github.com/cornerstonejs/cornerstoneTools/releases)** 8 | 9 | - [Installation](installation.md) 10 | - Essentials 11 | - [Getting Started](essentials/getting-started.md) 12 | - [Input Sources](essentials/input-sources.md) 13 | - [Tool States](essentials/tool-states.md) 14 | - [Image Tools](essentials/image-tools.md) 15 | - [Stack Tools](essentials/stack-tools.md) 16 | - [Tool Data](essentials/tool-data.md) 17 | - Advanced 18 | - [Layered Image Stacks](advanced/layered-image-stacks.md) 19 | - Custom Tools 20 | - [Anatomy of a Tool](custom-tools/anatomy-of-a-tool.md) 21 | - [Simple Tools](custom-tools/simple-tools.md) 22 | - API Reference 23 | - [Generated API Docs](api.md) 24 | - Guides 25 | - [Migrating Major Versions](guides/migrating-major-versions.md) 26 | - [Contributing](contributing.md) 27 | -------------------------------------------------------------------------------- /src/util/drawLinkedTextBox.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | import drawTextBox from './drawTextBox.js'; 3 | import drawLink from './drawLink.js'; 4 | 5 | export default function (context, element, textBox, text, 6 | handles, textBoxAnchorPoints, color, lineWidth, xOffset, yCenter) { 7 | const cornerstone = external.cornerstone; 8 | 9 | // Convert the textbox Image coordinates into Canvas coordinates 10 | const textCoords = cornerstone.pixelToCanvas(element, textBox); 11 | 12 | if (xOffset) { 13 | textCoords.x += xOffset; 14 | } 15 | 16 | const options = { 17 | centering: { 18 | x: false, 19 | y: yCenter 20 | } 21 | }; 22 | 23 | // Draw the text box 24 | textBox.boundingBox = drawTextBox(context, text, textCoords.x, textCoords.y, color, options); 25 | if (textBox.hasMoved) { 26 | // Identify the possible anchor points for the tool -> text line 27 | const linkAnchorPoints = textBoxAnchorPoints(handles).map((h) => cornerstone.pixelToCanvas(element, h)); 28 | 29 | // Draw dashed link line between tool and text 30 | drawLink(linkAnchorPoints, textCoords, textBox.boundingBox, context, color, lineWidth); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/orientation/getOrientationString.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | import convertToVector3 from '../util/convertToVector3.js'; 3 | 4 | export default function (vector) { 5 | const vec3 = convertToVector3(vector); 6 | 7 | // Thanks to David Clunie 8 | // https://sites.google.com/site/dicomnotes/ 9 | 10 | let orientation = ''; 11 | const orientationX = vec3.x < 0 ? 'R' : 'L'; 12 | const orientationY = vec3.y < 0 ? 'A' : 'P'; 13 | const orientationZ = vec3.z < 0 ? 'F' : 'H'; 14 | 15 | // Should probably make this a function vector3.abs 16 | const abs = new external.cornerstoneMath.Vector3(Math.abs(vec3.x), Math.abs(vec3.y), Math.abs(vec3.z)); 17 | 18 | for (let i = 0; i < 3; i++) { 19 | if (abs.x > 0.0001 && abs.x > abs.y && abs.x > abs.z) { 20 | orientation += orientationX; 21 | abs.x = 0; 22 | } else if (abs.y > 0.0001 && abs.y > abs.x && abs.y > abs.z) { 23 | orientation += orientationY; 24 | abs.y = 0; 25 | } else if (abs.z > 0.0001 && abs.z > abs.x && abs.z > abs.y) { 26 | orientation += orientationZ; 27 | abs.z = 0; 28 | } else { 29 | break; 30 | } 31 | } 32 | 33 | return orientation; 34 | } 35 | -------------------------------------------------------------------------------- /src/paintingTools/drawBrush.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | import { draw } from '../util/drawing.js'; 3 | 4 | function drawBrushPixels (pointerArray, storedPixels, brushPixelValue, columns) { 5 | const getPixelIndex = (x, y) => (y * columns) + x; 6 | 7 | pointerArray.forEach((point) => { 8 | const spIndex = getPixelIndex(point[0], point[1]); 9 | 10 | storedPixels[spIndex] = brushPixelValue; 11 | }); 12 | } 13 | 14 | function drawBrushOnCanvas (pointerArray, context, color, element) { 15 | const canvasPtTL = external.cornerstone.pixelToCanvas(element, { x: 0, 16 | y: 0 }); 17 | const canvasPtBR = external.cornerstone.pixelToCanvas(element, { x: 1, 18 | y: 1 }); 19 | const sizeX = canvasPtBR.x - canvasPtTL.x; 20 | const sizeY = canvasPtBR.y - canvasPtTL.y; 21 | 22 | draw(context, (context) => { 23 | context.fillStyle = color; 24 | 25 | pointerArray.forEach((point) => { 26 | const canvasPt = external.cornerstone.pixelToCanvas(element, { 27 | x: point[0], 28 | y: point[1] 29 | }); 30 | 31 | context.fillRect(canvasPt.x, canvasPt.y, sizeX, sizeY); 32 | }); 33 | }); 34 | } 35 | 36 | export { drawBrushPixels, drawBrushOnCanvas }; 37 | -------------------------------------------------------------------------------- /src/measurementManager/measurementManager.js: -------------------------------------------------------------------------------- 1 | import EVENTS from '../events.js'; 2 | import external from '../externalModules.js'; 3 | import triggerEvent from '../util/triggerEvent.js'; 4 | 5 | // This object manages a collection of measurements 6 | function MeasurementManager () { 7 | const cornerstone = external.cornerstone; 8 | const that = this; 9 | 10 | that.measurements = []; 11 | 12 | // Adds an element as both a source and a target 13 | this.add = function (measurement) { 14 | const index = that.measurements.push(measurement); 15 | // Fire event 16 | const eventDetail = { 17 | index, 18 | measurement 19 | }; 20 | 21 | triggerEvent(cornerstone.events, EVENTS.MEASUREMENT_ADDED, eventDetail); 22 | }; 23 | 24 | this.remove = function (index) { 25 | const measurement = that.measurements[index]; 26 | 27 | that.measurements.splice(index, 1); 28 | // Fire event 29 | const eventDetail = { 30 | index, 31 | measurement 32 | }; 33 | 34 | triggerEvent(cornerstone.events, EVENTS.MEASUREMENT_REMOVED, eventDetail); 35 | }; 36 | 37 | } 38 | 39 | // Module/private exports 40 | const manager = new MeasurementManager(); 41 | 42 | export default manager; 43 | -------------------------------------------------------------------------------- /src/synchronization/panZoomSynchronizer.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | 3 | // This function synchronizes the target zoom and pan to match the source 4 | export default function (synchronizer, sourceElement, targetElement) { 5 | 6 | // Ignore the case where the source and target are the same enabled element 7 | if (targetElement === sourceElement) { 8 | return; 9 | } 10 | 11 | const cornerstone = external.cornerstone; 12 | // Get the source and target viewports 13 | const sourceViewport = cornerstone.getViewport(sourceElement); 14 | const targetViewport = cornerstone.getViewport(targetElement); 15 | 16 | // Do nothing if the scale and translation are the same 17 | if (targetViewport.scale === sourceViewport.scale && targetViewport.translation.x === sourceViewport.translation.x && targetViewport.translation.y === sourceViewport.translation.y) { 18 | return; 19 | } 20 | 21 | // Scale and/or translation are different, sync them 22 | targetViewport.scale = sourceViewport.scale; 23 | targetViewport.translation.x = sourceViewport.translation.x; 24 | targetViewport.translation.y = sourceViewport.translation.y; 25 | synchronizer.setViewport(targetElement, targetViewport); 26 | } 27 | -------------------------------------------------------------------------------- /src/synchronization/wwwcSynchronizer.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | 3 | // This function synchronizes the target element ww/wc to match the source element 4 | export default function (synchronizer, sourceElement, targetElement) { 5 | 6 | // Ignore the case where the source and target are the same enabled element 7 | if (targetElement === sourceElement) { 8 | return; 9 | } 10 | 11 | const cornerstone = external.cornerstone; 12 | // Get the source and target viewports 13 | const sourceViewport = cornerstone.getViewport(sourceElement); 14 | const targetViewport = cornerstone.getViewport(targetElement); 15 | 16 | // Do nothing if the ww/wc already match 17 | if (targetViewport.voi.windowWidth === sourceViewport.voi.windowWidth && targetViewport.voi.windowCenter === sourceViewport.voi.windowCenter && targetViewport.invert === sourceViewport.invert) { 18 | return; 19 | } 20 | 21 | // Www/wc are different, sync them 22 | targetViewport.voi.windowWidth = sourceViewport.voi.windowWidth; 23 | targetViewport.voi.windowCenter = sourceViewport.voi.windowCenter; 24 | targetViewport.invert = sourceViewport.invert; 25 | synchronizer.setViewport(targetElement, targetViewport); 26 | } 27 | -------------------------------------------------------------------------------- /src/util/getRGBPixels.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | 3 | export default function (element, x, y, width, height) { 4 | if (!element) { 5 | throw new Error('getRGBPixels: parameter element must not be undefined'); 6 | } 7 | 8 | x = Math.round(x); 9 | y = Math.round(y); 10 | const enabledElement = external.cornerstone.getEnabledElement(element); 11 | const storedPixelData = []; 12 | let index = 0; 13 | const pixelData = enabledElement.image.getPixelData(); 14 | let spIndex, 15 | row, 16 | column; 17 | 18 | if (enabledElement.image.color) { 19 | for (row = 0; row < height; row++) { 20 | for (column = 0; column < width; column++) { 21 | spIndex = (((row + y) * enabledElement.image.columns) + (column + x)) * 4; 22 | const red = pixelData[spIndex]; 23 | const green = pixelData[spIndex + 1]; 24 | const blue = pixelData[spIndex + 2]; 25 | const alpha = pixelData[spIndex + 3]; 26 | 27 | storedPixelData[index++] = red; 28 | storedPixelData[index++] = green; 29 | storedPixelData[index++] = blue; 30 | storedPixelData[index++] = alpha; 31 | } 32 | } 33 | } 34 | 35 | return storedPixelData; 36 | } 37 | -------------------------------------------------------------------------------- /src/imageTools/simpleMouseButtonTool.js: -------------------------------------------------------------------------------- 1 | import EVENTS from '../events.js'; 2 | import { setToolOptions } from '../toolOptions.js'; 3 | 4 | export default function (mouseDownCallback, toolType) { 5 | if (!toolType) { 6 | throw new Error('simpleMouseButtonTool: toolType is required'); 7 | } 8 | 9 | let configuration = {}; 10 | 11 | return { 12 | activate (element, mouseButtonMask, options = {}) { 13 | options.mouseButtonMask = mouseButtonMask; 14 | setToolOptions(toolType, element, options); 15 | 16 | element.removeEventListener(EVENTS.MOUSE_DOWN_ACTIVATE, mouseDownCallback); 17 | element.addEventListener(EVENTS.MOUSE_DOWN_ACTIVATE, mouseDownCallback); 18 | }, 19 | disable (element) { 20 | element.removeEventListener(EVENTS.MOUSE_DOWN_ACTIVATE, mouseDownCallback); 21 | }, 22 | enable (element) { 23 | element.removeEventListener(EVENTS.MOUSE_DOWN_ACTIVATE, mouseDownCallback); 24 | }, 25 | deactivate (element) { 26 | element.removeEventListener(EVENTS.MOUSE_DOWN_ACTIVATE, mouseDownCallback); 27 | }, 28 | getConfiguration () { 29 | return configuration; 30 | }, 31 | setConfiguration (config) { 32 | configuration = config; 33 | } 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /config/webpack/webpack-base.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const rootPath = process.cwd(); 3 | const context = path.join(rootPath, "src"); 4 | const outputPath = path.join(rootPath, 'dist'); 5 | const bannerPlugin = require('./plugins/banner'); 6 | 7 | module.exports = { 8 | mode: "development", 9 | context: context, 10 | entry: { 11 | cornerstoneTools: './index.js' 12 | }, 13 | target: 'web', 14 | output: { 15 | filename: '[name].js', 16 | library: '[name]', 17 | libraryTarget: 'umd', 18 | path: outputPath, 19 | umdNamedDefine: true 20 | }, 21 | devtool: 'source-map', 22 | externals: { 23 | 'cornerstone-math': { 24 | commonjs: "cornerstone-math", 25 | commonjs2: "cornerstone-math", 26 | amd: "cornerstone-math", 27 | root: 'cornerstoneMath' 28 | } 29 | }, 30 | module: { 31 | rules: [{ 32 | enforce: 'pre', 33 | test: /\.js$/, 34 | exclude: /(node_modules|test)/, 35 | loader: 'eslint-loader', 36 | options: { 37 | failOnError: false 38 | } 39 | }, { 40 | test: /\.js$/, 41 | exclude: /(node_modules)/, 42 | use: [{ 43 | loader: 'babel-loader' 44 | }] 45 | }] 46 | }, 47 | plugins: [ 48 | bannerPlugin() 49 | ] 50 | }; 51 | -------------------------------------------------------------------------------- /src/imageTools/eraser.js: -------------------------------------------------------------------------------- 1 | import * as cornerstoneTools from '../index.js'; 2 | import external from '../externalModules.js'; 3 | import simpleMouseButtonTool from './simpleMouseButtonTool.js'; 4 | import simpleTouchTool from './simpleTouchTool.js'; 5 | 6 | const toolType = 'eraser'; 7 | 8 | function deleteNearbyMeasurement (mouseEventData) { 9 | const coords = mouseEventData.detail.currentPoints.canvas; 10 | const element = mouseEventData.detail.element; 11 | 12 | Object.keys(cornerstoneTools).forEach(function (toolName) { 13 | const tool = cornerstoneTools[toolName]; // eslint-disable-line import/namespace 14 | const toolState = cornerstoneTools.getToolState(element, toolName); 15 | 16 | if (toolState) { 17 | toolState.data.forEach(function (data) { 18 | if(typeof tool.pointNearTool === 'function' && 19 | tool.pointNearTool(element, data, coords)) { 20 | cornerstoneTools.removeToolState(element, toolName, data); 21 | external.cornerstone.updateImage(element); 22 | } 23 | }); 24 | } 25 | }); 26 | } 27 | 28 | const eraser = simpleMouseButtonTool(deleteNearbyMeasurement, toolType); 29 | const eraserTouch = simpleTouchTool(deleteNearbyMeasurement, toolType); 30 | 31 | export { 32 | eraser, 33 | eraserTouch 34 | }; 35 | -------------------------------------------------------------------------------- /src/imageTools/saveAs.js: -------------------------------------------------------------------------------- 1 | export default function saveAs (element, filename, mimetype = 'image/png') { 2 | // Setting the default value for mimetype to image/png 3 | const canvas = element.querySelector('canvas'); 4 | 5 | // If we are using IE, use canvas.msToBlob 6 | if (canvas.msToBlob) { 7 | const blob = canvas.msToBlob(); 8 | 9 | return window.navigator.msSaveBlob(blob, filename); 10 | } 11 | 12 | // Thanks to Ken Fyrstenber 13 | // http://stackoverflow.com/questions/18480474/how-to-save-an-image-from-canvas 14 | const lnk = document.createElement('a'); 15 | 16 | // The key here is to set the download attribute of the a tag 17 | lnk.download = filename; 18 | 19 | // Convert canvas content to data-uri for link. When download 20 | // Attribute is set the content pointed to by link will be 21 | // Pushed as 'download' in HTML5 capable browsers 22 | lnk.href = canvas.toDataURL(mimetype, 1); 23 | 24 | // Create a 'fake' click-event to trigger the download 25 | if (document.createEvent) { 26 | const e = document.createEvent('MouseEvents'); 27 | 28 | e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); 29 | 30 | lnk.dispatchEvent(e); 31 | } else if (lnk.fireEvent) { 32 | lnk.fireEvent('onclick'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/util/getLuminance.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | 3 | export default function (element, x, y, width, height) { 4 | if (!element) { 5 | throw new Error('getLuminance: parameter element must not be undefined'); 6 | } 7 | 8 | x = Math.round(x); 9 | y = Math.round(y); 10 | const enabledElement = external.cornerstone.getEnabledElement(element); 11 | const image = enabledElement.image; 12 | const luminance = []; 13 | let index = 0; 14 | const pixelData = image.getPixelData(); 15 | let spIndex, 16 | row, 17 | column; 18 | 19 | if (image.color) { 20 | for (row = 0; row < height; row++) { 21 | for (column = 0; column < width; column++) { 22 | spIndex = (((row + y) * image.columns) + (column + x)) * 4; 23 | const red = pixelData[spIndex]; 24 | const green = pixelData[spIndex + 1]; 25 | const blue = pixelData[spIndex + 2]; 26 | 27 | luminance[index++] = 0.2126 * red + 0.7152 * green + 0.0722 * blue; 28 | } 29 | } 30 | } else { 31 | for (row = 0; row < height; row++) { 32 | for (column = 0; column < width; column++) { 33 | spIndex = ((row + y) * image.columns) + (column + x); 34 | luminance[index++] = pixelData[spIndex] * image.slope + image.intercept; 35 | } 36 | } 37 | } 38 | 39 | return luminance; 40 | } 41 | -------------------------------------------------------------------------------- /test/util/freehand/freeHandArea_test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import freeHandArea from '../../../src/util/freehand/freeHandArea.js'; 3 | import { FreehandHandleData } from '../../../src/util/freehand/FreehandHandleData.js' 4 | 5 | 6 | describe('#freeHandArea', function() { 7 | let dataHandles; 8 | 9 | beforeEach(() => { 10 | // Simple square 11 | const handle0 = new FreehandHandleData({ 12 | x: 0.0, 13 | y: 0.0 14 | }); 15 | const handle1 = new FreehandHandleData({ 16 | x: 0.0, 17 | y: 1.0 18 | }); 19 | const handle2 = new FreehandHandleData({ 20 | x: 1.0, 21 | y: 1.0 22 | }); 23 | const handle3 = new FreehandHandleData({ 24 | x: 1.0, 25 | y: 0.0 26 | }); 27 | 28 | dataHandles = [ 29 | handle0, 30 | handle1, 31 | handle2, 32 | handle3 33 | ]; 34 | 35 | }); 36 | 37 | it('should return the area enclosed in dataHandles', function () { 38 | const area = freeHandArea(dataHandles, false); 39 | 40 | expect(area).to.not.be.undefined; 41 | expect(area).to.be.equal(1.0); 42 | }); 43 | 44 | it('should scale if the parameter is given.', function () { 45 | const area = freeHandArea(dataHandles, 10.0); 46 | 47 | expect(area).to.not.be.undefined; 48 | expect(area).to.be.equal(10.0); 49 | }); 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /src/util/drawArrow.js: -------------------------------------------------------------------------------- 1 | import { path } from './drawing.js'; 2 | 3 | export default function (context, start, end, color, lineWidth) { 4 | // Variables to be used when creating the arrow 5 | const headLength = 10; 6 | 7 | const angle = Math.atan2(end.y - start.y, end.x - start.x); 8 | 9 | // Starting path of the arrow from the start square to the end square and drawing the stroke 10 | path(context, { color, 11 | lineWidth }, (context) => { 12 | context.moveTo(start.x, start.y); 13 | context.lineTo(end.x, end.y); 14 | }); 15 | 16 | const fillStyle = color; 17 | 18 | path(context, { color, 19 | lineWidth, 20 | fillStyle }, (context) => { 21 | // Starting a new path from the head of the arrow to one of the sides of the point 22 | context.moveTo(end.x, end.y); 23 | context.lineTo(end.x - headLength * Math.cos(angle - Math.PI / 7), end.y - headLength * Math.sin(angle - Math.PI / 7)); 24 | 25 | // Path from the side point of the arrow, to the other side point 26 | context.lineTo(end.x - headLength * Math.cos(angle + Math.PI / 7), end.y - headLength * Math.sin(angle + Math.PI / 7)); 27 | 28 | // Path from the side point back to the tip of the arrow, and then again to the opposite side point 29 | context.lineTo(end.x, end.y); 30 | context.lineTo(end.x - headLength * Math.cos(angle - Math.PI / 7), end.y - headLength * Math.sin(angle - Math.PI / 7)); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /src/manipulators/drawHandles.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | import toolStyle from '../stateManagement/toolStyle.js'; 3 | import { path } from '../util/drawing.js'; 4 | 5 | const defaultHandleRadius = 6; 6 | 7 | export default function (context, renderData, handles, color, options) { 8 | context.strokeStyle = color; 9 | 10 | Object.keys(handles).forEach(function (name) { 11 | const handle = handles[name]; 12 | 13 | if (handle.drawnIndependently === true) { 14 | return; 15 | } 16 | 17 | if (options && options.drawHandlesIfActive === true && !handle.active) { 18 | return; 19 | } 20 | 21 | const lineWidth = handle.active ? toolStyle.getActiveWidth() : toolStyle.getToolWidth(); 22 | const fillStyle = options && options.fill; 23 | 24 | path(context, { lineWidth, 25 | fillStyle }, (context) => { 26 | const handleCanvasCoords = external.cornerstone.pixelToCanvas(renderData.element, handle); 27 | 28 | const handleRadius = getHandleRadius(options); 29 | 30 | context.arc(handleCanvasCoords.x, handleCanvasCoords.y, handleRadius, 0, 2 * Math.PI); 31 | }); 32 | }); 33 | } 34 | 35 | function getHandleRadius (options) { 36 | let handleRadius; 37 | 38 | if (options && options.handleRadius) { 39 | handleRadius = options.handleRadius; 40 | } else { 41 | handleRadius = defaultHandleRadius; 42 | } 43 | 44 | return handleRadius; 45 | } 46 | -------------------------------------------------------------------------------- /test/requestPool/requestPoolManager_test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import requestPoolManager from '../../src/requestPool/requestPoolManager.js'; 3 | 4 | describe('clearRequestStack with an invalid type', function () { 5 | beforeEach(() => { 6 | }); 7 | 8 | it('should throw an error when attempting to clear the request stack with an invalid type', function () { 9 | const nonExistantType = 'NotAnExistingType'; 10 | expect(() => requestPoolManager.clearRequestStack(nonExistantType)).to.throw(Error, 'Request type must be one of interaction, thumbnail, or prefetch'); 11 | }); 12 | }); 13 | 14 | describe('addRequest', function () { 15 | beforeEach(() => { 16 | }); 17 | 18 | it('should add the requests to the interaction stack, in the correct order', function () { 19 | requestPoolManager.addRequest({}, 'request1', 'interaction', false, function doneCallback() {}, function failCallback() {}); 20 | requestPoolManager.addRequest({}, 'request2', 'interaction', false, function doneCallback() {}, function failCallback() {}); 21 | requestPoolManager.addRequest({}, 'request3', 'interaction', false, function doneCallback() {}, function failCallback() {}, true); 22 | 23 | const interactionStack = requestPoolManager.getRequestPool()['interaction']; 24 | 25 | expect(interactionStack[0].imageId).to.equal('request3'); 26 | expect(interactionStack[1].imageId).to.equal('request1'); 27 | expect(interactionStack[2].imageId).to.equal('request2'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/util/drawLink.js: -------------------------------------------------------------------------------- 1 | import external from '../externalModules.js'; 2 | import { path } from './drawing.js'; 3 | 4 | export default function (linkAnchorPoints, refPoint, boundingBox, context, color, lineWidth) { 5 | // Draw a link from "the closest anchor point to refPoint" to "the nearest midpoint on the bounding box". 6 | 7 | // Find the closest anchor point to RefPoint 8 | const start = (linkAnchorPoints.length > 0) 9 | ? external.cornerstoneMath.point.findClosestPoint(linkAnchorPoints, refPoint) 10 | : refPoint; 11 | 12 | // Calculate the midpoints of the bounding box 13 | const boundingBoxPoints = [{ 14 | x: boundingBox.left + boundingBox.width / 2, 15 | y: boundingBox.top 16 | }, { 17 | x: boundingBox.left, 18 | y: boundingBox.top + boundingBox.height / 2 19 | }, { 20 | x: boundingBox.left + boundingBox.width / 2, 21 | y: boundingBox.top + boundingBox.height 22 | }, { 23 | x: boundingBox.left + boundingBox.width, 24 | y: boundingBox.top + boundingBox.height / 2 25 | } 26 | ]; 27 | 28 | // Calculate the link endpoint by identifying which midpoint of the bounding box 29 | // Is closest to the start point. 30 | const end = external.cornerstoneMath.point.findClosestPoint(boundingBoxPoints, start); 31 | 32 | // Finally we draw the dashed linking line 33 | const lineDash = [2, 3]; 34 | 35 | path(context, { color, 36 | lineWidth, 37 | lineDash }, (context) => { 38 | context.moveTo(start.x, start.y); 39 | context.lineTo(end.x, end.y); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /src/imageTools/touchDragTool.js: -------------------------------------------------------------------------------- 1 | import EVENTS from '../events.js'; 2 | import { setToolOptions } from '../toolOptions.js'; 3 | 4 | export default function (touchDragCallback, toolType, options) { 5 | const events = [EVENTS.TOUCH_DRAG]; 6 | 7 | if (options && options.fireOnTouchStart === true) { 8 | events.push(EVENTS.TOUCH_START); 9 | } 10 | 11 | return { 12 | activate (element) { 13 | if (options && options.eventData) { 14 | setToolOptions(toolType, element, options.eventData); 15 | } 16 | 17 | events.forEach((eventType) => { 18 | element.removeEventListener(eventType, touchDragCallback); 19 | element.addEventListener(eventType, touchDragCallback); 20 | }); 21 | 22 | if (options && options.activateCallback) { 23 | options.activateCallback(element); 24 | } 25 | }, 26 | disable (element) { 27 | events.forEach((eventType) => { 28 | element.removeEventListener(eventType, touchDragCallback); 29 | }); 30 | 31 | if (options && options.disableCallback) { 32 | options.disableCallback(element); 33 | } 34 | }, 35 | enable (element) { 36 | events.forEach((eventType) => { 37 | element.removeEventListener(eventType, touchDragCallback); 38 | }); 39 | 40 | if (options && options.enableCallback) { 41 | options.enableCallback(element); 42 | } 43 | }, 44 | deactivate (element) { 45 | events.forEach((eventType) => { 46 | element.removeEventListener(eventType, touchDragCallback); 47 | }); 48 | 49 | if (options && options.deactivateCallback) { 50 | options.deactivateCallback(element); 51 | } 52 | } 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /src/imageTools/multiTouchDragTool.js: -------------------------------------------------------------------------------- 1 | import EVENTS from '../events.js'; 2 | 3 | export default function (touchDragCallback, options) { 4 | let configuration = {}; 5 | const events = [EVENTS.MULTI_TOUCH_DRAG]; 6 | 7 | if (options && options.fireOnTouchStart === true) { 8 | events.push(EVENTS.MULTI_TOUCH_START); 9 | } 10 | 11 | return { 12 | activate (element) { 13 | events.forEach((eventType) => { 14 | element.removeEventListener(eventType, touchDragCallback); 15 | element.addEventListener(eventType, touchDragCallback); 16 | }); 17 | 18 | if (options && options.activateCallback) { 19 | options.activateCallback(element); 20 | } 21 | }, 22 | disable (element) { 23 | events.forEach((eventType) => { 24 | element.removeEventListener(eventType, touchDragCallback); 25 | }); 26 | 27 | if (options && options.disableCallback) { 28 | options.disableCallback(element); 29 | } 30 | }, 31 | enable (element) { 32 | events.forEach((eventType) => { 33 | element.removeEventListener(eventType, touchDragCallback); 34 | }); 35 | 36 | if (options && options.enableCallback) { 37 | options.enableCallback(element); 38 | } 39 | }, 40 | deactivate (element) { 41 | events.forEach((eventType) => { 42 | element.removeEventListener(eventType, touchDragCallback); 43 | }); 44 | 45 | if (options && options.deactivateCallback) { 46 | options.deactivateCallback(element); 47 | } 48 | }, 49 | getConfiguration () { 50 | return configuration; 51 | }, 52 | setConfiguration (config) { 53 | configuration = config; 54 | } 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /config/karma/karma-base.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpackConfig = require('../webpack'); 3 | 4 | /* eslint no-process-env:0 */ 5 | process.env.CHROME_BIN = require('puppeteer').executablePath(); 6 | 7 | // Deleting output.library to avoid "Uncaught SyntaxError: Unexpected token /" error 8 | // when running testes (var test/foo_test.js = ...) 9 | delete webpackConfig.output.library; 10 | 11 | // Code coverage 12 | webpackConfig.module.rules.push({ 13 | test: /\.js$/, 14 | include: path.resolve('./src/'), 15 | loader: 'istanbul-instrumenter-loader', 16 | query: { 17 | esModules: true 18 | } 19 | }); 20 | 21 | module.exports = { 22 | basePath: '../../', 23 | frameworks: ['mocha'], 24 | reporters: ['progress', 'coverage'], 25 | files: [ 26 | 'node_modules/cornerstone-core/dist/cornerstone.js', 27 | 'node_modules/cornerstone-math/dist/cornerstoneMath.js', 28 | 'node_modules/hammerjs/hammer.js', 29 | 'test/**/*_test.js' 30 | ], 31 | 32 | plugins: [ 33 | 'karma-webpack', 34 | 'karma-mocha', 35 | 'karma-chrome-launcher', 36 | 'karma-firefox-launcher', 37 | 'karma-coverage' 38 | ], 39 | 40 | preprocessors: { 41 | 'src/**/*.js': ['webpack'], 42 | 'test/**/*_test.js': ['webpack'] 43 | }, 44 | 45 | webpack: webpackConfig, 46 | 47 | webpackMiddleware: { 48 | noInfo: false, 49 | stats: { 50 | chunks: false, 51 | timings: false, 52 | errorDetails: true 53 | } 54 | }, 55 | 56 | coverageReporter: { 57 | dir: './coverage', 58 | reporters: [ 59 | {type: 'html', subdir: 'html'}, 60 | {type: 'lcov', subdir: '.'}, 61 | {type: 'text', subdir: '.', file: 'text.txt'}, 62 | {type: 'text-summary', subdir: '.', file: 'text-summary.txt'} 63 | ] 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ### Direct Download / CDN 4 | 5 | [https://unpkg.com/cornerstone-tools](https://unpkg.com/cornerstone-tools) 6 | 7 | 8 | [Unpkg.com](https://unpkg.com) provides NPM-based CDN links. The above link will always point to the latest release on NPM. You can also use a specific version/tag via URLs like `https://unpkg.com/cornerstone-tools@2.0.0`. 9 | 10 | 11 | Include `cornerstoneTools` after Cornerstone and it will install itself automatically: 12 | 13 | ``` html 14 | 15 | 16 | 17 | ``` 18 | 19 | ### NPM 20 | 21 | ``` bash 22 | npm install cornerstone-tools --save 23 | ``` 24 | 25 | When used with a module system, you can import `cornerstoneTools` like this: 26 | 27 | ``` js 28 | // External Dependencies 29 | import Hammer from 'hammerjs' 30 | 31 | // Cornerstone Libraries 32 | import * as cornerstone from 'cornerstone-core' 33 | import * as cornerstoneMath from 'cornerstone-math' 34 | import * as cornerstoneTools from 'cornerstone-tools' 35 | 36 | // Specify external dependencies 37 | cornerstoneTools.external.Hammer = Hammer 38 | cornerstoneTools.external.cornerstone = cornerstone 39 | cornerstoneTools.external.cornerstoneMath = cornerstoneMath 40 | ``` 41 | 42 | You don't need to do this when using global script tags. 43 | 44 | ### Dev Build 45 | 46 | You will have to clone directly from GitHub and build `cornerstoneTools` yourself if you want to use the latest dev build. 47 | 48 | ``` bash 49 | git clone https://github.com/cornerstonejs/cornerstoneTools.git node_modules/cornerstoneTools 50 | cd node_modules/cornerstoneTools 51 | npm install 52 | npm run build 53 | ``` 54 | -------------------------------------------------------------------------------- /examples/exampleMetaDataProvider.js: -------------------------------------------------------------------------------- 1 | (function metaDataProvider(cornerstone) { 2 | 3 | "use strict"; 4 | 5 | function metaDataProvider(type, imageId) { 6 | if(type === 'imagePlaneModule') { 7 | if (imageId === 'example://1') { 8 | return { 9 | frameOfReferenceUID: '1.2.3.4.5', 10 | rows: 256, 11 | columns: 256, 12 | rowCosines: [0, 1, 0], 13 | columnCosines: [0, 0, -1], 14 | imagePositionPatient: [-9.4, -92.5, 98], 15 | columnPixelSpacing: 0.78, 16 | rowPixelSpacing: 0.78 17 | }; 18 | } else if (imageId === 'example://2') { 19 | return { 20 | frameOfReferenceUID: '1.2.3.4.5', 21 | rows: 256, 22 | columns: 256, 23 | rowCosines: [0, 1, 0], 24 | columnCosines: [0, 0, -1], 25 | imagePositionPatient: [-7, -92.5, 98], 26 | columnPixelSpacing: 0.78, 27 | rowPixelSpacing: 0.78 28 | }; 29 | } else if (imageId === 'example://3') { 30 | return { 31 | frameOfReferenceUID: '1.2.3.4.5', 32 | rows: 256, 33 | columns: 256, 34 | rowCosines: [1, 0, 0], 35 | columnCosines: [0, 0, -1], 36 | imagePositionPatient: [-100, -13, 98], 37 | columnPixelSpacing: 0.78, 38 | rowPixelSpacing: 0.78 39 | }; 40 | } 41 | } 42 | } 43 | 44 | cornerstone.metaData.addProvider(metaDataProvider); 45 | 46 | })(cornerstone); -------------------------------------------------------------------------------- /src/util/freehand/keysHeld.js: -------------------------------------------------------------------------------- 1 | import external from '../../externalModules.js'; 2 | import { freehand } from '../../imageTools/freehand.js'; 3 | 4 | /** 5 | * Triggers held down buttons such that we can update the image on CTRL click 6 | * to show all points, for example. 7 | */ 8 | 9 | /** 10 | * An enum containing textual representations of keyCodes. 11 | * @type {Object} 12 | */ 13 | const keyCodes = { 14 | SHIFT: 16, 15 | CTRL: 17, 16 | ALT: 18 17 | }; 18 | 19 | /** 20 | * Event handler for KEY_DOWN event. 21 | * 22 | * @param {Object} e - The event. 23 | */ 24 | export function keyDownCallback (e) { 25 | const eventData = e.detail; 26 | const config = freehand.getConfiguration(); 27 | const keyCode = eventData.keyCode; 28 | let imageNeedsUpdate = false; 29 | 30 | if (keyCode === keyCodes.CTRL) { 31 | config.keyDown.ctrl = true; 32 | imageNeedsUpdate = true; 33 | } 34 | 35 | if(keyCode === keyCodes.SHIFT) { 36 | config.keyDown.shift = true; 37 | } 38 | 39 | if(keyCode === keyCodes.ALT) { 40 | config.keyDown.alt = true; 41 | } 42 | 43 | if (imageNeedsUpdate) { 44 | // Force onImageRendered to fire 45 | external.cornerstone.updateImage(eventData.element); 46 | } 47 | } 48 | 49 | /** 50 | * Event handler for KEY_UP event. 51 | * 52 | * @param {Object} e - The event. 53 | */ 54 | export function keyUpCallback (e) { 55 | const eventData = e.detail; 56 | const config = freehand.getConfiguration(); 57 | const keyCode = eventData.keyCode; 58 | let imageNeedsUpdate = false; 59 | 60 | if (keyCode === keyCodes.CTRL) { 61 | config.keyDown.ctrl = false; 62 | imageNeedsUpdate = true; 63 | } 64 | 65 | if(keyCode === keyCodes.SHIFT) { 66 | config.keyDown.shift = false; 67 | } 68 | 69 | if(keyCode === keyCodes.ALT) { 70 | config.keyDown.alt = false; 71 | } 72 | 73 | if (imageNeedsUpdate) { 74 | // Force onImageRendered to fire 75 | external.cornerstone.updateImage(eventData.element); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/manipulators/moveHandle.js: -------------------------------------------------------------------------------- 1 | import EVENTS from '../events.js'; 2 | import external from '../externalModules.js'; 3 | import triggerEvent from '../util/triggerEvent.js'; 4 | import { clipToBox } from '../util/clip.js'; 5 | 6 | export default function (mouseEventData, toolType, data, handle, doneMovingCallback, preventHandleOutsideImage) { 7 | const cornerstone = external.cornerstone; 8 | const element = mouseEventData.element; 9 | const distanceFromTool = { 10 | x: handle.x - mouseEventData.currentPoints.image.x, 11 | y: handle.y - mouseEventData.currentPoints.image.y 12 | }; 13 | 14 | function mouseDragCallback (e) { 15 | const eventData = e.detail; 16 | 17 | if (handle.hasMoved === false) { 18 | handle.hasMoved = true; 19 | } 20 | 21 | handle.active = true; 22 | handle.x = eventData.currentPoints.image.x + distanceFromTool.x; 23 | handle.y = eventData.currentPoints.image.y + distanceFromTool.y; 24 | 25 | if (preventHandleOutsideImage) { 26 | clipToBox(handle, eventData.image); 27 | } 28 | 29 | cornerstone.updateImage(element); 30 | 31 | const eventType = EVENTS.MEASUREMENT_MODIFIED; 32 | const modifiedEventData = { 33 | toolType, 34 | element, 35 | measurementData: data 36 | }; 37 | 38 | triggerEvent(element, eventType, modifiedEventData); 39 | } 40 | 41 | element.addEventListener(EVENTS.MOUSE_DRAG, mouseDragCallback); 42 | 43 | function mouseUpCallback () { 44 | handle.active = false; 45 | element.removeEventListener(EVENTS.MOUSE_DRAG, mouseDragCallback); 46 | element.removeEventListener(EVENTS.MOUSE_UP, mouseUpCallback); 47 | element.removeEventListener(EVENTS.MOUSE_CLICK, mouseUpCallback); 48 | cornerstone.updateImage(element); 49 | 50 | if (typeof doneMovingCallback === 'function') { 51 | doneMovingCallback(); 52 | } 53 | } 54 | 55 | element.addEventListener(EVENTS.MOUSE_UP, mouseUpCallback); 56 | element.addEventListener(EVENTS.MOUSE_CLICK, mouseUpCallback); 57 | } 58 | -------------------------------------------------------------------------------- /src/events.js: -------------------------------------------------------------------------------- 1 | const EVENTS = { 2 | // Events from Cornerstone Core 3 | IMAGE_RENDERED: 'cornerstoneimagerendered', 4 | NEW_IMAGE: 'cornerstonenewimage', 5 | IMAGE_CACHE_PROMISE_REMOVED: 'cornerstoneimagecachepromiseremoved', 6 | ELEMENT_DISABLED: 'cornerstoneelementdisabled', 7 | 8 | // Mouse events 9 | MOUSE_DOWN: 'cornerstonetoolsmousedown', 10 | MOUSE_UP: 'cornerstonetoolsmouseup', 11 | MOUSE_DOWN_ACTIVATE: 'cornerstonetoolsmousedownactivate', 12 | MOUSE_DRAG: 'cornerstonetoolsmousedrag', 13 | MOUSE_MOVE: 'cornerstonetoolsmousemove', 14 | MOUSE_CLICK: 'cornerstonetoolsmouseclick', 15 | MOUSE_DOUBLE_CLICK: 'cornerstonetoolsmousedoubleclick', 16 | MOUSE_WHEEL: 'cornerstonetoolsmousewheel', 17 | 18 | // Touch events 19 | TOUCH_START: 'cornerstonetoolstouchstart', 20 | TOUCH_START_ACTIVE: 'cornerstonetoolstouchstartactive', 21 | TOUCH_END: 'cornerstonetoolstouchend', 22 | TOUCH_DRAG: 'cornerstonetoolstouchdrag', 23 | TOUCH_DRAG_END: 'cornerstonetoolstouchdragend', 24 | TOUCH_PINCH: 'cornerstonetoolstouchpinch', 25 | TOUCH_ROTATE: 'cornerstonetoolstouchrotate', 26 | TOUCH_PRESS: 'cornerstonetoolstouchpress', 27 | TAP: 'cornerstonetoolstap', 28 | DOUBLE_TAP: 'cornerstonetoolsdoubletap', 29 | MULTI_TOUCH_START: 'cornerstonetoolsmultitouchstart', 30 | MULTI_TOUCH_START_ACTIVE: 'cornerstonetoolsmultitouchstartactive', 31 | MULTI_TOUCH_DRAG: 'cornerstonetoolsmultitouchdrag', 32 | 33 | // Keyboard events 34 | KEY_DOWN: 'cornerstonetoolskeydown', 35 | KEY_UP: 'cornerstonetoolskeyup', 36 | KEY_PRESS: 'cornerstonetoolskeypress', 37 | 38 | // Measurement / tool events 39 | MEASUREMENT_ADDED: 'cornerstonetoolsmeasurementadded', 40 | MEASUREMENT_MODIFIED: 'cornerstonetoolsmeasurementmodified', 41 | MEASUREMENT_REMOVED: 'cornerstonemeasurementremoved', 42 | TOOL_DEACTIVATED: 'cornerstonetoolstooldeactivated', 43 | CLIP_STOPPED: 'cornerstonetoolsclipstopped', 44 | STACK_SCROLL: 'cornerstonestackscroll', // Should be renamed 45 | 46 | LINE_SAMPLE_UPDATED: 'cornerstonelinesampleupdated' 47 | }; 48 | 49 | export default EVENTS; 50 | -------------------------------------------------------------------------------- /src/inputSources/keyboardInput.js: -------------------------------------------------------------------------------- 1 | import EVENTS from '../events.js'; 2 | import external from '../externalModules.js'; 3 | import triggerEvent from '../util/triggerEvent.js'; 4 | 5 | let mouseX; 6 | let mouseY; 7 | 8 | function keyPress (e) { 9 | const cornerstone = external.cornerstone; 10 | const element = e.currentTarget; 11 | const enabledElement = cornerstone.getEnabledElement(element); 12 | 13 | if (!enabledElement.image) { 14 | return; 15 | } 16 | 17 | const keyPressData = { 18 | event: window.event || e, // Old IE support 19 | element, 20 | viewport: cornerstone.getViewport(element), 21 | image: enabledElement.image, 22 | currentPoints: { 23 | page: { 24 | x: mouseX, 25 | y: mouseY 26 | }, 27 | image: cornerstone.pageToPixel(element, mouseX, mouseY) 28 | }, 29 | keyCode: e.keyCode, 30 | which: e.which 31 | }; 32 | 33 | keyPressData.currentPoints.canvas = cornerstone.pixelToCanvas(element, keyPressData.currentPoints.image); 34 | 35 | const keyPressEvents = { 36 | keydown: EVENTS.KEY_DOWN, 37 | keypress: EVENTS.KEY_PRESS, 38 | keyup: EVENTS.KEY_UP 39 | }; 40 | 41 | triggerEvent(element, keyPressEvents[e.type], keyPressData); 42 | } 43 | 44 | function mouseMove (e) { 45 | mouseX = e.pageX; 46 | mouseY = e.pageY; 47 | } 48 | 49 | const keyboardEvents = ['keydown', 'keypress', 'keyup']; 50 | 51 | function enable (element) { 52 | keyboardEvents.forEach((eventType) => { 53 | element.removeEventListener(eventType, keyPress); 54 | element.addEventListener(eventType, keyPress); 55 | }); 56 | 57 | element.removeEventListener('mousemove', mouseMove); 58 | element.addEventListener('mousemove', mouseMove); 59 | } 60 | 61 | function disable (element) { 62 | keyboardEvents.forEach((eventType) => { 63 | element.removeEventListener(eventType, keyPress); 64 | }); 65 | 66 | element.removeEventListener('mousemove', mouseMove); 67 | } 68 | 69 | // Module exports 70 | const keyboardInput = { 71 | enable, 72 | disable 73 | }; 74 | 75 | export default keyboardInput; 76 | -------------------------------------------------------------------------------- /src/util/freehand/dragObject.js: -------------------------------------------------------------------------------- 1 | import { freehand } from '../../imageTools/freehand.js'; 2 | import freeHandIntersect from './freeHandIntersect.js'; 3 | 4 | /** 5 | * Moves a part of the freehand tool whilst it is dragged by the mouse. 6 | * 7 | * @param {Object} currentHandle - The handle being dragged. 8 | * @param {Object} data - The data associated with the freehand tool being modified. 9 | * @modifies {currentHandle|data} 10 | */ 11 | export default function (currentHandle, data) { 12 | const config = freehand.getConfiguration(); 13 | 14 | if (config.movingTextBox) { 15 | dragTextBox(currentHandle); 16 | } 17 | 18 | if (config.modifying) { 19 | dragHandle(currentHandle, data); 20 | } 21 | } 22 | 23 | /** 24 | * Moves a freehand tool's textBox whilst it is dragged by the mouse. 25 | * 26 | * @param {Object} currentHandle - The handle being dragged. 27 | * @modifies {currentHandle} 28 | */ 29 | function dragTextBox (currentHandle) { 30 | const config = freehand.getConfiguration(); 31 | 32 | currentHandle.hasMoved = true; 33 | currentHandle.x = config.mouseLocation.handles.start.x; 34 | currentHandle.y = config.mouseLocation.handles.start.y; 35 | } 36 | 37 | /** 38 | * Moves a handle of the freehand tool whilst it is dragged by the mouse. 39 | * 40 | * @param {Object} currentHandle - The handle being dragged. 41 | * @param {Object} data - The data associated with the freehand tool being modified. 42 | * @modifies {data} 43 | */ 44 | function dragHandle (currentHandle, data) { 45 | const config = freehand.getConfiguration(); 46 | 47 | data.handles.invalidHandlePlacement = freeHandIntersect.modify(data.handles, currentHandle); 48 | data.active = true; 49 | data.highlight = true; 50 | data.handles[currentHandle].x = config.mouseLocation.handles.start.x; 51 | data.handles[currentHandle].y = config.mouseLocation.handles.start.y; 52 | if (currentHandle) { 53 | const lastLineIndex = data.handles[currentHandle - 1].lines.length - 1; 54 | const lastLine = data.handles[currentHandle - 1].lines[lastLineIndex]; 55 | 56 | lastLine.x = config.mouseLocation.handles.start.x; 57 | lastLine.y = config.mouseLocation.handles.start.y; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /docs/guides/migrating-major-versions.md: -------------------------------------------------------------------------------- 1 | # Migrating Major Versions 2 | 3 | In [Semantic Versioning](https://semver.org/), a major version change (1.x.x to 2.x.x) occurs when we make incompatible API changes between releases. Simply put, sometimes we need to _break_ things to provide new major features or optimizations. If you find yourself upgrading to a new major version, you should expect additional work will need to be done to upgrade your codebase. This page contains documentation to help update code bases using an older version of Cornerstone: 4 | 5 | 6 | ## v2.x.x from v1.x.x 7 | 8 | This major version drops jQuery as a dependency across all Cornerstone libraries. To accomplish this, how we changed promises internally needed to change; as well as how we emit and consume emitted events. The migration should be fairly painless, unless you've built a large number of custom tools that rely on jQuery. 9 | 10 | ### Update Packages 11 | 12 | - Update `cornerstone-core` to v2.x.x 13 | - Update `cornerstone-tools` to v2.x.x 14 | - If you use `cornerstone-wado-image-loader`, update it to v2.x.x 15 | - If you use `cornerstone-web-image-loader`, update it to v2.x.x 16 | 17 | ### Update Code 18 | 19 | _You no longer need to provide a jQuery external dependency:_ 20 | 21 | `cornerstonePackageNameHere.external.$ = $` 22 | 23 | _Framework level events have been changed:_ 24 | 25 | - From: `$(cornerstone.events).on('CornerstoneEventName', function(evt, data){ })` 26 | - To: `cornerstone.event.addEventListener('cornerstoneeventname', function(evt){ })` 27 | 28 | Where `evt.detail` now contains the data previously in the old handler's second param. 29 | 30 | _To listen to events for a given enabled element, change:_ 31 | 32 | - From: `$.on('CornerstoneCamelCaseName', function (evt, data) { })` 33 | - To: `element.addEventListener('cornerstonelowercasename', function (evt){ })` 34 | 35 | Where `evt.detail` now contains the data previously in the old handler's second param. 36 | 37 | You can see a full list of CornerstoneTool events here: [src/events.js](https://github.com/cornerstonejs/cornerstoneTools/blob/29182180ed3f850ba41c609b98b96464affca5f0/src/events.js) 38 | 39 | ## v1.x.x from v0.9.x 40 | 41 | **TODO** 42 | -------------------------------------------------------------------------------- /examples/exampleTextImageLoader.js: -------------------------------------------------------------------------------- 1 | (function (cs) { 2 | 3 | "use strict"; 4 | 5 | function createTextPixelData(l, bg){ 6 | bg = (bg === undefined?'#222':bg); 7 | var canvas = document.createElement('canvas'); 8 | canvas.width = 256; 9 | canvas.height = 256; 10 | var ctx = canvas.getContext('2d'); 11 | ctx.fillStyle = bg; 12 | ctx.fillRect(0, 0, 256, 256); 13 | ctx.font = "48px courier"; 14 | ctx.fillStyle = 'white'; 15 | ctx.strokeStyle = 'white'; 16 | ctx.strokeText(''+l, 50, 100); 17 | var RgbaPixelData = ctx.getImageData(0,0,256,256).data; 18 | var RedPiexelData = RgbaPixelData.filter((p,i)=>i%4===0); 19 | return RedPiexelData; 20 | } 21 | 22 | // imageId should be // example-n://