47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
84 |
85 |
--------------------------------------------------------------------------------
/examples/loadHandlers/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
20 |
29 |
30 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
85 |
86 |
--------------------------------------------------------------------------------
/docs/essentials/tool-states.md:
--------------------------------------------------------------------------------
1 | # Tool States
2 |
3 | Each CornerstoneTool can be in one of four states: **disabled, enabled, active, passive**. You can see a quick demo of these states in action in [this codepen](https://codepen.io/dannyrb/full/GyjEqW/), or in the [pixel probe example](https://rawgit.com/chafey/cornerstoneTools/master/examples/probe/index.html).
4 |
5 | You set the state per tool, per enabled element. That means you can have multiple enabled canvases displaying images, with a different set of tools active in each.
6 |
7 | > Note: examples below use a "mouseButtonTool". The API may differ slightly for tools with a different flavor of input (ie. touch tools)
8 |
9 | ### Disabled (default)
10 |
11 | This is the default state for all tools. Tools that are disabled cannot be intereacted with and are not rendered on the enabled element.
12 |
13 | ```js
14 | const enabledElement = document.querySelector('.my-cornerstone-canvas')
15 |
16 | // Ex. cornerstoneTools.toolName.disable(enabledElement)
17 | // Disables the length tool, not rendered, and cannot be interacted with
18 | cornerstoneTools.length.disable(enabledElement)
19 | ```
20 |
21 | ### Enabled
22 |
23 | Enabled tools will render, but will not respond to input. The "enabled" tool state is essentially a "read-only" state.
24 |
25 | ```js
26 | const enabledElement = document.querySelector('.my-cornerstone-canvas')
27 |
28 | // Ex. cornerstoneTools.toolName.enable(enabledElement)
29 | // Enables the length tool, can be moved/manipulated with the left mouse button
30 | cornerstoneTools.length.enable(enabledElement)
31 | ```
32 |
33 | ### Active
34 |
35 | Active tools will render and respond to user input. In this state, for tools that "draw", you will be able to add new data to the canvas to add annotations and/or measurements.
36 |
37 | ```js
38 | const LEFT_MOUSE_BUTTON = 1
39 | const MIDDLE_MOUSE_BUTTON = 2
40 | const RIGHT_MOUSE_BUTTON = 4
41 | const enabledElement = document.querySelector('.my-cornerstone-canvas')
42 |
43 | // Ex. cornerstoneTools.toolName.enable(enabledElement, mouseButtonMask, options = {})
44 | // Activates the length tool, and binds it to the left mouse button
45 | cornerstoneTools.length.activate(enabledElement, LEFT_MOUSE_BUTTON)
46 | ```
47 |
48 | ### Passive (Deactivated)
49 |
50 | Passive tools will render and **_passively_** respond to user input. This means that you will be able to position and resize existing measurements and annotations, but not create new measurements and annotations.
51 |
52 | ```js
53 | const LEFT_MOUSE_BUTTON = 1
54 | const enabledElement = document.querySelector('.my-cornerstone-canvas')
55 |
56 | // Ex. cornerstoneTools.toolName.enable(enabledElement, options)
57 | // Length tool becomes passive, can be moved/manipulated with the left mouse button
58 | cornerstoneTools.length.deactivate(enabledElement, LEFT_MOUSE_BUTTON)
59 | ```
60 |
61 |
62 | ## State Management
63 |
64 | As you can imagine, maniging the state for all tools across many canvases can be challenging. If you want to `activate` a new tool, how do you know which already activated tools need to be `disabled`? If you want to apply/sync tool states across enabled elements, how can you?
65 |
66 | > TODO: Basic "state store" and "state management" example
67 |
68 |
69 |
--------------------------------------------------------------------------------
/examples/imageStats/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
22 |
23 |
24 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
90 |
91 |
--------------------------------------------------------------------------------
/src/stackTools/fusionRenderer.js:
--------------------------------------------------------------------------------
1 | import external from '../externalModules.js';
2 | import { getToolState } from '../stateManagement/toolState.js';
3 |
4 | export default class FusionRenderer {
5 | constructor () {
6 | this.currentImageIdIndex = 0;
7 | this.layerIds = [];
8 | this.findImageFn = undefined;
9 | }
10 |
11 | render (element, imageStacks) {
12 | // Move this to base Renderer class
13 | if (!Number.isInteger(this.currentImageIdIndex)) {
14 | throw new Error('FusionRenderer: render - Image ID Index is not an integer');
15 | }
16 |
17 | if (!this.findImageFn) {
18 | throw new Error('No findImage function has been defined');
19 | }
20 |
21 | if (!imageStacks) {
22 | const toolData = getToolState(element, 'stack');
23 |
24 | imageStacks = toolData.data;
25 | }
26 | // TODO: Figure out what to do with LoadHandlers in this scenario...
27 |
28 | const cornerstone = external.cornerstone;
29 |
30 | // For the base layer, go to the currentImageIdIndex
31 | const baseImageObject = imageStacks[0];
32 | const currentImageId = baseImageObject.imageIds[this.currentImageIdIndex];
33 | const overlayImageStacks = imageStacks.slice(1, imageStacks.length);
34 |
35 | cornerstone.loadAndCacheImage(currentImageId).then((baseImage) => {
36 | let baseLayerId = this.layerIds[0];
37 |
38 | // Get the base layer if one exists
39 | if (baseLayerId) {
40 | cornerstone.setLayerImage(element, baseImage, baseLayerId);
41 | } else {
42 | // Otherwise, create a new layer with the base layer's image
43 | baseLayerId = cornerstone.addLayer(element, baseImage, baseImageObject.options);
44 | this.layerIds.push(baseLayerId);
45 | }
46 |
47 | // Display the image immediately while the overlay images are identified
48 | cornerstone.displayImage(element, baseImage);
49 |
50 | // Loop through the remaining 'overlay' image stacks
51 | overlayImageStacks.forEach((imgObj, overlayLayerIndex) => {
52 | const imageId = this.findImageFn(imgObj.imageIds, currentImageId);
53 | const layerIndex = overlayLayerIndex + 1;
54 | let currentLayerId = this.layerIds[layerIndex];
55 |
56 | // If no layer exists yet for this overlaid stack, create
57 | // One and add it to the layerIds property for this instance
58 | // Of the fusion renderer.
59 | if (!currentLayerId) {
60 | currentLayerId = cornerstone.addLayer(element, undefined, imgObj.options);
61 | this.layerIds.push(currentLayerId);
62 | }
63 |
64 | if (imageId) {
65 | // If an imageId was returned from the findImage function,
66 | // Load it, make sure it's visible and update the layer
67 | // With the new image object.
68 | cornerstone.loadAndCacheImage(imageId).then((image) => {
69 | cornerstone.setLayerImage(element, image, currentLayerId);
70 | cornerstone.updateImage(element);
71 | });
72 | } else {
73 | // If no imageId was returned from the findImage function.
74 | // This means that there is no relevant image to display.
75 | cornerstone.setLayerImage(element, undefined, currentLayerId);
76 | cornerstone.setActiveLayer(element, baseLayerId);
77 | cornerstone.updateImage(element);
78 | }
79 | });
80 | });
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/manipulators/moveNewHandle.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 |
10 | function moveCallback (e) {
11 | const eventData = e.detail;
12 |
13 | handle.active = true;
14 | handle.x = eventData.currentPoints.image.x;
15 | handle.y = eventData.currentPoints.image.y;
16 |
17 | if (preventHandleOutsideImage) {
18 | clipToBox(handle, eventData.image);
19 | }
20 |
21 | cornerstone.updateImage(element);
22 |
23 | const eventType = EVENTS.MEASUREMENT_MODIFIED;
24 | const modifiedEventData = {
25 | toolType,
26 | element,
27 | measurementData: data
28 | };
29 |
30 | triggerEvent(element, eventType, modifiedEventData);
31 | }
32 |
33 | function whichMovement (e) {
34 | element.removeEventListener(EVENTS.MOUSE_MOVE, whichMovement);
35 | element.removeEventListener(EVENTS.MOUSE_DRAG, whichMovement);
36 |
37 | element.addEventListener(EVENTS.MOUSE_MOVE, moveCallback);
38 | element.addEventListener(EVENTS.MOUSE_DRAG, moveCallback);
39 |
40 | element.addEventListener(EVENTS.MOUSE_CLICK, moveEndCallback);
41 | if (e.type === EVENTS.MOUSE_DRAG) {
42 | element.addEventListener(EVENTS.MOUSE_UP, moveEndCallback);
43 | }
44 | }
45 |
46 | function measurementRemovedCallback (e) {
47 | const eventData = e.detail;
48 |
49 | if (eventData.measurementData === data) {
50 | moveEndCallback();
51 | }
52 | }
53 |
54 | function toolDeactivatedCallback (e) {
55 | const eventData = e.detail;
56 |
57 | if (eventData.toolType === toolType) {
58 | element.removeEventListener(EVENTS.MOUSE_MOVE, moveCallback);
59 | element.removeEventListener(EVENTS.MOUSE_DRAG, moveCallback);
60 | element.removeEventListener(EVENTS.MOUSE_CLICK, moveEndCallback);
61 | element.removeEventListener(EVENTS.MOUSE_UP, moveEndCallback);
62 | element.removeEventListener(EVENTS.MEASUREMENT_REMOVED, measurementRemovedCallback);
63 | element.removeEventListener(EVENTS.TOOL_DEACTIVATED, toolDeactivatedCallback);
64 |
65 | handle.active = false;
66 | cornerstone.updateImage(element);
67 | }
68 | }
69 |
70 | element.addEventListener(EVENTS.MOUSE_DRAG, whichMovement);
71 | element.addEventListener(EVENTS.MOUSE_MOVE, whichMovement);
72 | element.addEventListener(EVENTS.MEASUREMENT_REMOVED, measurementRemovedCallback);
73 | element.addEventListener(EVENTS.TOOL_DEACTIVATED, toolDeactivatedCallback);
74 |
75 | function moveEndCallback () {
76 | element.removeEventListener(EVENTS.MOUSE_MOVE, moveCallback);
77 | element.removeEventListener(EVENTS.MOUSE_DRAG, moveCallback);
78 | element.removeEventListener(EVENTS.MOUSE_CLICK, moveEndCallback);
79 | element.removeEventListener(EVENTS.MOUSE_UP, moveEndCallback);
80 | element.removeEventListener(EVENTS.MEASUREMENT_REMOVED, measurementRemovedCallback);
81 | element.removeEventListener(EVENTS.TOOL_DEACTIVATED, toolDeactivatedCallback);
82 |
83 | handle.active = false;
84 | cornerstone.updateImage(element);
85 |
86 | if (typeof doneMovingCallback === 'function') {
87 | doneMovingCallback();
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/toolOptions.js:
--------------------------------------------------------------------------------
1 | const elementToolOptions = {};
2 |
3 | /**
4 | * Retrieve the options object associated with a particular toolType and element
5 | *
6 | * @param {string} toolType Tool type identifier of the target options object
7 | * @param {HTMLElement} element Element of the target options object
8 | *
9 | * @return {Object} Target options object (empty if not yet set)
10 | */
11 | function getToolOptions (toolType, element) {
12 | if (!elementToolOptions[toolType]) {
13 | return {};
14 | }
15 |
16 | const toolOptions = elementToolOptions[toolType];
17 | const optionsObject = toolOptions.find((toolOptionObject) => toolOptionObject.element === element);
18 |
19 | if (!optionsObject) {
20 | return {};
21 | }
22 |
23 | return optionsObject.options;
24 | }
25 |
26 | /**
27 | * Set the options object associated with a particular toolType and element
28 | *
29 | * @param {string} toolType Tool type identifier of the target options object
30 | * @param {HTMLElement} element Element of the target options object
31 | * @param {Object} options Options object to store at target
32 | *
33 | * @return {void}
34 | */
35 | function setToolOptions (toolType, element, options) {
36 | if (!elementToolOptions[toolType]) {
37 | elementToolOptions[toolType] = [{
38 | element,
39 | options
40 | }];
41 |
42 | return;
43 | }
44 |
45 | const toolOptions = elementToolOptions[toolType];
46 | const index = toolOptions.findIndex((toolOptionObject) => toolOptionObject.element === element);
47 |
48 | if (index === -1) {
49 | elementToolOptions[toolType].push({
50 | element,
51 | options
52 | });
53 | } else {
54 | const elementOptions = elementToolOptions[toolType][index].options || {};
55 |
56 | elementToolOptions[toolType][index].options = Object.assign(elementOptions, options);
57 | }
58 | }
59 |
60 | /**
61 | * Clear the options object associated with a particular toolType and element
62 | *
63 | * @param {string} toolType Tool type identifier of the target options object
64 | * @param {HTMLElement} element Element of the target options object
65 | *
66 | * @return {void}
67 | */
68 | function clearToolOptions (toolType, element) {
69 | const toolOptions = elementToolOptions[toolType];
70 |
71 | if (toolOptions) {
72 | elementToolOptions[toolType] = toolOptions.filter(
73 | (toolOptionObject) => toolOptionObject.element !== element
74 | );
75 | }
76 | }
77 |
78 | /**
79 | * Clear the options objects associated with a particular toolType
80 | *
81 | * @param {string} toolType Tool type identifier of the target options objects
82 | *
83 | * @return {void}
84 | */
85 | function clearToolOptionsByToolType (toolType) {
86 | delete elementToolOptions[toolType];
87 | }
88 |
89 | /**
90 | * Clear the options objects associated with a particular element
91 | *
92 | * @param {HTMLElement} element Element of the target options objects
93 | *
94 | * @return {void}
95 | */
96 | function clearToolOptionsByElement (element) {
97 | for (const toolType in elementToolOptions) {
98 | elementToolOptions[toolType] = elementToolOptions[toolType].filter(
99 | (toolOptionObject) => toolOptionObject.element !== element
100 | );
101 | }
102 | }
103 |
104 | export {
105 | getToolOptions,
106 | setToolOptions,
107 | clearToolOptions,
108 | clearToolOptionsByToolType,
109 | clearToolOptionsByElement
110 | };
111 |
--------------------------------------------------------------------------------
/src/synchronization/stackImagePositionSynchronizer.js:
--------------------------------------------------------------------------------
1 | import external from '../externalModules.js';
2 | import { getToolState } from '../stateManagement/toolState.js';
3 | import loadHandlerManager from '../stateManagement/loadHandlerManager.js';
4 | import convertToVector3 from '../util/convertToVector3.js';
5 |
6 | // This function causes the image in the target stack to be set to the one closest
7 | // To the image in the source stack by image position
8 | export default function (synchronizer, sourceElement, targetElement) {
9 |
10 | // Ignore the case where the source and target are the same enabled element
11 | if (targetElement === sourceElement) {
12 | return;
13 | }
14 |
15 | const cornerstone = external.cornerstone;
16 | const sourceImage = cornerstone.getEnabledElement(sourceElement).image;
17 | const sourceImagePlane = cornerstone.metaData.get('imagePlaneModule', sourceImage.imageId);
18 |
19 | if (sourceImagePlane === undefined || sourceImagePlane.imagePositionPatient === undefined) {
20 | // Console.log('No position found for image ' + sourceImage.imageId);
21 |
22 | return;
23 | }
24 |
25 | const sourceImagePosition = convertToVector3(sourceImagePlane.imagePositionPatient);
26 | const stackToolDataSource = getToolState(targetElement, 'stack');
27 | const stackData = stackToolDataSource.data[0];
28 |
29 | let minDistance = Number.MAX_VALUE;
30 | let newImageIdIndex = -1;
31 |
32 | stackData.imageIds.forEach((imageId, index) => {
33 | const imagePlane = cornerstone.metaData.get('imagePlaneModule', imageId);
34 |
35 | if (imagePlane === undefined || imagePlane.imagePositionPatient === undefined) {
36 | // Console.log('No position found for image ' + imageId);
37 |
38 | return;
39 | }
40 |
41 | const imagePosition = convertToVector3(imagePlane.imagePositionPatient);
42 | const distance = imagePosition.distanceToSquared(sourceImagePosition);
43 | // Console.log(index + '=' + distance);
44 |
45 | if (distance < minDistance) {
46 | minDistance = distance;
47 | newImageIdIndex = index;
48 | }
49 | });
50 |
51 | if (newImageIdIndex === stackData.currentImageIdIndex) {
52 | return;
53 | }
54 |
55 | const startLoadingHandler = loadHandlerManager.getStartLoadHandler();
56 | const endLoadingHandler = loadHandlerManager.getEndLoadHandler();
57 | const errorLoadingHandler = loadHandlerManager.getErrorLoadingHandler();
58 |
59 | stackData.currentImageIdIndex = newImageIdIndex;
60 | const newImageId = stackData.imageIds[newImageIdIndex];
61 |
62 | if (startLoadingHandler) {
63 | startLoadingHandler(targetElement);
64 | }
65 |
66 | if (newImageIdIndex !== -1) {
67 | let loader;
68 |
69 | if (stackData.preventCache === true) {
70 | loader = cornerstone.loadImage(newImageId);
71 | } else {
72 | loader = cornerstone.loadAndCacheImage(newImageId);
73 | }
74 |
75 | loader.then(function (image) {
76 | const viewport = cornerstone.getViewport(targetElement);
77 |
78 | if (stackData.currentImageIdIndex !== newImageIdIndex) {
79 | return;
80 | }
81 |
82 | synchronizer.displayImage(targetElement, image, viewport);
83 | if (endLoadingHandler) {
84 | endLoadingHandler(targetElement, image);
85 | }
86 | }, function (error) {
87 | const imageId = stackData.imageIds[newImageIdIndex];
88 |
89 | if (errorLoadingHandler) {
90 | errorLoadingHandler(targetElement, imageId, error);
91 | }
92 | });
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/stateManagement/frameOfReferenceStateManager.js:
--------------------------------------------------------------------------------
1 | // This implements a frame-of-reference specific tool state management strategy. This means that
2 | // Measurement data are tied to a specific frame of reference UID and only visible to objects using
3 | // That frame-of-reference UID
4 |
5 | function newFrameOfReferenceSpecificToolStateManager () {
6 | const toolState = {};
7 |
8 | // Here we add tool state, this is done by tools as well
9 | // As modules that restore saved state
10 | function addFrameOfReferenceSpecificToolState (frameOfReference, toolType, data) {
11 | // If we don't have any tool state for this frameOfReference, add an empty object
12 | if (toolState.hasOwnProperty(frameOfReference) === false) {
13 | toolState[frameOfReference] = {};
14 | }
15 |
16 | const frameOfReferenceToolState = toolState[frameOfReference];
17 |
18 | // If we don't have tool state for this type of tool, add an empty object
19 | if (frameOfReferenceToolState.hasOwnProperty(toolType) === false) {
20 | frameOfReferenceToolState[toolType] = {
21 | data: []
22 | };
23 | }
24 |
25 | const toolData = frameOfReferenceToolState[toolType];
26 |
27 | // Finally, add this new tool to the state
28 | toolData.data.push(data);
29 | }
30 |
31 | // Here you can get state - used by tools as well as modules
32 | // That save state persistently
33 | function getFrameOfReferenceSpecificToolState (frameOfReference, toolType) {
34 | // If we don't have any tool state for this frame of reference, return undefined
35 | if (toolState.hasOwnProperty(frameOfReference) === false) {
36 | return;
37 | }
38 |
39 | const frameOfReferenceToolState = toolState[frameOfReference];
40 |
41 | // If we don't have tool state for this type of tool, return undefined
42 | if (frameOfReferenceToolState.hasOwnProperty(toolType) === false) {
43 | return;
44 | }
45 |
46 | const toolData = frameOfReferenceToolState[toolType];
47 |
48 |
49 | return toolData;
50 | }
51 |
52 | function removeFrameOfReferenceSpecificToolState (frameOfReference, toolType, data) {
53 | // If we don't have any tool state for this frame of reference, return undefined
54 | if (toolState.hasOwnProperty(frameOfReference) === false) {
55 | return;
56 | }
57 |
58 | const frameOfReferenceToolState = toolState[frameOfReference];
59 |
60 | // If we don't have tool state for this type of tool, return undefined
61 | if (frameOfReferenceToolState.hasOwnProperty(toolType) === false) {
62 | return;
63 | }
64 |
65 | const toolData = frameOfReferenceToolState[toolType];
66 | // Find this tool data
67 | let indexOfData = -1;
68 |
69 | for (let i = 0; i < toolData.data.length; i++) {
70 | if (toolData.data[i] === data) {
71 | indexOfData = i;
72 | }
73 | }
74 |
75 | if (indexOfData !== -1) {
76 | toolData.data.splice(indexOfData, 1);
77 | }
78 | }
79 |
80 | return {
81 | get: getFrameOfReferenceSpecificToolState,
82 | add: addFrameOfReferenceSpecificToolState,
83 | remove: removeFrameOfReferenceSpecificToolState
84 | };
85 | }
86 |
87 | // A global frameOfReferenceSpecificToolStateManager - the most common case is to share 3d information
88 | // Between stacks of images
89 | const globalFrameOfReferenceSpecificToolStateManager = newFrameOfReferenceSpecificToolStateManager();
90 |
91 | export {
92 | newFrameOfReferenceSpecificToolStateManager,
93 | globalFrameOfReferenceSpecificToolStateManager
94 | };
95 |
--------------------------------------------------------------------------------
/src/paintingTools/brush.js:
--------------------------------------------------------------------------------
1 | import external from '../externalModules.js';
2 | import { getToolState } from '../stateManagement/toolState.js';
3 | import brushTool from './brushTool.js';
4 | import getCircle from './getCircle.js';
5 | import { drawBrushPixels, drawBrushOnCanvas } from './drawBrush.js';
6 |
7 | // This module is for creating segmentation overlays
8 |
9 | const TOOL_STATE_TOOL_TYPE = 'brush';
10 | const toolType = 'brush';
11 | const configuration = {
12 | draw: 1,
13 | radius: 5,
14 | minRadius: 1,
15 | maxRadius: 20,
16 | hoverColor: 'rgba(230, 25, 75, 1.0)',
17 | dragColor: 'rgba(230, 25, 75, 0.8)',
18 | active: false
19 | };
20 |
21 | let lastImageCoords;
22 | let dragging = false;
23 |
24 | function paint (eventData) {
25 | const configuration = brush.getConfiguration();
26 | const element = eventData.element;
27 | const layer = external.cornerstone.getLayer(element, configuration.brushLayerId);
28 | const { rows, columns } = layer.image;
29 | const { x, y } = eventData.currentPoints.image;
30 | const toolData = getToolState(element, TOOL_STATE_TOOL_TYPE);
31 | const pixelData = toolData.data[0].pixelData;
32 | const brushPixelValue = configuration.draw;
33 | const radius = configuration.radius;
34 |
35 | if (x < 0 || x > columns ||
36 | y < 0 || y > rows) {
37 | return;
38 | }
39 |
40 | const pointerArray = getCircle(radius, rows, columns, x, y);
41 |
42 | drawBrushPixels(pointerArray, pixelData, brushPixelValue, columns);
43 |
44 | layer.invalid = true;
45 |
46 | external.cornerstone.updateImage(element);
47 | }
48 |
49 | function onMouseUp (e) {
50 | const eventData = e.detail;
51 |
52 | lastImageCoords = eventData.currentPoints.image;
53 | dragging = false;
54 | }
55 |
56 | function onMouseDown (e) {
57 | const eventData = e.detail;
58 |
59 | paint(eventData);
60 | dragging = true;
61 | lastImageCoords = eventData.currentPoints.image;
62 | }
63 |
64 | function onMouseMove (e) {
65 | const eventData = e.detail;
66 |
67 | lastImageCoords = eventData.currentPoints.image;
68 | external.cornerstone.updateImage(eventData.element);
69 | }
70 |
71 | function onDrag (e) {
72 | const eventData = e.detail;
73 |
74 | paint(eventData);
75 | dragging = true;
76 | lastImageCoords = eventData.currentPoints.image;
77 | }
78 |
79 | function onImageRendered (e) {
80 | const eventData = e.detail;
81 |
82 | if (!lastImageCoords) {
83 | return;
84 | }
85 |
86 | const { rows, columns } = eventData.image;
87 | const { x, y } = lastImageCoords;
88 |
89 | if (x < 0 || x > columns ||
90 | y < 0 || y > rows) {
91 | return;
92 | }
93 |
94 | // Draw the hover overlay on top of the pixel data
95 | const configuration = brush.getConfiguration();
96 | const radius = configuration.radius;
97 | const context = eventData.canvasContext;
98 | const color = dragging ? configuration.dragColor : configuration.hoverColor;
99 | const element = eventData.element;
100 |
101 | context.setTransform(1, 0, 0, 1, 0, 0);
102 |
103 | if (configuration.active) {
104 | const pointerArray = getCircle(radius, rows, columns, x, y);
105 |
106 | drawBrushOnCanvas(pointerArray, context, color, element);
107 | }
108 | }
109 |
110 | const brush = brushTool({
111 | onMouseMove,
112 | onMouseDown,
113 | onMouseUp,
114 | onDrag,
115 | toolType,
116 | onImageRendered
117 | });
118 |
119 | brush.setConfiguration(configuration);
120 |
121 | export { brush };
122 |
--------------------------------------------------------------------------------
/src/stateManagement/stackSpecificStateManager.js:
--------------------------------------------------------------------------------
1 | import { globalImageIdSpecificToolStateManager } from './imageIdSpecificStateManager.js';
2 | import { getElementToolStateManager, setElementToolStateManager } from './toolState.js';
3 |
4 | // This implements an Stack specific tool state management strategy. This means
5 | // That tool data is shared between all imageIds in a given stack
6 | function newStackSpecificToolStateManager (toolTypes, oldStateManager) {
7 | let toolState = {};
8 |
9 | function saveToolState () {
10 | return toolState;
11 | }
12 |
13 | function restoreToolState (stackToolState) {
14 | toolState = stackToolState;
15 | }
16 |
17 | // Here we add tool state, this is done by tools as well
18 | // As modules that restore saved state
19 | function addStackSpecificToolState (element, toolType, data) {
20 | // If this is a tool type to apply to the stack, do so
21 | if (toolTypes.indexOf(toolType) >= 0) {
22 |
23 | // If we don't have tool state for this type of tool, add an empty object
24 | if (toolState.hasOwnProperty(toolType) === false) {
25 | toolState[toolType] = {
26 | data: []
27 | };
28 | }
29 |
30 | const toolData = toolState[toolType];
31 |
32 | // Finally, add this new tool to the state
33 | toolData.data.push(data);
34 | } else {
35 | // Call the imageId specific tool state manager
36 | return oldStateManager.add(element, toolType, data);
37 | }
38 | }
39 |
40 | // Here you can get state - used by tools as well as modules
41 | // That save state persistently
42 | function getStackSpecificToolState (element, toolType) {
43 | // If this is a tool type to apply to the stack, do so
44 | if (toolTypes.indexOf(toolType) >= 0) {
45 | // If we don't have tool state for this type of tool, add an empty object
46 | if (toolState.hasOwnProperty(toolType) === false) {
47 | toolState[toolType] = {
48 | data: []
49 | };
50 | }
51 |
52 | return toolState[toolType];
53 | }
54 |
55 | // Call the imageId specific tool state manager
56 | return oldStateManager.get(element, toolType);
57 |
58 | }
59 |
60 | const stackSpecificToolStateManager = {
61 | get: getStackSpecificToolState,
62 | add: addStackSpecificToolState,
63 | saveToolState,
64 | restoreToolState,
65 | toolState
66 | };
67 |
68 |
69 | return stackSpecificToolStateManager;
70 | }
71 |
72 | const stackStateManagers = [];
73 |
74 | function addStackStateManager (element, otherTools) {
75 | let oldStateManager = getElementToolStateManager(element);
76 |
77 | if (!oldStateManager) {
78 | oldStateManager = globalImageIdSpecificToolStateManager;
79 | }
80 |
81 | let stackTools = ['stack', 'stackPrefetch', 'playClip', 'volume', 'slab', 'referenceLines', 'crosshairs', 'stackRenderer'];
82 |
83 | if (otherTools) {
84 | stackTools = stackTools.concat(otherTools);
85 | }
86 |
87 | const stackSpecificStateManager = newStackSpecificToolStateManager(stackTools, oldStateManager);
88 |
89 | stackStateManagers.push(stackSpecificStateManager);
90 | setElementToolStateManager(element, stackSpecificStateManager);
91 | }
92 |
93 | const stackSpecificStateManager = {
94 | newStackSpecificToolStateManager,
95 | addStackStateManager
96 | };
97 |
98 | export {
99 | stackSpecificStateManager,
100 | newStackSpecificToolStateManager,
101 | addStackStateManager
102 | };
103 |
--------------------------------------------------------------------------------
/src/referenceLines/renderActiveReferenceLine.js:
--------------------------------------------------------------------------------
1 | import external from '../externalModules.js';
2 | import calculateReferenceLine from './calculateReferenceLine.js';
3 | import toolColors from '../stateManagement/toolColors.js';
4 | import toolStyle from '../stateManagement/toolStyle.js';
5 | import convertToVector3 from '../util/convertToVector3.js';
6 | import { draw, path } from '../util/drawing.js';
7 |
8 | // Renders the active reference line
9 | export default function (context, eventData, targetElement, referenceElement) {
10 | const cornerstone = external.cornerstone;
11 | const targetImage = cornerstone.getEnabledElement(targetElement).image;
12 | const referenceImage = cornerstone.getEnabledElement(referenceElement).image;
13 |
14 | // Make sure the images are actually loaded for the target and reference
15 | if (!targetImage || !referenceImage) {
16 | return;
17 | }
18 |
19 | const targetImagePlane = cornerstone.metaData.get('imagePlaneModule', targetImage.imageId);
20 | const referenceImagePlane = cornerstone.metaData.get('imagePlaneModule', referenceImage.imageId);
21 |
22 | // Make sure the target and reference actually have image plane metadata
23 | if (!targetImagePlane ||
24 | !referenceImagePlane ||
25 | !targetImagePlane.rowCosines ||
26 | !targetImagePlane.columnCosines ||
27 | !targetImagePlane.imagePositionPatient ||
28 | !referenceImagePlane.rowCosines ||
29 | !referenceImagePlane.columnCosines ||
30 | !referenceImagePlane.imagePositionPatient) {
31 | return;
32 | }
33 |
34 | // The image planes must be in the same frame of reference
35 | if (targetImagePlane.frameOfReferenceUID !== referenceImagePlane.frameOfReferenceUID) {
36 | return;
37 | }
38 |
39 | targetImagePlane.rowCosines = convertToVector3(targetImagePlane.rowCosines);
40 | targetImagePlane.columnCosines = convertToVector3(targetImagePlane.columnCosines);
41 | targetImagePlane.imagePositionPatient = convertToVector3(targetImagePlane.imagePositionPatient);
42 | referenceImagePlane.rowCosines = convertToVector3(referenceImagePlane.rowCosines);
43 | referenceImagePlane.columnCosines = convertToVector3(referenceImagePlane.columnCosines);
44 | referenceImagePlane.imagePositionPatient = convertToVector3(referenceImagePlane.imagePositionPatient);
45 |
46 | // The image plane normals must be > 30 degrees apart
47 | const targetNormal = targetImagePlane.rowCosines.clone().cross(targetImagePlane.columnCosines);
48 | const referenceNormal = referenceImagePlane.rowCosines.clone().cross(referenceImagePlane.columnCosines);
49 | let angleInRadians = targetNormal.angleTo(referenceNormal);
50 |
51 | angleInRadians = Math.abs(angleInRadians);
52 | if (angleInRadians < 0.5) { // 0.5 radians = ~30 degrees
53 | return;
54 | }
55 |
56 | const referenceLine = calculateReferenceLine(targetImagePlane, referenceImagePlane);
57 |
58 | if (!referenceLine) {
59 | return;
60 | }
61 |
62 | const refLineStartCanvas = cornerstone.pixelToCanvas(eventData.element, referenceLine.start);
63 | const refLineEndCanvas = cornerstone.pixelToCanvas(eventData.element, referenceLine.end);
64 |
65 | const color = toolColors.getActiveColor();
66 | const lineWidth = toolStyle.getToolWidth();
67 |
68 | // Draw the referenceLines
69 | context.setTransform(1, 0, 0, 1, 0, 0);
70 |
71 | draw(context, (context) => {
72 | path(context, { color,
73 | lineWidth }, (context) => {
74 | context.moveTo(refLineStartCanvas.x, refLineStartCanvas.y);
75 | context.lineTo(refLineEndCanvas.x, refLineEndCanvas.y);
76 | });
77 | });
78 | }
79 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cornerstone-tools",
3 | "version": "2.3.7",
4 | "description": "Medical imaging tools for the Cornerstone library",
5 | "main": "./dist/cornerstoneTools.min.js",
6 | "keywords": [
7 | "DICOM",
8 | "medical",
9 | "imaging"
10 | ],
11 | "author": "Chris Hafey, Prasath",
12 | "homepage": "https://github.com/cornerstonejs/cornerstoneTools",
13 | "license": "MIT",
14 | "module": "src/index.js",
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/cornerstonejs/cornerstoneTools.git"
18 | },
19 | "scripts": {
20 | "build": "npm run test && npm run version && npm run webpack",
21 | "clean": "npm run clean:dist && npm run clean:coverage",
22 | "clean:dist": "shx rm -rf dist",
23 | "clean:apidocs": "shx rm -rf docs/api.md",
24 | "clean:coverage": "shx rm -rf coverage",
25 | "docs": "npm run docs:api && cd docs && gitbook install && gitbook serve",
26 | "docs:api": "npm run clean:apidocs && documentation build src/index.js -f md -o docs/api.md",
27 | "docs:deploy": "bash ./build/update-docs.sh",
28 | "eslint": "eslint -c .eslintrc.js src",
29 | "eslint-quiet": "eslint -c .eslintrc.js --quiet src",
30 | "eslint-fix": "eslint -c .eslintrc.js --fix src",
31 | "prepublish": "yarn run build",
32 | "start": "npm run webpack",
33 | "start:dev": "webpack-dev-server --config ./config/webpack/webpack-dev",
34 | "test": "npm run test:chrome",
35 | "test:watch": "karma start config/karma/karma-watch.js",
36 | "test:chrome": "karma start config/karma/karma-chrome.js",
37 | "test:firefox": "karma start config/karma/karma-firefox.js",
38 | "test:all": "npm run test:chrome && npm run test:firefox",
39 | "watch": "npm run webpack:watch",
40 | "webpack": "npm run clean:dist && npm run webpack:prod && npm run webpack:dev",
41 | "webpack:dev": "webpack --progress --config ./config/webpack/webpack-dev",
42 | "webpack:prod": "webpack --progress --config ./config/webpack/webpack-prod",
43 | "webpack:watch": "webpack --progress --debug --watch --config ./config/webpack",
44 | "version": "node -p -e \"'export default \\'' + require('./package.json').version + '\\';'\" > src/version.js"
45 | },
46 | "devDependencies": {
47 | "babel-core": "^6.26.3",
48 | "babel-loader": "^7.1.4",
49 | "babel-preset-env": "^1.7.0",
50 | "chai": "^4.1.2",
51 | "cornerstone-core": "^2.2.4",
52 | "coveralls": "^3.0.1",
53 | "docdash": "^0.4.0",
54 | "documentation": "^7.1.0",
55 | "eslint": "^4.19.1",
56 | "eslint-loader": "^2.0.0",
57 | "eslint-plugin-import": "^2.12.0",
58 | "fs-extra": "^6.0.1",
59 | "gitbook-plugin-edit-link": "^2.0.2",
60 | "gitbook-plugin-ga": "^2.0.0",
61 | "gitbook-plugin-github": "^3.0.0",
62 | "gitbook-plugin-sitemap": "^1.2.0",
63 | "hammerjs": "^2.0.8",
64 | "istanbul": "^0.4.5",
65 | "istanbul-instrumenter-loader": "^3.0.1",
66 | "karma": "^2.0.2",
67 | "karma-chrome-launcher": "^2.2.0",
68 | "karma-coverage": "^1.1.2",
69 | "karma-firefox-launcher": "^1.1.0",
70 | "karma-mocha": "^1.3.0",
71 | "karma-webpack": "^3.0.0",
72 | "lodash": "^4.17.10",
73 | "mocha": "^5.2.0",
74 | "opn-cli": "^3.1.0",
75 | "puppeteer": "^1.4.0",
76 | "shx": "^0.2.2",
77 | "uglifyjs-webpack-plugin": "^1.2.5",
78 | "webpack": "^4.9.1",
79 | "webpack-cli": "^2.1.4",
80 | "webpack-dev-server": "^3.1.4"
81 | },
82 | "dependencies": {
83 | "cornerstone-math": "^0.1.6"
84 | },
85 | "files": [
86 | "dist/cornerstoneTools.js",
87 | "dist/cornerstoneTools.js.map",
88 | "dist/cornerstoneTools.min.js",
89 | "dist/cornerstoneTools.min.js.map"
90 | ]
91 | }
92 |
--------------------------------------------------------------------------------
/src/imageTools/orientationMarkers.js:
--------------------------------------------------------------------------------
1 | import external from '../externalModules.js';
2 | import orientation from '../orientation/index.js';
3 | import displayTool from './displayTool.js';
4 | import toolColors from '../stateManagement/toolColors.js';
5 | import drawTextBox from '../util/drawTextBox.js';
6 | import { getNewContext } from '../util/drawing.js';
7 |
8 | function getOrientationMarkers (element) {
9 | const cornerstone = external.cornerstone;
10 | const enabledElement = cornerstone.getEnabledElement(element);
11 | const imagePlaneMetaData = cornerstone.metaData.get('imagePlaneModule', enabledElement.image.imageId);
12 |
13 | if (!imagePlaneMetaData || !imagePlaneMetaData.rowCosines || !imagePlaneMetaData.columnCosines) {
14 | return;
15 | }
16 |
17 | const rowString = orientation.getOrientationString(imagePlaneMetaData.rowCosines);
18 | const columnString = orientation.getOrientationString(imagePlaneMetaData.columnCosines);
19 |
20 | const oppositeRowString = orientation.invertOrientationString(rowString);
21 | const oppositeColumnString = orientation.invertOrientationString(columnString);
22 |
23 | return {
24 | top: oppositeColumnString,
25 | bottom: columnString,
26 | left: oppositeRowString,
27 | right: rowString
28 | };
29 | }
30 |
31 | function getOrientationMarkerPositions (element) {
32 | const cornerstone = external.cornerstone;
33 | const enabledElement = cornerstone.getEnabledElement(element);
34 | let coords;
35 |
36 | coords = {
37 | x: enabledElement.image.width / 2,
38 | y: 5
39 | };
40 | const top = cornerstone.pixelToCanvas(element, coords);
41 |
42 | coords = {
43 | x: enabledElement.image.width / 2,
44 | y: enabledElement.image.height - 5
45 | };
46 | const bottom = cornerstone.pixelToCanvas(element, coords);
47 |
48 | coords = {
49 | x: 5,
50 | y: enabledElement.image.height / 2
51 | };
52 | const left = cornerstone.pixelToCanvas(element, coords);
53 |
54 | coords = {
55 | x: enabledElement.image.width - 10,
56 | y: enabledElement.image.height / 2
57 | };
58 | const right = cornerstone.pixelToCanvas(element, coords);
59 |
60 | return {
61 | top,
62 | bottom,
63 | left,
64 | right
65 | };
66 | }
67 |
68 | function onImageRendered (e) {
69 | const eventData = e.detail;
70 | const element = eventData.element;
71 |
72 | const markers = getOrientationMarkers(element);
73 |
74 | if (!markers) {
75 | return;
76 | }
77 |
78 | const coords = getOrientationMarkerPositions(element, markers);
79 |
80 | const context = getNewContext(eventData.canvasContext.canvas);
81 |
82 | const color = toolColors.getToolColor();
83 |
84 | const textWidths = {
85 | top: context.measureText(markers.top).width,
86 | left: context.measureText(markers.left).width,
87 | right: context.measureText(markers.right).width,
88 | bottom: context.measureText(markers.bottom).width
89 | };
90 |
91 | drawTextBox(context, markers.top, coords.top.x - textWidths.top / 2, coords.top.y, color);
92 | drawTextBox(context, markers.left, coords.left.x - textWidths.left / 2, coords.left.y, color);
93 |
94 | const config = orientationMarkers.getConfiguration();
95 |
96 | if (config && config.drawAllMarkers) {
97 | drawTextBox(context, markers.right, coords.right.x - textWidths.right / 2, coords.right.y, color);
98 | drawTextBox(context, markers.bottom, coords.bottom.x - textWidths.bottom / 2, coords.bottom.y, color);
99 | }
100 | }
101 | // /////// END IMAGE RENDERING ///////
102 |
103 | // Module exports
104 | const orientationMarkers = displayTool(onImageRendered);
105 |
106 | export default orientationMarkers;
107 |
--------------------------------------------------------------------------------
/examples/scaleOverlayTool/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
24 |
25 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
99 |
100 |
--------------------------------------------------------------------------------
/src/stateManagement/imageIdSpecificStateManager.js:
--------------------------------------------------------------------------------
1 | import external from '../externalModules.js';
2 |
3 | // This implements an imageId specific tool state management strategy. This means that
4 | // Measurements data is tied to a specific imageId and only visible for enabled elements
5 | // That are displaying that imageId.
6 |
7 | function newImageIdSpecificToolStateManager () {
8 | let toolState = {};
9 |
10 | // Here we add tool state, this is done by tools as well
11 | // As modules that restore saved state
12 |
13 | function saveImageIdToolState (imageId) {
14 | return toolState[imageId];
15 | }
16 |
17 | function restoreImageIdToolState (imageId, imageIdToolState) {
18 | toolState[imageId] = imageIdToolState;
19 | }
20 |
21 | function saveToolState () {
22 | return toolState;
23 | }
24 |
25 | function restoreToolState (savedToolState) {
26 | toolState = savedToolState;
27 | }
28 |
29 | // Here we add tool state, this is done by tools as well
30 | // As modules that restore saved state
31 | function addImageIdSpecificToolState (element, toolType, data) {
32 | const enabledImage = external.cornerstone.getEnabledElement(element);
33 | // If we don't have any tool state for this imageId, add an empty object
34 |
35 | if (!enabledImage.image || toolState.hasOwnProperty(enabledImage.image.imageId) === false) {
36 | toolState[enabledImage.image.imageId] = {};
37 | }
38 |
39 | const imageIdToolState = toolState[enabledImage.image.imageId];
40 |
41 | // If we don't have tool state for this type of tool, add an empty object
42 | if (imageIdToolState.hasOwnProperty(toolType) === false) {
43 | imageIdToolState[toolType] = {
44 | data: []
45 | };
46 | }
47 |
48 | const toolData = imageIdToolState[toolType];
49 |
50 | // Finally, add this new tool to the state
51 | toolData.data.push(data);
52 | }
53 |
54 | // Here you can get state - used by tools as well as modules
55 | // That save state persistently
56 | function getImageIdSpecificToolState (element, toolType) {
57 | const enabledImage = external.cornerstone.getEnabledElement(element);
58 | // If we don't have any tool state for this imageId, return undefined
59 |
60 | if (!enabledImage.image || toolState.hasOwnProperty(enabledImage.image.imageId) === false) {
61 | return;
62 | }
63 |
64 | const imageIdToolState = toolState[enabledImage.image.imageId];
65 |
66 | // If we don't have tool state for this type of tool, return undefined
67 | if (imageIdToolState.hasOwnProperty(toolType) === false) {
68 | return;
69 | }
70 |
71 | const toolData = imageIdToolState[toolType];
72 |
73 |
74 | return toolData;
75 | }
76 |
77 | // Clears all tool data from this toolStateManager.
78 | function clearImageIdSpecificToolStateManager (element) {
79 | const enabledImage = external.cornerstone.getEnabledElement(element);
80 |
81 | if (!enabledImage.image || toolState.hasOwnProperty(enabledImage.image.imageId) === false) {
82 | return;
83 | }
84 |
85 | delete toolState[enabledImage.image.imageId];
86 | }
87 |
88 | return {
89 | get: getImageIdSpecificToolState,
90 | add: addImageIdSpecificToolState,
91 | clear: clearImageIdSpecificToolStateManager,
92 | saveImageIdToolState,
93 | restoreImageIdToolState,
94 | saveToolState,
95 | restoreToolState,
96 | toolState
97 | };
98 | }
99 |
100 | // A global imageIdSpecificToolStateManager - the most common case is to share state between all
101 | // Visible enabled images
102 | const globalImageIdSpecificToolStateManager = newImageIdSpecificToolStateManager();
103 |
104 | export {
105 | newImageIdSpecificToolStateManager,
106 | globalImageIdSpecificToolStateManager
107 | };
108 |
--------------------------------------------------------------------------------