简介

Cornerstone 是一组 JavaScript 库,可用于构建基于 Web 的医学成像应用程序,它提供了构建放射学应用程序(例如OHIF 查看器)的框架

本文叙述的是库 @cornerstonejs/cornerstoneTools,提供了操作、注释和分割工具以及用于构建交互式成像应用程序的工具框架

最新版本的是@cornerstonejs/cornerstone3D/tree/main/packages/tools,重构于旧的cornerstoneTools

cornerstoneTools常用工具

Annotation Tools

  • AngleTool
    • 角度工具:通过放置三个连续点来创建并定位角度
  • ArrowAnnotateTool
    • 箭头工具:创建并定位箭头和标签
  • BidirectionalTool
    • 双向工具:创建并定位测量的注释区域的长度和宽度
  • CircleRoiTool
    • 用于绘制圆形感兴趣区域并测量封闭像素统计数据的工具
  • CobbAngleTool
    • Cobb角:用于测量两条直线之间角度的工具
  • EllipticalRoiTool
    • 椭圆工具:用于绘制椭圆感兴趣区域并测量封闭像素统计数据的工具
  • FreehandRoiTool
    • 自由测量:用于绘制任意多边形感兴趣区域并测量封闭像素统计数据的工具
  • LengthTool
    • 长度工具:测量距离
  • ProbeTool
    • 探针工具:在所需位置提供图像数据探测的工具
  • RectangleRoiTool
    • 矩形工具:用于绘制感兴趣的矩形区域并测量封闭像素的统计数据的工具
  • TextMarkerTool
    • 使用文本标记注释图像的工具

Segmentation Tools

  • BrushTool
    • 在图像上绘制分割的工具
  • SphericalBrushTool
    • 球形图像上绘制分割的工具
  • CircleScissorsTool
    • 通过绘制圆圈来操作标签图数据的工具
  • CorrectionScissorsTool
    • 用于纠正标签图上的分段的工具
  • FreehandScissorsTool
    • 通过徒手绘制多边形来操作标签图数据的工具
  • RectangleScissorsTool
    • 通过绘制矩形来操作标签图数据的工具

Other Tools

  • CrosshairsTool

    • 十字线工具:用于查找另一个元素中对应于的切片的工具同步图像系列中的图像位置
  • DragProbeTool

    • 提供图像数据探测的工具,拖动时输入位置
  • EraserTool

    • 橡皮擦:删除其他注释工具数据的工具
  • MagnifyTool

    • 放大镜工具:用于以增加的放大倍率检查区域的工具
  • OrientationMarkersTool

    • 用于在图像上显示方向标记的工具
  • OverlayTool

    • 用于在图像上显示比例覆盖的工具,使用 viewport.overlayColor 设置默认颜色
  • PanMultiTouchTool

    • 平移多点触控工具
  • PanTool

    • 平移工具:用于平移图像的工具
  • ReferenceLinesTool

    • 参考线:用于显示其他启用元素的参考线的工具
  • RotateTool

    • 旋转工具:旋转图像
  • RotateTouchTool

    • 使用触摸旋转图像的工具
  • ScaleOverlayTool

    • 用于在图像上显示比例覆盖的工具
  • StackScrollMouseWheelTool

    • 鼠标滚轮工具:是一个允许用户使用鼠标滚轮滚动图像堆栈的工具
  • StackScrollMultiTouchTool

    • 多点触控滚动工具:使用多点触控滚动浏览系列的工具
  • StackScrollTool

    • 拖动滑动工具:允许用户通过按鼠标单击并拖动来滚动图像堆栈
  • WwwcRegionTool

    • 窗体工具:基于矩形区域设置wwwc的工具
  • WwwcTool

    • 窗体工具:通过鼠标/触摸拖动设置wwwc的工具
  • ZoomMouseWheelTool

    • 缩放工具:使用鼠标滚轮更改放大倍率的工具
  • ZoomTool

    • 改变放大倍率的工具
  • ZoomTouchPinchTool

    • 使用多点触控改变放大倍率

工具模板原理

BaseTool

  • 基础工具类说明
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    /**
    * @typedef ToolConfiguration
    * @param {String} name
    * @param {object} strategies - Named strategy functions
    * @param {String} defaultStrategy - The name of the strategy to use by default
    * @param {Object} configuration
    * @param {String[]} mixins - A list of mixin names to apply to the tool
    */

    class BaseTool {
    /**
    * Constructor description
    * @param {props} [props={}] Tool properties set on instantiation of a tool
    * @param {defaultProps} [defaultProps={}] Tools Default properties
    */
    constructor(props, defaultProps) {
    /**
    * Merge default props with custom props
    */
    this.initialConfiguration = deepmerge(defaultProps, props);

    const {
    name,
    strategies,
    defaultStrategy,
    configuration,
    supportedInteractionTypes,
    mixins,
    svgCursor,
    } = this.initialConfiguration;

    /**
    * A unique, identifying tool name
    * @type {String}
    */
    this.name = name;

    /** @type {String} */
    this.mode = 'disabled';
    this.element = undefined;
    this.supportedInteractionTypes = supportedInteractionTypes || [];

    this.strategies = strategies || {};
    this.defaultStrategy =
    defaultStrategy || Object.keys(this.strategies)[0] || undefined;
    this.activeStrategy = this.defaultStrategy;

    if (svgCursor) {
    this.svgCursor = svgCursor;
    }

    // Options are set when a tool is added, during a "mode" change,
    // or via a tool's option's setter
    this._options = {};

    // Configuration is set at tool initalization
    this._configuration = Object.assign({}, configuration);

    // `updateOnMouseMove` causes the frame to render on every mouse move when
    // the tool is active. This is useful for tools that render large/dynamic
    // items to the canvas which can't easily be respresented with an SVG Cursor.
    this.updateOnMouseMove = false;
    this.hideDefaultCursor = false;

    // Apply mixins if mixinsArray is not empty.
    if (mixins && mixins.length) {
    this._applyMixins(mixins);
    }

    this._cursors = Object.assign({}, this.initialConfiguration.cursors);

    const defaultCursor =
    this.defaultStrategy && this._cursors[this.activeStrategy];

    if (defaultCursor) {
    this.svgCursor = defaultCursor;
    }
    }

    // CONFIGURATION
    /**
    * Config...
    * @public
    * @type {Object}
    * @instance
    */
    static get configuration() {}

    get configuration() {
    return this._configuration;
    }

    set configuration(configuration) {
    this._configuration = configuration;
    }

    // OPTIONS

    /**
    * Options...
    * @readonly
    * @instance
    */
    get options() {
    return this._options;
    }

    /**
    * Merges provided options with existing options.
    *
    * @public
    * @instance
    * @param {Object} options - options object to merge with existing options.
    * @returns {undefined}
    */
    mergeOptions(options) {
    this._options = Object.assign({}, this._options, options);
    }

    /**
    * Clears the tools options.
    *
    * @public
    * @instance
    * @memberof Tools.Base.BaseTool
    * @returns {undefined}
    */
    clearOptions() {
    this._options = {};
    }

    /**
    * Apply the currently set/active strategy.
    *
    * @public
    * @instance
    * @method applyActiveStrategy
    * @memberof Tools.Base.BaseTool
    *
    * @param {Object} evt The event that triggered the strategies application
    * @param {Object} operationData - An object containing extra data not present in the `evt`,
    * required to apply the strategy.
    * @returns {*} strategies vary widely; check each specific strategy to find expected return value
    */
    applyActiveStrategy(evt, operationData) {
    return this.strategies[this.activeStrategy].call(this, evt, operationData);
    }

    /**
    * Iterates over registered mixins; any matching names in the provided `mixinsArray` will
    * be merged with this instance.
    *
    * @private
    * @method _applyMixins
    * @param {string[]} mixinsArray An array of mixin identifiers (strings).
    * @returns {undefined}
    */
    _applyMixins(mixinsArray) {
    for (let i = 0; i < mixinsArray.length; i++) {
    const mixin = mixins[`${mixinsArray[i]}`];

    if (typeof mixin === 'object') {
    Object.assign(this, mixin);

    if (typeof this.initializeMixin === 'function') {
    // Run the mixin's initialisation process.
    this.initializeMixin();
    }
    } else {
    logger.warn(`${this.name}: mixin ${mixins[i]} does not exist.`);
    }
    }

    // Don't keep initialiseMixin from last mixin.
    if (this.initializeMixin === 'function') {
    delete this.initializeMixin;
    }
    }

    /**
    * Change the active strategy.
    *
    * @public
    * @method setActiveStrategy
    * @param {string} strategy
    * @returns {null}
    */
    setActiveStrategy(strategy) {
    this.activeStrategy = strategy;

    if (globalConfigurationModule.configuration.showSVGCursors) {
    this.changeCursor(this.element, strategy);
    }
    }

    /**
    * Function responsible for changing the Cursor, according to the strategy.
    * @param {HTMLElement} element
    * @param {string} strategy The strategy to be used on Tool
    * @public
    * @returns {void}
    */
    changeCursor(element, strategy) {
    // Necessary to avoid setToolCursor call without elements, which throws an error.
    if (!element) {
    return;
    }

    // If there are cursors set per strategy, change the cursor.
    const cursor = this._cursors[strategy];

    if (cursor) {
    this.svgCursor = cursor;

    if (this.mode === 'active') {
    setToolCursor(element, cursor);
    // External.cornerstone.updateImage(element);
    }
    }
    }

    // ===================================================================
    // Virtual Methods - Have default behavior but may be overridden.
    // ===================================================================

    //
    // MOUSE
    //

    /**
    * Callback that takes priority if the tool is active, before `MOUSE_DOWN`
    * events are processed. Does nothing by default.
    *
    * @callback BaseTool~preMouseDownCallback
    * @param {CornerstoneTools.event:cornerstonetoolsmousedown} evt
    * @returns {boolean} consumedEvent - True if function consumed the event.
    */
    /**
    * Callback that takes priority if the tool is active, after `MOUSE_DOWN`
    * events are processed. Does nothing by default.
    *
    * @callback BaseTool~postMouseDownCallback
    * @param {CornerstoneTools.event:cornerstonetoolsmousedown} evt
    * @returns {boolean} consumedEvent - True if function consumed the event.
    */

    /**
    * Callback that is called if the tool is active, after `MOUSE_DOWN`
    * events are processed. Does nothing by default.
    *
    * @virtual
    * @param {type} evt
    * @returns {boolean} consumedEvent - True if function consumed the event.
    */

    /**
    * Callback that takes priority if the tool is active, before `TOUCH_START`
    * events are processed. Does nothing by default.
    *
    * @virtual
    * @param {type} evt
    * @returns {boolean} consumedEvent - True if function consumed the event.
    */

    /**
    * Callback that is called if the tool is active, after `TOUCH_START`
    * events are processed. Does nothing by default.
    *
    * @virtual
    * @param {type} evt
    * @returns {boolean} consumedEvent - True if function consumed the event.
    */
    }

    export default BaseTool;

BaseAnnotationTool

  • 在基石画布上创建和显示注释的工具的抽象类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    class BaseAnnotationTool extends BaseTool {
    // ===================================================================
    // Abstract Methods - Must be implemented.
    // ===================================================================

    /**
    * Creates a new annotation.
    *
    * @method createNewMeasurement
    * @memberof Tools.Base.BaseAnnotationTool
    *
    * @param {type} evt description
    * @returns {type} description
    */
    // eslint-disable-next-line no-unused-vars
    createNewMeasurement(evt) {
    throw new Error(
    `Method createNewMeasurement not implemented for ${this.name}.`
    );
    }

    /**
    *
    * Returns true if the given coords are need the tool.
    *
    * @method pointNearTool
    * @memberof Tools.Base.BaseAnnotationTool
    *
    * @param {*} element
    * @param {*} data
    * @param {*} coords
    * @param {string} [interactionType=mouse]
    * @returns {boolean} If the point is near the tool
    */
    // eslint-disable-next-line no-unused-vars
    pointNearTool(element, data, coords, interactionType = 'mouse') {
    throw new Error(`Method pointNearTool not implemented for ${this.name}.`);
    }

    /**
    * Returns the distance in px from the given coords to the closest handle of the annotation.
    *
    * @method distanceFromPoint
    * @memberof Tools.Base.BaseAnnotationTool
    *
    * @param {*} element
    * @param {*} data
    * @param {*} coords
    * @returns {number} - the distance in px from the provided coordinates to the
    * closest rendered portion of the annotation. -1 if the distance cannot be
    * calculated.
    */
    // eslint-disable-next-line no-unused-vars
    distanceFromPoint(element, data, coords) {
    throw new Error(
    `Method distanceFromPoint not implemented for ${this.name}.`
    );
    }

    /**
    * Used to redraw the tool's annotation data per render
    *
    * @abstract
    * @param {*} evt
    * @returns {void}
    */
    // eslint-disable-next-line no-unused-vars
    renderToolData(evt) {
    throw new Error(`renderToolData not implemented for ${this.name}.`);
    }

    // ===================================================================
    // Virtual Methods - Have default behavior but may be overriden.
    // ===================================================================

    /**
    * Event handler for MOUSE_MOVE event.
    *
    * @abstract
    * @event
    * @param {Object} evt - The event.
    * @returns {boolean} - True if the image needs to be updated
    */
    mouseMoveCallback(evt) {
    const { element, currentPoints } = evt.detail;
    const coords = currentPoints.canvas;
    const toolState = getToolState(element, this.name);

    let imageNeedsUpdate = false;

    for (let d = 0; d < toolState.data.length; d++) {
    const data = toolState.data[d];

    // Hovering a handle?
    if (handleActivator(element, data.handles, coords) === true) {
    imageNeedsUpdate = true;
    }

    // Tool data's 'active' does not match coordinates
    // TODO: can't we just do an if/else and save on a pointNearTool check?
    const nearToolAndNotMarkedActive =
    this.pointNearTool(element, data, coords, 'mouse') && !data.active;
    const notNearToolAndMarkedActive =
    !this.pointNearTool(element, data, coords, 'mouse') && data.active;

    if (nearToolAndNotMarkedActive || notNearToolAndMarkedActive) {
    data.active = !data.active;
    imageNeedsUpdate = true;
    }
    }

    return imageNeedsUpdate;
    }

    /**
    * Custom callback for when a handle is selected.
    * @method handleSelectedCallback
    * @memberof Tools.Base.BaseAnnotationTool
    *
    * @param {*} evt -
    * @param {*} toolData -
    * @param {*} handle - The selected handle.
    * @param {String} interactionType -
    * @returns {void}
    */
    handleSelectedCallback(evt, toolData, handle, interactionType = 'mouse') {
    moveHandleNearImagePoint(evt, this, toolData, handle, interactionType);
    }

    /**
    * Custom callback for when a tool is selected.
    *
    * @method toolSelectedCallback
    * @memberof Tools.Base.BaseAnnotationTool
    *
    * @param {*} evt
    * @param {*} annotation
    * @param {string} [interactionType=mouse]
    * @returns {void}
    */
    toolSelectedCallback(evt, annotation, interactionType = 'mouse') {
    moveAnnotation(evt, this, annotation, interactionType);
    }

    /**
    * Updates cached statistics for the tool's annotation data on the element
    *
    * @param {*} image
    * @param {*} element
    * @param {*} data
    * @returns {void}
    */
    updateCachedStats(image, element, data) {
    // eslint-disable-line
    logger.warn(`updateCachedStats not implemented for ${this.name}.`);
    }
    }

    export default BaseAnnotationTool;

BaseBrushTool

  • 用于操作要在基石画布上显示的遮罩数据的工具的抽象类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    class BaseBrushTool extends BaseTool {
    constructor(props, defaultProps = {}) {
    if (!defaultProps.configuration) {
    defaultProps.configuration = { alwaysEraseOnClick: false };
    }

    super(props, defaultProps);

    this.updateOnMouseMove = true;
    this.hideDefaultCursor = true;

    this._drawing = false;
    this._drawingMouseUpCallback = this._drawingMouseUpCallback.bind(this);
    }

    // ===================================================================
    // Abstract Methods - Must be implemented.
    // ===================================================================

    /**
    * Helper function for rendering the brush.
    *
    * @abstract
    * @param {Object} evt - The event.
    * @returns {void}
    */
    // eslint-disable-next-line no-unused-vars
    renderBrush(evt) {
    throw new Error(`Method renderBrush not implemented for ${this.name}.`);
    }

    /**
    * Paints the data to the labelmap.
    *
    * @protected
    * @abstract
    * @param {Object} evt The event.
    * @returns {void}
    */
    // eslint-disable-next-line no-unused-vars
    _paint(evt) {
    throw new Error(`Method _paint not implemented for ${this.name}.`);
    }

    // ===================================================================
    // Virtual Methods - Have default behavior but may be overriden.
    // ===================================================================

    /**
    * Event handler for MOUSE_DRAG event.
    *
    * @override
    * @abstract
    * @event
    * @param {Object} evt - The event.
    */
    mouseDragCallback(evt) {
    const { currentPoints } = evt.detail;

    this._lastImageCoords = currentPoints.image;

    // Safety measure incase _startPainting is overridden and doesn't always
    // start a paint.
    if (this._drawing) {
    this._paint(evt);
    }
    }

    /**
    * Event handler for MOUSE_DOWN event.
    *
    * @override
    * @abstract
    * @event
    * @param {Object} evt - The event.
    */
    preMouseDownCallback(evt) {
    const eventData = evt.detail;
    const { element, currentPoints } = eventData;

    this._startPainting(evt);

    this._lastImageCoords = currentPoints.image;
    this._drawing = true;
    this._startListeningForMouseUp(element);
    this._paint(evt);

    return true;
    }

    /**
    * Initialise painting with BaseBrushTool.
    *
    * @abstract
    * @event
    * @param {Object} evt - The event.
    * @returns {void}
    */
    _startPainting(evt) {
    const eventData = evt.detail;
    const element = eventData.element;
    const { configuration, getters } = segmentationModule;

    const {
    labelmap2D,
    labelmap3D,
    currentImageIdIndex,
    activeLabelmapIndex,
    } = getters.labelmap2D(element);

    const shouldErase =
    this._isCtrlDown(eventData) || this.configuration.alwaysEraseOnClick;

    this.paintEventData = {
    labelmap2D,
    labelmap3D,
    currentImageIdIndex,
    activeLabelmapIndex,
    shouldErase,
    };

    if (configuration.storeHistory) {
    const previousPixelData = labelmap2D.pixelData.slice();

    this.paintEventData.previousPixelData = previousPixelData;
    }
    }

    /**
    * End painting with BaseBrushTool.
    *
    * @abstract
    * @event
    * @param {Object} evt - The event.
    * @returns {void}
    */
    _endPainting(evt) {
    const { configuration, setters } = segmentationModule;
    const { labelmap2D, currentImageIdIndex } = this.paintEventData;

    // Grab the labels on the slice.
    const segmentSet = new Set(labelmap2D.pixelData);
    const iterator = segmentSet.values();

    const segmentsOnLabelmap = [];
    let done = false;

    while (!done) {
    const next = iterator.next();

    done = next.done;

    if (!done) {
    segmentsOnLabelmap.push(next.value);
    }
    }

    labelmap2D.segmentsOnLabelmap = segmentsOnLabelmap;

    if (configuration.storeHistory) {
    const { previousPixelData } = this.paintEventData;
    const newPixelData = labelmap2D.pixelData;
    const operation = {
    imageIdIndex: currentImageIdIndex,
    diff: getDiffBetweenPixelData(previousPixelData, newPixelData),
    };

    setters.pushState(this.element, [operation]);
    }

    triggerLabelmapModifiedEvent(this.element);
    }

    // ===================================================================
    // Implementation interface
    // ===================================================================

    /**
    * Event handler for MOUSE_MOVE event.
    *
    * @override
    * @abstract
    * @event
    * @param {Object} evt - The event.
    */
    mouseMoveCallback(evt) {
    const { currentPoints } = evt.detail;

    this._lastImageCoords = currentPoints.image;
    }

    /**
    * Used to redraw the tool's cursor per render.
    *
    * @override
    * @param {Object} evt - The event.
    */
    renderToolData(evt) {
    const eventData = evt.detail;
    const element = eventData.element;

    // Only brush needs to render.
    if (isToolActiveForElement(element, this.name)) {
    // Call the hover event for the brush
    this.renderBrush(evt);
    }
    }

    /**
    * Event handler for switching mode to passive.
    *
    * @override
    * @event
    * @param {Object} evt - The event.
    */
    // eslint-disable-next-line no-unused-vars
    passiveCallback(evt) {
    try {
    external.cornerstone.updateImage(this.element);
    } catch (error) {
    // It is possible that the image has not been loaded
    // when this is called.
    return;
    }
    }

    /**
    * Event handler for MOUSE_UP during the drawing event loop.
    *
    * @protected
    * @event
    * @param {Object} evt - The event.
    * @returns {void}
    */
    _drawingMouseUpCallback(evt) {
    const eventData = evt.detail;
    const element = eventData.element;

    this._endPainting(evt);

    this._drawing = false;
    this._mouseUpRender = true;
    this._stopListeningForMouseUp(element);
    }

    newImageCallback(evt) {
    if (this._drawing) {
    // End painting on one slice and start on another.
    this._endPainting(evt);
    this._startPainting(evt);
    }
    }

    /**
    * Adds modify loop event listeners.
    *
    * @protected
    * @param {Object} element - The viewport element to add event listeners to.
    * @modifies {element}
    * @returns {void}
    */
    _startListeningForMouseUp(element) {
    element.removeEventListener(EVENTS.MOUSE_UP, this._drawingMouseUpCallback);
    element.removeEventListener(
    EVENTS.MOUSE_CLICK,
    this._drawingMouseUpCallback
    );

    element.addEventListener(EVENTS.MOUSE_UP, this._drawingMouseUpCallback);
    element.addEventListener(EVENTS.MOUSE_CLICK, this._drawingMouseUpCallback);

    external.cornerstone.updateImage(element);
    }

    /**
    * Adds modify loop event listeners.
    *
    * @protected
    * @param {Object} element - The viewport element to add event listeners to.
    * @modifies {element}
    * @returns {void}
    */
    _stopListeningForMouseUp(element) {
    element.removeEventListener(EVENTS.MOUSE_UP, this._drawingMouseUpCallback);
    element.removeEventListener(
    EVENTS.MOUSE_CLICK,
    this._drawingMouseUpCallback
    );

    external.cornerstone.updateImage(element);
    }

    // ===================================================================
    // Brush API. This is effectively a wrapper around the store.
    // ===================================================================

    /**
    * Increases the brush size
    *
    * @public
    * @api
    * @returns {void}
    */
    increaseBrushSize() {
    const { configuration, setters } = segmentationModule;
    const oldRadius = configuration.radius;
    let newRadius = Math.floor(oldRadius * 1.2);

    // If e.g. only 2 pixels big. Math.floor(2*1.2) = 2.
    // Hence, have minimum increment of 1 pixel.
    if (newRadius === oldRadius) {
    newRadius += 1;
    }

    setters.radius(newRadius);
    }

    /**
    * Decreases the brush size
    *
    * @public
    * @api
    * @returns {void}
    */
    decreaseBrushSize() {
    const { configuration, setters } = segmentationModule;
    const oldRadius = configuration.radius;
    const newRadius = Math.floor(oldRadius * 0.8);

    setters.radius(newRadius);
    }

    _isCtrlDown(eventData) {
    return (eventData.event && eventData.event.ctrlKey) || eventData.ctrlKey;
    }
    }

    export default BaseBrushTool;