-
Notifications
You must be signed in to change notification settings - Fork 245
/
BaseTool.ts
267 lines (239 loc) · 8.59 KB
/
BaseTool.ts
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
import { utilities, BaseVolumeViewport } from '@cornerstonejs/core';
import { Types } from '@cornerstonejs/core';
import ToolModes from '../../enums/ToolModes';
import StrategyCallbacks from '../../enums/StrategyCallbacks';
import { InteractionTypes, ToolProps, PublicToolProps } from '../../types';
export interface IBaseTool {
/** ToolGroup ID the tool instance belongs to */
toolGroupId: string;
/** Tool supported interaction types */
supportedInteractionTypes: InteractionTypes[];
/** Tool Mode : Active, Passive, Enabled, Disabled */
mode: ToolModes;
/** Tool Configuration */
configuration: {
preventHandleOutsideImage?: boolean;
strategies?: Record<string, any>;
defaultStrategy?: string;
activeStrategy?: string;
strategyOptions?: Record<string, unknown>;
};
}
/**
* Abstract base class from which all tools derive.
* Deals with cleanly merging custom and default configuration, and strategy
* application.
*/
abstract class BaseTool implements IBaseTool {
static toolName;
/** Supported Interaction Types - currently only Mouse */
public supportedInteractionTypes: InteractionTypes[];
public configuration: Record<string, any>;
/** ToolGroup ID the tool instance belongs to */
public toolGroupId: string;
/** Tool Mode - Active/Passive/Enabled/Disabled/ */
public mode: ToolModes;
constructor(toolProps: PublicToolProps, defaultToolProps: ToolProps) {
const initialProps = utilities.deepMerge(defaultToolProps, toolProps);
const {
configuration = {},
supportedInteractionTypes,
toolGroupId,
} = initialProps;
// If strategies are not initialized in the tool config
if (!configuration.strategies) {
configuration.strategies = {};
configuration.defaultStrategy = undefined;
configuration.activeStrategy = undefined;
configuration.strategyOptions = {};
}
this.toolGroupId = toolGroupId;
this.supportedInteractionTypes = supportedInteractionTypes || [];
this.configuration = Object.assign({}, configuration);
this.mode = ToolModes.Disabled;
}
/**
* Returns the name of the tool
* @returns The name of the tool.
*/
public getToolName(): string {
// Since toolName is static we get it from the class constructor
return (<typeof BaseTool>this.constructor).toolName;
}
/**
* Applies the active strategy function to the enabled element with the specified
* operation data.
* @param enabledElement - The element that is being operated on.
* @param operationData - The data that needs to be passed to the strategy.
* @returns The result of the strategy.
*/
public applyActiveStrategy(
enabledElement: Types.IEnabledElement,
operationData: unknown
): any {
const { strategies, activeStrategy } = this.configuration;
return strategies[activeStrategy]?.call(
this,
enabledElement,
operationData
);
}
/**
* Applies the active strategy, with a given event type being applied.
* The event type function is found by indexing it on the active strategy
* function.
*
* @param enabledElement - The element that is being operated on.
* @param operationData - The data that needs to be passed to the strategy.
* @param callbackType - the type of the callback
*
* @returns The result of the strategy.
*/
public applyActiveStrategyCallback(
enabledElement: Types.IEnabledElement,
operationData: unknown,
callbackType: StrategyCallbacks | string
): any {
const { strategies, activeStrategy } = this.configuration;
if (!strategies[activeStrategy]) {
throw new Error(
`applyActiveStrategyCallback: active strategy ${activeStrategy} not found, check tool configuration or spellings`
);
}
return strategies[activeStrategy][callbackType]?.call(
this,
enabledElement,
operationData
);
}
/**
* merges the new configuration with the tool configuration
* @param configuration - toolConfiguration
*/
public setConfiguration(newConfiguration: Record<string, any>): void {
this.configuration = utilities.deepMerge(
this.configuration,
newConfiguration
);
}
/**
* Sets the active strategy for a tool. Strategies are
* multiple implementations of tool behavior that can be switched by tool
* configuration.
*
* @param strategyName - name of the strategy to be set as active
*/
public setActiveStrategy(strategyName: string): void {
this.setConfiguration({ activeStrategy: strategyName });
}
/**
* Returns the volumeId for the volume viewport. It will grabbed the volumeId
* from the volumeId if particularly specified in the tool configuration, or if
* not, the first actorUID in the viewport is returned as the volumeId. NOTE: for
* segmentations, actorUID is not necessarily the volumeId since the segmentation
* can have multiple representations, use segmentation helpers to get the volumeId
* based on the actorUID.
*
* @param viewport - Volume viewport
* @returns the volumeId for the viewport if specified in the tool configuration,
* or the first actorUID in the viewport if not.
*/
private getTargetVolumeId(viewport: Types.IViewport): string | undefined {
if (this.configuration.volumeId) {
return this.configuration.volumeId;
}
// If volume not specified, then return the actorUID for the
// default actor - first actor
const actorEntries = viewport.getActors();
if (!actorEntries) {
return;
}
// find the first image actor of instance type vtkVolume
return actorEntries.find(
(actorEntry) => actorEntry.actor.getClassName() === 'vtkVolume'
)?.uid;
}
/**
* Get the image that is displayed for the targetId in the cachedStats
* which can be
* * imageId:<imageId>
* * volumeId:<volumeId>
* * videoId:<basePathForVideo>/frames/<frameSpecifier>
*
* @param targetId - annotation targetId stored in the cached stats
* @param renderingEngine - The rendering engine
* @returns The image data for the target.
*/
protected getTargetIdImage(
targetId: string,
renderingEngine: Types.IRenderingEngine
): Types.IImageData | Types.CPUIImageData | Types.IImageVolume {
if (targetId.startsWith('imageId:')) {
const imageId = targetId.split('imageId:')[1];
const imageURI = utilities.imageIdToURI(imageId);
let viewports = utilities.getViewportsWithImageURI(
imageURI,
renderingEngine.id
);
if (!viewports || !viewports.length) {
return;
}
viewports = viewports.filter((viewport) => {
return viewport.getCurrentImageId() === imageId;
});
if (!viewports || !viewports.length) {
return;
}
return viewports[0].getImageData();
} else if (targetId.startsWith('volumeId:')) {
const volumeId = utilities.getVolumeId(targetId);
const viewports = utilities.getViewportsWithVolumeId(
volumeId,
renderingEngine.id
);
if (!viewports || !viewports.length) {
return;
}
return viewports[0].getImageData();
} else if (targetId.startsWith('videoId:')) {
// Video id can be multi-valued for the frame information
const imageURI = utilities.imageIdToURI(targetId);
const viewports = utilities.getViewportsWithImageURI(
imageURI,
renderingEngine.id
);
if (!viewports || !viewports.length) {
return;
}
return viewports[0].getImageData();
} else {
throw new Error(
'getTargetIdImage: targetId must start with "imageId:" or "volumeId:"'
);
}
}
/**
* Get the target Id for the viewport which will be used to store the cached
* statistics scoped to that target in the annotations.
* For StackViewport, targetId is the viewportId, but for the volume viewport,
* the targetId will be grabbed from the volumeId if particularly specified
* in the tool configuration, or if not, the first actorUID in the viewport.
*
* @param viewport - viewport to get the targetId for
* @returns targetId
*/
protected getTargetId(viewport: Types.IViewport): string | undefined {
const targetId = viewport.getReferenceId?.();
if (targetId) {
return targetId;
}
if (viewport instanceof BaseVolumeViewport) {
return `volumeId:${this.getTargetVolumeId(viewport)}`;
}
throw new Error('getTargetId: viewport must have a getTargetId method');
}
}
// Note: this is a workaround since terser plugin does not support static blocks
// yet and we can't easily say static toolName = "BaseTool" in the class definition.
BaseTool.toolName = 'BaseTool';
export default BaseTool;