med-viewer-sdk 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -137
- package/dist/core/Engine.d.ts +21 -9
- package/dist/core/Magnification.d.ts +1 -0
- package/dist/core/SelectionPlugin.d.ts +2 -2
- package/dist/med-viewer-sdk.d.ts +8 -8
- package/dist/med-viewer-sdk.mjs +12602 -12576
- package/dist/med-viewer-sdk.umd.js +2 -2
- package/package.json +1 -1
- package/src/core/Engine.ts +190 -198
- package/src/core/Magnification.ts +43 -17
- package/src/core/SelectionPlugin.ts +112 -118
- package/src/i18n/i18n.ts +32 -34
- package/src/index.ts +23 -30
package/package.json
CHANGED
package/src/core/Engine.ts
CHANGED
|
@@ -1,62 +1,65 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from "./ColorAdjustPlugin";
|
|
15
|
-
import { SelectionPlugin, type SelectionOptions } from "./SelectionPlugin";
|
|
16
|
-
import { ScalebarPlugin, type ScalebarOptions } from "./Scalebar";
|
|
17
|
-
import {
|
|
18
|
-
MagnificationPlugin,
|
|
19
|
-
type MagnificationOptions,
|
|
20
|
-
} from "./Magnification";
|
|
21
|
-
|
|
22
|
-
import { Locale, setLocale } from "../i18n/i18n";
|
|
1
|
+
import type OpenSeadragonType from 'openseadragon'
|
|
2
|
+
import OpenSeadragon from 'openseadragon'
|
|
3
|
+
|
|
4
|
+
import { ShapeLabelsFormatter, injectShapeLabelStyles } from '@/plugins/ShapeLabelsFormatter'
|
|
5
|
+
|
|
6
|
+
import { AnnoAnnotator } from './AnnoAnnotator'
|
|
7
|
+
import { ColorAdjustPlugin, type ColorAdjustOptions } from './ColorAdjustPlugin'
|
|
8
|
+
import { MagnificationPlugin, type MagnificationOptions } from './Magnification'
|
|
9
|
+
import { ScalebarPlugin, type ScalebarOptions } from './Scalebar'
|
|
10
|
+
import { SelectionPlugin, type SelectionOptions } from './SelectionPlugin'
|
|
11
|
+
import { MedToolbar, type ToolbarOptions } from './Toolbar'
|
|
12
|
+
|
|
13
|
+
import { Locale, setLocale } from '../i18n/i18n'
|
|
23
14
|
/**
|
|
24
15
|
* 引擎配置接口
|
|
25
16
|
*/
|
|
26
17
|
export interface MedEngineOptions {
|
|
27
|
-
osdOptions: OpenSeadragonType.Options
|
|
28
|
-
locale?: Locale
|
|
18
|
+
osdOptions: OpenSeadragonType.Options
|
|
19
|
+
locale?: Locale // 国际化语言设置
|
|
29
20
|
plugins?: {
|
|
30
|
-
annotorious?: boolean | any
|
|
31
|
-
toolbar?: boolean | ToolbarOptions
|
|
32
|
-
colorAdjust?: boolean | ColorAdjustOptions
|
|
33
|
-
selection?: boolean | SelectionOptions
|
|
34
|
-
scalebar?: boolean | ScalebarOptions
|
|
35
|
-
magnification?: boolean | MagnificationOptions
|
|
36
|
-
}
|
|
21
|
+
annotorious?: boolean | any // 是否启用 Annotorious 插件
|
|
22
|
+
toolbar?: boolean | ToolbarOptions // 是否启用工具栏插件
|
|
23
|
+
colorAdjust?: boolean | ColorAdjustOptions // 是否启用色彩调节插件
|
|
24
|
+
selection?: boolean | SelectionOptions // 是否启用选择插件
|
|
25
|
+
scalebar?: boolean | ScalebarOptions // 是否启用比例尺插件
|
|
26
|
+
magnification?: boolean | MagnificationOptions // 是否启用放大镜插件
|
|
27
|
+
}
|
|
37
28
|
}
|
|
38
29
|
|
|
39
30
|
export interface AiBoxRectLabel {
|
|
40
|
-
label: string
|
|
41
|
-
value: string
|
|
31
|
+
label: string
|
|
32
|
+
value: string
|
|
42
33
|
style?: {
|
|
43
|
-
color: string
|
|
44
|
-
fontSize: number
|
|
45
|
-
[key: string]: any
|
|
46
|
-
}
|
|
34
|
+
color: string
|
|
35
|
+
fontSize: number
|
|
36
|
+
[key: string]: any // 允许任意扩展
|
|
37
|
+
}
|
|
47
38
|
}
|
|
48
39
|
|
|
49
40
|
export interface AiBoxRect {
|
|
50
|
-
x: number
|
|
51
|
-
y: number
|
|
52
|
-
width: number
|
|
53
|
-
height: number
|
|
41
|
+
x: number
|
|
42
|
+
y: number
|
|
43
|
+
width: number
|
|
44
|
+
height: number
|
|
45
|
+
style: {
|
|
46
|
+
border: string
|
|
47
|
+
fontSize: number
|
|
48
|
+
[key: string]: any // 允许任意扩展
|
|
49
|
+
}
|
|
50
|
+
labels: Array<AiBoxRectLabel>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface SelectionBox {
|
|
54
|
+
x: number
|
|
55
|
+
y: number
|
|
56
|
+
width: number
|
|
57
|
+
height: number
|
|
54
58
|
style: {
|
|
55
|
-
border: string
|
|
56
|
-
fontSize: number
|
|
57
|
-
[key: string]: any
|
|
58
|
-
}
|
|
59
|
-
labels: Array<AiBoxRectLabel>;
|
|
59
|
+
border: string
|
|
60
|
+
fontSize: number
|
|
61
|
+
[key: string]: any // 允许任意扩展
|
|
62
|
+
}
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
/**
|
|
@@ -64,32 +67,31 @@ export interface AiBoxRect {
|
|
|
64
67
|
* 职责:初始化 OpenSeadragon,管理插件生命周期
|
|
65
68
|
*/
|
|
66
69
|
export class MedViewerEngine {
|
|
67
|
-
public viewer: OpenSeadragon.Viewer
|
|
70
|
+
public viewer: OpenSeadragon.Viewer
|
|
68
71
|
// public konva: KonvaAnnotator | null = null;
|
|
69
|
-
public anno: AnnoAnnotator | null = null
|
|
70
|
-
public toolbar: MedToolbar | null = null
|
|
71
|
-
public colorAdjust: ColorAdjustPlugin | null = null
|
|
72
|
-
public selection: SelectionPlugin | null = null
|
|
73
|
-
public scalebar: ScalebarPlugin | null = null
|
|
74
|
-
public magnification: MagnificationPlugin | null = null
|
|
75
|
-
private options: MedEngineOptions
|
|
76
|
-
private events: Map<string, Set<Function>> = new Map()
|
|
77
|
-
|
|
72
|
+
public anno: AnnoAnnotator | null = null
|
|
73
|
+
public toolbar: MedToolbar | null = null
|
|
74
|
+
public colorAdjust: ColorAdjustPlugin | null = null
|
|
75
|
+
public selection: SelectionPlugin | null = null
|
|
76
|
+
public scalebar: ScalebarPlugin | null = null
|
|
77
|
+
public magnification: MagnificationPlugin | null = null
|
|
78
|
+
private options: MedEngineOptions
|
|
79
|
+
private events: Map<string, Set<Function>> = new Map()
|
|
78
80
|
|
|
79
81
|
constructor(options: MedEngineOptions) {
|
|
80
82
|
if (!options.osdOptions) {
|
|
81
|
-
throw new Error(
|
|
83
|
+
throw new Error('osdOptions is required')
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
if (options.locale) {
|
|
85
|
-
setLocale(options.locale)
|
|
87
|
+
setLocale(options.locale)
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
// 合并默认配置
|
|
89
91
|
const osdOptions: OpenSeadragonType.Options = {
|
|
90
|
-
id:
|
|
91
|
-
crossOriginPolicy:
|
|
92
|
-
prefixUrl: options.osdOptions?.prefixUrl ||
|
|
92
|
+
id: 'osd-container',
|
|
93
|
+
crossOriginPolicy: 'Anonymous',
|
|
94
|
+
prefixUrl: options.osdOptions?.prefixUrl || '',
|
|
93
95
|
opacity: 1,
|
|
94
96
|
preload: false,
|
|
95
97
|
immediateRender: false,
|
|
@@ -119,12 +121,12 @@ export class MedViewerEngine {
|
|
|
119
121
|
// --- Navigator 小图 ---
|
|
120
122
|
showNavigator: true,
|
|
121
123
|
navigatorSizeRatio: 0.15,
|
|
122
|
-
navigatorPosition:
|
|
124
|
+
navigatorPosition: 'TOP_RIGHT',
|
|
123
125
|
navigatorAutoFade: false,
|
|
124
126
|
navigatorOpacity: 0.8,
|
|
125
|
-
navigatorBackground:
|
|
126
|
-
navigatorBorderColor:
|
|
127
|
-
navigatorDisplayRegionColor:
|
|
127
|
+
navigatorBackground: '#fff',
|
|
128
|
+
navigatorBorderColor: '#aaa',
|
|
129
|
+
navigatorDisplayRegionColor: '#f21616',
|
|
128
130
|
|
|
129
131
|
// --- 控件 ---
|
|
130
132
|
showNavigationControl: false, // 你自己做 UI 更专业
|
|
@@ -138,22 +140,22 @@ export class MedViewerEngine {
|
|
|
138
140
|
gestureSettingsMouse: {
|
|
139
141
|
dragToPan: true,
|
|
140
142
|
clickToZoom: false,
|
|
141
|
-
dblClickToZoom: false
|
|
143
|
+
dblClickToZoom: false
|
|
142
144
|
},
|
|
143
145
|
gestureSettingsTouch: {
|
|
144
146
|
flickEnabled: false,
|
|
145
|
-
pinchToZoom: true
|
|
147
|
+
pinchToZoom: true
|
|
146
148
|
},
|
|
147
149
|
|
|
148
|
-
...options.osdOptions
|
|
149
|
-
}
|
|
150
|
-
options.osdOptions = osdOptions
|
|
150
|
+
...options.osdOptions
|
|
151
|
+
}
|
|
152
|
+
options.osdOptions = osdOptions
|
|
151
153
|
|
|
152
|
-
this.options = options
|
|
154
|
+
this.options = options
|
|
153
155
|
|
|
154
156
|
// 1. 初始化 OpenSeadragon
|
|
155
157
|
|
|
156
|
-
this.viewer = OpenSeadragon(osdOptions)
|
|
158
|
+
this.viewer = OpenSeadragon(osdOptions)
|
|
157
159
|
|
|
158
160
|
// this.viewer.addOnceHandler("open", () => {
|
|
159
161
|
// this.viewer.viewport && this.viewer.viewport.resize();
|
|
@@ -161,20 +163,11 @@ export class MedViewerEngine {
|
|
|
161
163
|
// // const isWebGL = this.viewer.drawer instanceof OpenSeadragon.WebGLDrawer;
|
|
162
164
|
// });
|
|
163
165
|
|
|
164
|
-
this.viewer.addOnceHandler(
|
|
166
|
+
this.viewer.addOnceHandler('open', () => {
|
|
165
167
|
// this.viewer.viewport && this.viewer.viewport.resize();
|
|
166
|
-
this.viewer.viewport.goHome()
|
|
167
|
-
this.emit(
|
|
168
|
-
|
|
169
|
-
// if (this.options?.aiMarks?.aiBoxes) {
|
|
170
|
-
// this.loadAIMarks(
|
|
171
|
-
// this.options?.aiMarks?.aiBoxes || [],
|
|
172
|
-
// this.options?.aiMarks?.showLabel || false,
|
|
173
|
-
// );
|
|
174
|
-
// }
|
|
175
|
-
console.log(
|
|
176
|
-
"[MedViewerEngine] First tile loaded, refreshing viewer layout.",
|
|
177
|
-
);
|
|
168
|
+
this.viewer.viewport.goHome()
|
|
169
|
+
this.emit('ready')
|
|
170
|
+
console.log('[MedViewerEngine] First tile loaded, refreshing viewer layout.')
|
|
178
171
|
// console.log(this.viewer.viewport.getBoundsNoRotate());
|
|
179
172
|
|
|
180
173
|
// setTimeout(() => {
|
|
@@ -183,13 +176,10 @@ export class MedViewerEngine {
|
|
|
183
176
|
// console.log("[MedViewerEngine] Viewer layout refreshed after tile load.");
|
|
184
177
|
// })
|
|
185
178
|
// this.viewer.viewport.resize();
|
|
186
|
-
})
|
|
179
|
+
})
|
|
187
180
|
|
|
188
181
|
// 2. 初始化插件
|
|
189
|
-
this.initPlugins()
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
182
|
+
this.initPlugins()
|
|
193
183
|
|
|
194
184
|
// 优化:强制刷新 OSD 布局,确保显示完整
|
|
195
185
|
}
|
|
@@ -199,81 +189,66 @@ export class MedViewerEngine {
|
|
|
199
189
|
* 解决异步 DOM 挂载问题,防止 Annotorious 初始化时找不到 Canvas
|
|
200
190
|
*/
|
|
201
191
|
private initPlugins(): void {
|
|
202
|
-
const { plugins } = this.options
|
|
203
|
-
if (!plugins) return
|
|
192
|
+
const { plugins } = this.options
|
|
193
|
+
if (!plugins) return
|
|
204
194
|
// --- Scalebar 插件初始化 ---
|
|
205
195
|
if (plugins.scalebar) {
|
|
206
|
-
const scalebarConfig =
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
console.log("[MedEngine] Scalebar plugin initialized.");
|
|
196
|
+
const scalebarConfig = typeof plugins.scalebar === 'object' ? plugins.scalebar : {}
|
|
197
|
+
this.scalebar = new ScalebarPlugin(this.viewer, scalebarConfig)
|
|
198
|
+
console.log('[MedEngine] Scalebar plugin initialized.')
|
|
210
199
|
}
|
|
211
200
|
|
|
212
201
|
// --- Annotorious 插件初始化 ---
|
|
213
202
|
// 关键:解决 "Cannot set properties of undefined (setting 'display')"
|
|
214
203
|
// Annotorious v3 必须等待 OSD 的 Canvas 元素创建并挂载后才能初始化
|
|
215
204
|
if (plugins.annotorious) {
|
|
216
|
-
const annoConfig =
|
|
217
|
-
typeof plugins.annotorious === "object" ? plugins.annotorious : {};
|
|
205
|
+
const annoConfig = typeof plugins.annotorious === 'object' ? plugins.annotorious : {}
|
|
218
206
|
if (!annoConfig.locale) {
|
|
219
|
-
annoConfig.locale = this.options.locale ||
|
|
207
|
+
annoConfig.locale = this.options.locale || 'zh-CN'
|
|
220
208
|
}
|
|
221
209
|
|
|
222
210
|
// 如果formatter为对象,则渲染默认格式化,需要添加样式
|
|
223
|
-
if (typeof annoConfig.options?.formatter ===
|
|
224
|
-
const pixelsPerMeter =
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
annoConfig.formatter = ShapeLabelsFormatter(
|
|
229
|
-
pixelsPerMeter,
|
|
230
|
-
showMeasure,
|
|
231
|
-
);
|
|
211
|
+
if (typeof annoConfig.options?.formatter === 'object') {
|
|
212
|
+
const pixelsPerMeter = annoConfig.options?.formatter?.pixelsPerMeter || 1
|
|
213
|
+
const showMeasure = annoConfig.options?.formatter?.showMeasure || false
|
|
214
|
+
injectShapeLabelStyles()
|
|
215
|
+
annoConfig.formatter = ShapeLabelsFormatter(pixelsPerMeter, showMeasure)
|
|
232
216
|
}
|
|
233
217
|
if (this.viewer.isOpen()) {
|
|
234
|
-
console.log(
|
|
235
|
-
"[MedEngine] Viewer is already open, mounting Annotorious.",
|
|
236
|
-
annoConfig,
|
|
237
|
-
);
|
|
218
|
+
console.log('[MedEngine] Viewer is already open, mounting Annotorious.', annoConfig)
|
|
238
219
|
// 如果已经打开(极少见,除非 TileSource 是同步加载的本地对象)
|
|
239
|
-
this.mountAnnotorious(annoConfig)
|
|
220
|
+
this.mountAnnotorious(annoConfig)
|
|
240
221
|
} else {
|
|
241
222
|
// 推荐:监听 open 事件,确保 OSD 内部 DOM 结构完全就绪
|
|
242
|
-
this.viewer.addOnceHandler(
|
|
243
|
-
console.log(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
);
|
|
247
|
-
this.mountAnnotorious(annoConfig);
|
|
248
|
-
});
|
|
223
|
+
this.viewer.addOnceHandler('open', () => {
|
|
224
|
+
console.log('[MedEngine] Viewer is already open, mounting Annotorious.', annoConfig)
|
|
225
|
+
this.mountAnnotorious(annoConfig)
|
|
226
|
+
})
|
|
249
227
|
}
|
|
250
228
|
}
|
|
251
229
|
|
|
252
230
|
// --- Toolbar 插件初始化 ---
|
|
253
231
|
if (plugins.toolbar) {
|
|
254
|
-
const toolbarConfig =
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
console.log("[MedEngine] Toolbar plugin initialized.");
|
|
232
|
+
const toolbarConfig = typeof plugins.toolbar === 'object' ? plugins.toolbar : {}
|
|
233
|
+
this.toolbar = new MedToolbar(this, toolbarConfig)
|
|
234
|
+
console.log('[MedEngine] Toolbar plugin initialized.')
|
|
258
235
|
}
|
|
259
236
|
|
|
260
237
|
// --- Selection 插件初始化 ---
|
|
261
238
|
if (plugins.selection) {
|
|
262
|
-
const selectionConfig =
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
console.log("[MedEngine] Selection plugin initialized.");
|
|
239
|
+
const selectionConfig = typeof plugins.selection === 'object' ? plugins.selection : {}
|
|
240
|
+
this.selection = new SelectionPlugin(this.viewer, selectionConfig)
|
|
241
|
+
console.log('[MedEngine] Selection plugin initialized.')
|
|
266
242
|
}
|
|
267
243
|
|
|
268
244
|
// --- ColorAdjust 插件初始化 ---
|
|
269
245
|
if (plugins.colorAdjust) {
|
|
270
246
|
// 1. 显式提取配置对象,如果是 boolean 则给空对象
|
|
271
|
-
const config: any =
|
|
272
|
-
typeof plugins.colorAdjust === "object" ? plugins.colorAdjust : {};
|
|
247
|
+
const config: any = typeof plugins.colorAdjust === 'object' ? plugins.colorAdjust : {}
|
|
273
248
|
|
|
274
249
|
// 2. 传入配置
|
|
275
|
-
this.colorAdjust = new ColorAdjustPlugin(this.viewer, config)
|
|
276
|
-
console.log(
|
|
250
|
+
this.colorAdjust = new ColorAdjustPlugin(this.viewer, config)
|
|
251
|
+
console.log('[MedEngine] ColorAdjust plugin initialized.')
|
|
277
252
|
// if(this.viewer.isOpen()){
|
|
278
253
|
|
|
279
254
|
// }
|
|
@@ -287,10 +262,9 @@ export class MedViewerEngine {
|
|
|
287
262
|
|
|
288
263
|
// --- Magnification 插件初始化 ---
|
|
289
264
|
if (plugins.magnification) {
|
|
290
|
-
const config: any =
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
console.log("[MedEngine] Magnification plugin initialized.");
|
|
265
|
+
const config: any = typeof plugins.magnification === 'object' ? plugins.magnification : {}
|
|
266
|
+
this.magnification = new MagnificationPlugin(this.viewer, config)
|
|
267
|
+
console.log('[MedEngine] Magnification plugin initialized.')
|
|
294
268
|
}
|
|
295
269
|
}
|
|
296
270
|
|
|
@@ -299,111 +273,129 @@ export class MedViewerEngine {
|
|
|
299
273
|
*/
|
|
300
274
|
private mountAnnotorious(config: any): void {
|
|
301
275
|
try {
|
|
302
|
-
this.anno = new AnnoAnnotator(this, config)
|
|
303
|
-
console.log(
|
|
304
|
-
"[MedEngine] Annotorious plugin initialized. version: 2.7.17",
|
|
305
|
-
);
|
|
276
|
+
this.anno = new AnnoAnnotator(this, config)
|
|
277
|
+
console.log('[MedEngine] Annotorious plugin initialized. version: 2.7.17')
|
|
306
278
|
} catch (error) {
|
|
307
|
-
console.error(
|
|
279
|
+
console.error('[MedEngine] Failed to initialize Annotorious:', error)
|
|
308
280
|
}
|
|
309
281
|
}
|
|
310
282
|
|
|
311
283
|
public loadAIMarks(aiBoxes: AiBoxRect[] = [], showLabel = false) {
|
|
312
|
-
|
|
313
284
|
// 清理旧的标注(如果需要)
|
|
314
|
-
this.clearAIMarks()
|
|
285
|
+
this.clearAIMarks()
|
|
315
286
|
aiBoxes.forEach((box: AiBoxRect) => {
|
|
316
|
-
var elt = document.createElement(
|
|
317
|
-
elt.classList.add(
|
|
287
|
+
var elt = document.createElement('div')
|
|
288
|
+
elt.classList.add('ld-ai-box')
|
|
318
289
|
if (box.style) {
|
|
319
|
-
Object.assign(elt.style, box.style)
|
|
290
|
+
Object.assign(elt.style, box.style)
|
|
320
291
|
}
|
|
321
292
|
if (showLabel) {
|
|
322
|
-
let text = document.createElement(
|
|
323
|
-
text.className =
|
|
324
|
-
text.style.position =
|
|
293
|
+
let text = document.createElement('div')
|
|
294
|
+
text.className = 'celltext'
|
|
295
|
+
text.style.position = 'absolute'
|
|
325
296
|
// text.style.display = "none";
|
|
326
|
-
text.style.right =
|
|
327
|
-
text.style.top =
|
|
328
|
-
text.style.color =
|
|
329
|
-
text.style.background =
|
|
330
|
-
text.style.opacity =
|
|
331
|
-
text.style.width =
|
|
332
|
-
text.style.padding =
|
|
333
|
-
text.style.borderRadius =
|
|
297
|
+
text.style.right = '102%'
|
|
298
|
+
text.style.top = '0'
|
|
299
|
+
text.style.color = '#333'
|
|
300
|
+
text.style.background = '#F5F5F5'
|
|
301
|
+
text.style.opacity = '0.7'
|
|
302
|
+
text.style.width = 'max-content'
|
|
303
|
+
text.style.padding = '5px'
|
|
304
|
+
text.style.borderRadius = '8px'
|
|
334
305
|
box.labels.forEach((label: AiBoxRectLabel) => {
|
|
335
|
-
const labelStyle = label.style || {}
|
|
336
|
-
text.innerHTML += `<div style="${Object.entries(labelStyle)
|
|
337
|
-
|
|
338
|
-
|
|
306
|
+
const labelStyle = label.style || {}
|
|
307
|
+
text.innerHTML += `<div style="${Object.entries(labelStyle)
|
|
308
|
+
.map(([key, value]) => `${key}:${value}`)
|
|
309
|
+
.join(';')}">${label.label} : ${label.value}</div>`
|
|
310
|
+
})
|
|
311
|
+
elt.appendChild(text)
|
|
339
312
|
}
|
|
340
|
-
const img = this.viewer.world.getItemAt(0)
|
|
341
|
-
const size = img.getContentSize()
|
|
313
|
+
const img = this.viewer.world.getItemAt(0)
|
|
314
|
+
const size = img.getContentSize() // { x: 10240, y: 10240 }
|
|
342
315
|
|
|
343
|
-
const x = box.x / size.x
|
|
344
|
-
const y = box.y / size.y
|
|
345
|
-
const w = box.width / size.x
|
|
346
|
-
const h = box.height / size.y
|
|
316
|
+
const x = box.x / size.x
|
|
317
|
+
const y = box.y / size.y
|
|
318
|
+
const w = box.width / size.x
|
|
319
|
+
const h = box.height / size.y
|
|
347
320
|
this.viewer.addOverlay({
|
|
348
321
|
element: elt,
|
|
349
|
-
location: new OpenSeadragon.Rect(
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
w,
|
|
353
|
-
h
|
|
354
|
-
)
|
|
355
|
-
});
|
|
356
|
-
});
|
|
357
|
-
|
|
322
|
+
location: new OpenSeadragon.Rect(x, y, w, h)
|
|
323
|
+
})
|
|
324
|
+
})
|
|
358
325
|
}
|
|
326
|
+
|
|
359
327
|
updateAIMarks(aiBoxes: AiBoxRect[] = [], showLabel = false) {
|
|
360
|
-
this.loadAIMarks(aiBoxes, showLabel)
|
|
328
|
+
this.loadAIMarks(aiBoxes, showLabel)
|
|
361
329
|
}
|
|
362
330
|
|
|
363
331
|
public clearAIMarks() {
|
|
364
332
|
const aiBoxesList = Array.from(
|
|
365
|
-
document.getElementsByClassName(
|
|
366
|
-
)
|
|
333
|
+
document.getElementsByClassName('ld-ai-box') as HTMLCollectionOf<HTMLDivElement>
|
|
334
|
+
)
|
|
367
335
|
// 删除所有 overlay
|
|
368
|
-
aiBoxesList.forEach(box => {
|
|
369
|
-
this.viewer.removeOverlay(box)
|
|
370
|
-
})
|
|
336
|
+
aiBoxesList.forEach((box) => {
|
|
337
|
+
this.viewer.removeOverlay(box)
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
public loadSelectionBox(selections: SelectionBox[] = []) {
|
|
341
|
+
const aiBoxesList = Array.from(
|
|
342
|
+
document.getElementsByClassName('ld-selection-box') as HTMLCollectionOf<HTMLDivElement>
|
|
343
|
+
)
|
|
344
|
+
// 删除所有 overlay
|
|
345
|
+
aiBoxesList.forEach((box) => {
|
|
346
|
+
this.viewer.removeOverlay(box)
|
|
347
|
+
})
|
|
348
|
+
selections.forEach((selection: SelectionBox) => {
|
|
349
|
+
let elt = document.createElement('div')
|
|
350
|
+
elt.classList.add('ld-selection-box')
|
|
351
|
+
if (selection.style) {
|
|
352
|
+
Object.assign(elt.style, selection.style)
|
|
353
|
+
}
|
|
354
|
+
const img = this.viewer.world.getItemAt(0)
|
|
355
|
+
const size = img.getContentSize() // { x: 10240, y: 10240 }
|
|
356
|
+
const x = selection.x / size.x
|
|
357
|
+
const y = selection.y / size.y
|
|
358
|
+
const w = selection.width / size.x
|
|
359
|
+
const h = selection.height / size.y
|
|
360
|
+
this.viewer.addOverlay({
|
|
361
|
+
element: elt,
|
|
362
|
+
location: new OpenSeadragon.Rect(x, y, w, h)
|
|
363
|
+
})
|
|
364
|
+
})
|
|
371
365
|
}
|
|
372
|
-
|
|
373
366
|
public addHandler(event: string, handler: Function) {
|
|
374
367
|
if (!this.events.has(event)) {
|
|
375
|
-
this.events.set(event, new Set())
|
|
368
|
+
this.events.set(event, new Set())
|
|
376
369
|
}
|
|
377
|
-
this.events.get(event)!.add(handler)
|
|
370
|
+
this.events.get(event)!.add(handler)
|
|
378
371
|
}
|
|
379
372
|
|
|
380
373
|
public addOnceHandler(event: string, handler: Function) {
|
|
381
374
|
const wrapper = (...args: any[]) => {
|
|
382
|
-
handler(...args)
|
|
383
|
-
this.removeHandler(event, wrapper)
|
|
384
|
-
}
|
|
385
|
-
this.addHandler(event, wrapper)
|
|
375
|
+
handler(...args)
|
|
376
|
+
this.removeHandler(event, wrapper)
|
|
377
|
+
}
|
|
378
|
+
this.addHandler(event, wrapper)
|
|
386
379
|
}
|
|
387
380
|
|
|
388
381
|
public removeHandler(event: string, handler: Function) {
|
|
389
|
-
this.events.get(event)?.delete(handler)
|
|
382
|
+
this.events.get(event)?.delete(handler)
|
|
390
383
|
}
|
|
391
384
|
|
|
392
385
|
private emit(event: string, payload?: any) {
|
|
393
|
-
this.events.get(event)?.forEach(fn => fn(payload))
|
|
386
|
+
this.events.get(event)?.forEach((fn) => fn(payload))
|
|
394
387
|
}
|
|
395
388
|
|
|
396
|
-
|
|
397
389
|
/**
|
|
398
390
|
* 销毁引擎与所有插件
|
|
399
391
|
*/
|
|
400
392
|
public destroy(): void {
|
|
401
393
|
// this.konva?.destroy();
|
|
402
|
-
this.anno?.destroy()
|
|
403
|
-
this.toolbar?.destroy()
|
|
404
|
-
this.selection?.destroy()
|
|
405
|
-
this.scalebar?.destroy()
|
|
406
|
-
this.colorAdjust?.destroy()
|
|
407
|
-
this.viewer.destroy()
|
|
394
|
+
this.anno?.destroy()
|
|
395
|
+
this.toolbar?.destroy()
|
|
396
|
+
this.selection?.destroy()
|
|
397
|
+
this.scalebar?.destroy()
|
|
398
|
+
this.colorAdjust?.destroy()
|
|
399
|
+
this.viewer.destroy()
|
|
408
400
|
}
|
|
409
401
|
}
|
|
@@ -45,7 +45,6 @@ export class MagnificationPlugin {
|
|
|
45
45
|
this.viewer = viewer;
|
|
46
46
|
this.options = options;
|
|
47
47
|
|
|
48
|
-
|
|
49
48
|
this.init();
|
|
50
49
|
}
|
|
51
50
|
|
|
@@ -99,24 +98,22 @@ export class MagnificationPlugin {
|
|
|
99
98
|
|
|
100
99
|
// 修改 updateMagnificationDisplay 方法
|
|
101
100
|
private updateMagnificationDisplay = (): void => {
|
|
102
|
-
|
|
103
101
|
if (this.options.type === "LD") {
|
|
104
102
|
const tiledImage = this.viewer.world.getItemAt(0);
|
|
105
103
|
if (!this.magnificationDisplay || !tiledImage) return;
|
|
106
|
-
const source =
|
|
107
|
-
((this.viewer as any).source as any);
|
|
104
|
+
const source = (this.viewer as any).source as any;
|
|
108
105
|
const width = (source as any).width;
|
|
109
106
|
const height = (source as any).height;
|
|
110
|
-
const pixelsPerMeter =
|
|
107
|
+
const pixelsPerMeter =
|
|
108
|
+
this.options.pixelsPerMeter ||
|
|
109
|
+
(this.viewer as any).scalebarInstance?.pixelsPerMeter ||
|
|
110
|
+
96;
|
|
111
111
|
const currentZoom = this.viewer.viewport.getZoom();
|
|
112
112
|
// const len = Math.max(width, height);
|
|
113
113
|
const conversionFactor = (20 * 0.0011 * pixelsPerMeter) / width;
|
|
114
114
|
const magnification = conversionFactor * currentZoom;
|
|
115
115
|
|
|
116
116
|
this.magnificationDisplay.innerHTML = `<span>${magnification.toFixed(2)}X</span>`;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
117
|
} else {
|
|
121
118
|
const tiledImage = this.viewer.world.getItemAt(0);
|
|
122
119
|
if (!this.magnificationDisplay || !tiledImage) return;
|
|
@@ -136,28 +133,57 @@ export class MagnificationPlugin {
|
|
|
136
133
|
this.magnificationDisplay.innerHTML = `<span>${magnification.toFixed(2)}X</span>`;
|
|
137
134
|
}
|
|
138
135
|
};
|
|
136
|
+
public getMagnification(): number {
|
|
137
|
+
const tiledImage = this.viewer.world.getItemAt(0);
|
|
138
|
+
if (!tiledImage) return 0;
|
|
139
|
+
|
|
140
|
+
if (this.options.type === "LD") {
|
|
141
|
+
const source = (this.viewer as any).source as any;
|
|
142
|
+
const width = source?.width;
|
|
143
|
+
const pixelsPerMeter =
|
|
144
|
+
this.options.pixelsPerMeter ||
|
|
145
|
+
(this.viewer as any).scalebarInstance?.pixelsPerMeter ||
|
|
146
|
+
96;
|
|
147
|
+
|
|
148
|
+
const currentZoom = this.viewer.viewport.getZoom();
|
|
149
|
+
const conversionFactor = (20 * 0.0011 * pixelsPerMeter) / width;
|
|
150
|
+
|
|
151
|
+
return conversionFactor * currentZoom;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// --- OSD 模式 ---
|
|
155
|
+
const currentZoom = this.viewer.viewport.viewportToImageZoom(
|
|
156
|
+
this.viewer.viewport.getZoom(),
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const baseMag =
|
|
160
|
+
((this.viewer as any).source as any)?.max_magnification || 40;
|
|
161
|
+
|
|
162
|
+
return currentZoom * baseMag;
|
|
163
|
+
}
|
|
139
164
|
|
|
140
165
|
// 修改 setMagnification 方法
|
|
141
166
|
private setMagnification(mag: number): void {
|
|
142
167
|
if (this.options.type === "LD") {
|
|
143
|
-
|
|
144
168
|
const tiledImage = this.viewer.world.getItemAt(0);
|
|
145
169
|
if (!this.magnificationDisplay || !tiledImage) return;
|
|
146
|
-
const source =
|
|
147
|
-
((this.viewer as any).source as any);
|
|
170
|
+
const source = (this.viewer as any).source as any;
|
|
148
171
|
const width = (source as any).width;
|
|
149
172
|
const height = (source as any).height;
|
|
150
|
-
const pixelsPerMeter =
|
|
173
|
+
const pixelsPerMeter =
|
|
174
|
+
this.options.pixelsPerMeter ||
|
|
175
|
+
(this.viewer as any).scalebarInstance?.pixelsPerMeter ||
|
|
176
|
+
96;
|
|
151
177
|
const currentZoom = this.viewer.viewport.getZoom();
|
|
152
178
|
// const len = Math.max(width, height);
|
|
153
179
|
const conversionFactor = (20 * 0.0011 * pixelsPerMeter) / width;
|
|
154
180
|
const targetZoom = mag / conversionFactor;
|
|
155
|
-
this.viewer.viewport.zoomTo(
|
|
181
|
+
this.viewer.viewport.zoomTo(
|
|
182
|
+
targetZoom,
|
|
183
|
+
this.viewer.viewport.getCenter(),
|
|
184
|
+
false,
|
|
185
|
+
);
|
|
156
186
|
this.updateMagnificationDisplay();
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
187
|
} else {
|
|
162
188
|
const tiledImage = this.viewer.world.getItemAt(0);
|
|
163
189
|
if (!tiledImage) return;
|