med-viewer-sdk 0.1.0
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 +253 -0
- package/dist/adapters/vue/MedViewer.d.ts +17 -0
- package/dist/adapters/vue/index.d.ts +2 -0
- package/dist/core/AnnoAnnotator.d.ts +15 -0
- package/dist/core/BaseAnnotator.d.ts +33 -0
- package/dist/core/ColorAdjustPlugin.d.ts +29 -0
- package/dist/core/Coords.d.ts +6 -0
- package/dist/core/Engine.d.ts +57 -0
- package/dist/core/KonvaAnnotator.d.ts +36 -0
- package/dist/core/Scalebar.d.ts +42 -0
- package/dist/core/SelectionPlugin.d.ts +102 -0
- package/dist/core/Toolbar.d.ts +32 -0
- package/dist/med-viewer-sdk.d.ts +6 -0
- package/dist/med-viewer-sdk.mjs +14248 -0
- package/dist/med-viewer-sdk.umd.js +2 -0
- package/dist/style.css +1 -0
- package/package.json +34 -0
- package/src/adapters/vue/MedViewer.ts +37 -0
- package/src/adapters/vue/index.ts +4 -0
- package/src/assets/icons/button_grouphover.png +0 -0
- package/src/assets/icons/button_hover.png +0 -0
- package/src/assets/icons/button_pressed.png +0 -0
- package/src/assets/icons/button_rest.png +0 -0
- package/src/assets/icons/flip_grouphover.png +0 -0
- package/src/assets/icons/flip_hover.png +0 -0
- package/src/assets/icons/flip_pressed.png +0 -0
- package/src/assets/icons/flip_rest.png +0 -0
- package/src/assets/icons/fullpage_grouphover.png +0 -0
- package/src/assets/icons/fullpage_hover.png +0 -0
- package/src/assets/icons/fullpage_pressed.png +0 -0
- package/src/assets/icons/fullpage_rest.png +0 -0
- package/src/assets/icons/home_grouphover.png +0 -0
- package/src/assets/icons/home_hover.png +0 -0
- package/src/assets/icons/home_pressed.png +0 -0
- package/src/assets/icons/home_rest.png +0 -0
- package/src/assets/icons/next_grouphover.png +0 -0
- package/src/assets/icons/next_hover.png +0 -0
- package/src/assets/icons/next_pressed.png +0 -0
- package/src/assets/icons/next_rest.png +0 -0
- package/src/assets/icons/previous_grouphover.png +0 -0
- package/src/assets/icons/previous_hover.png +0 -0
- package/src/assets/icons/previous_pressed.png +0 -0
- package/src/assets/icons/previous_rest.png +0 -0
- package/src/assets/icons/rotateleft_grouphover.png +0 -0
- package/src/assets/icons/rotateleft_hover.png +0 -0
- package/src/assets/icons/rotateleft_pressed.png +0 -0
- package/src/assets/icons/rotateleft_rest.png +0 -0
- package/src/assets/icons/rotateright_grouphover.png +0 -0
- package/src/assets/icons/rotateright_hover.png +0 -0
- package/src/assets/icons/rotateright_pressed.png +0 -0
- package/src/assets/icons/rotateright_rest.png +0 -0
- package/src/assets/icons/selection_cancel_grouphover.png +0 -0
- package/src/assets/icons/selection_cancel_hover.png +0 -0
- package/src/assets/icons/selection_cancel_pressed.png +0 -0
- package/src/assets/icons/selection_cancel_rest.png +0 -0
- package/src/assets/icons/selection_confirm_grouphover.png +0 -0
- package/src/assets/icons/selection_confirm_hover.png +0 -0
- package/src/assets/icons/selection_confirm_pressed.png +0 -0
- package/src/assets/icons/selection_confirm_rest.png +0 -0
- package/src/assets/icons/selection_grouphover.png +0 -0
- package/src/assets/icons/selection_hover.png +0 -0
- package/src/assets/icons/selection_pressed.png +0 -0
- package/src/assets/icons/selection_rest.png +0 -0
- package/src/assets/icons/tool_anno.png +0 -0
- package/src/assets/icons/tool_selection.png +0 -0
- package/src/assets/icons/zoomin_grouphover.png +0 -0
- package/src/assets/icons/zoomin_hover.png +0 -0
- package/src/assets/icons/zoomin_pressed.png +0 -0
- package/src/assets/icons/zoomin_rest.png +0 -0
- package/src/assets/icons/zoomout_grouphover.png +0 -0
- package/src/assets/icons/zoomout_hover.png +0 -0
- package/src/assets/icons/zoomout_pressed.png +0 -0
- package/src/assets/icons/zoomout_rest.png +0 -0
- package/src/core/AnnoAnnotator.ts +102 -0
- package/src/core/BaseAnnotator.ts +43 -0
- package/src/core/ColorAdjustPlugin.ts +256 -0
- package/src/core/Coords.ts +9 -0
- package/src/core/Engine.ts +246 -0
- package/src/core/KonvaAnnotator.ts +185 -0
- package/src/core/Scalebar.ts +87 -0
- package/src/core/SelectionPlugin.ts +252 -0
- package/src/core/Toolbar.ts +370 -0
- package/src/index.ts +21 -0
- package/src/plugins/ShapeLabelsFormatter.js +435 -0
- package/src/plugins/openseadragon-scalebar.js +592 -0
- package/src/plugins/openseadragon-selection.js +657 -0
- package/src/types/type.d.ts +9 -0
package/dist/style.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.r6o-editor{top:0;left:0;margin-left:-19px}.a9s-annotationlayer{position:absolute;top:0;left:0;width:100%;height:100%;outline:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.a9s-annotationlayer.no-cursor,.a9s-annotationlayer.no-cursor *{cursor:none!important}.a9s-crosshair line{stroke-width:1px;stroke:#00000080;pointer-events:none;vector-effect:non-scaling-stroke;shape-rendering:crispEdges}.a9s-selection-mask{stroke:none;fill:transparent;pointer-events:none}.a9s-annotation rect,.a9s-annotation circle,.a9s-annotation ellipse,.a9s-annotation path,.a9s-annotation polygon,.a9s-annotation line,.a9s-selection rect,.a9s-selection circle,.a9s-selection ellipse,.a9s-selection path,.a9s-selection polygon,.a9s-selection line{fill:transparent;cursor:pointer;vector-effect:non-scaling-stroke}.a9s-annotation .a9s-inner,.a9s-selection .a9s-inner{stroke:#fff;stroke-width:1px;fill:transparent}.a9s-annotation .a9s-inner:hover,.a9s-selection .a9s-inner:hover{stroke:#fff000}.a9s-annotation .a9s-outer,.a9s-selection .a9s-outer{stroke:#000000b3;stroke-width:3px;fill:none}.a9s-annotation .a9s-formatter-el,.a9s-selection .a9s-formatter-el{overflow:visible}.a9s-annotation.a9s-point .a9s-inner{display:none}.a9s-annotation.a9s-point .a9s-outer{stroke:#5a5a5a;stroke-width:1.5px;fill:#ffffff80}.a9s-annotation.a9s-point .a9s-outer:hover{fill:#fff000}.a9s-annotation.selected .a9s-inner,.a9s-selection .a9s-inner{stroke:#fff000}.a9s-annotation.editable .a9s-inner{stroke:#fff000;cursor:move!important}.a9s-annotation.editable .a9s-inner:hover{fill:#fff0001a}.a9s-handle{cursor:move}.a9s-handle .a9s-handle-inner{stroke:#fff000;fill:#000}.a9s-handle .a9s-handle-outer{stroke:#000;fill:#fff}.a9s-handle:hover .a9s-handle-inner{fill:#fff000}.r6o-btn{background-color:#4483c4;border:1px solid #4483c4;box-sizing:border-box;color:#fff;cursor:pointer;display:inline-block;font-size:14px;margin:0;outline:none;text-decoration:none;white-space:nowrap;padding:6px 18px;min-width:70px;vertical-align:middle;-webkit-border-radius:2px;-khtml-border-radius:2px;-moz-border-radius:2px;border-radius:2px}.r6o-btn *{vertical-align:middle;cursor:pointer}.r6o-btn .r6o-icon{margin-right:4px}.r6o-btn:disabled{border-color:#a3c2e2!important;background-color:#a3c2e2!important}.r6o-btn:hover{background-color:#4f92d7;border-color:#4f92d7}.r6o-btn.outline{border:1px solid #4483c4;color:#4483c4;background-color:transparent;text-shadow:none}.r6o-autocomplete{display:inline;position:relative}.r6o-autocomplete div[role=combobox]{display:inline}.r6o-autocomplete input{outline:none;border:none;width:80px;height:100%;line-height:14px;white-space:pre;box-sizing:border-box;background-color:transparent;font-size:14px;color:#3f3f3f}.r6o-autocomplete ul{position:absolute;margin:0;padding:0;list-style-type:none;background-color:#fff;border-radius:3px;border:1px solid #d6d7d9;box-sizing:border-box;box-shadow:0 0 20px #00000040}.r6o-autocomplete ul:empty{display:none}.r6o-autocomplete li{box-sizing:border-box;padding:2px 12px;width:100%;cursor:pointer}.r6o-editable-text{max-height:120px;overflow:auto;outline:none;min-height:2em;font-size:14px;font-family:Lato,sans-serif}.r6o-editable-text:empty:not(:focus):before{content:attr(data-placeholder);color:#c2c2c2}.r6o-widget.comment{font-size:14px;min-height:3em;background-color:#fff;position:relative}.r6o-widget.comment .r6o-editable-text,.r6o-widget.comment .r6o-readonly-comment{padding:10px;width:100%;box-sizing:border-box;outline:none;border:none;background-color:transparent;resize:none}.r6o-widget.comment .r6o-readonly-comment{white-space:pre-line}.r6o-widget.comment .r6o-editable-text::-webkit-input-placeholder{color:#c2c2c2}.r6o-widget.comment .r6o-editable-text::-moz-placeholder{color:#c2c2c2}.r6o-widget.comment .r6o-editable-text:-moz-placeholder{color:#c2c2c2}.r6o-widget.comment .r6o-editable-text:-ms-input-placeholder{color:#c2c2c2}.r6o-widget.comment .r6o-lastmodified{border:1px solid #e5e5e5;display:inline-block;border-radius:2px;margin:0 10px 8px;padding:4px 5px;line-height:100%;font-size:12px}.r6o-widget.comment .r6o-lastmodified .r6o-lastmodified-at{color:#757575;padding-left:3px}.r6o-widget.comment .r6o-arrow-down{position:absolute;height:20px;width:20px;top:9px;right:9px;line-height:22px;background-color:#fff;text-align:center;-webkit-font-smoothing:antialiased;border:1px solid #e5e5e5;cursor:pointer;-webkit-border-radius:1px;-khtml-border-radius:1px;-moz-border-radius:1px;border-radius:1px}.r6o-widget.comment .r6o-arrow-down.r6o-menu-open{border-color:#4483c4}.r6o-widget.comment .r6o-comment-dropdown-menu{position:absolute;top:32px;right:8px;background-color:#fff;border:1px solid #e5e5e5;list-style-type:none;margin:0;padding:5px 0;z-index:9999;-webkit-box-shadow:0 2px 4px rgba(0,0,0,.2);-moz-box-shadow:0 2px 4px rgba(0,0,0,.2);box-shadow:0 2px 4px #0003}.r6o-widget.comment .r6o-comment-dropdown-menu li{padding:0 15px;cursor:pointer}.r6o-widget.comment .r6o-comment-dropdown-menu li:hover{background-color:#ecf0f1}.r6o-widget.comment .r6o-purposedropdown{position:relative;z-index:2}.r6o-widget.comment.editable{background-color:#ecf0f1}.r6o-widget.r6o-tag:empty{display:none}@media all and (-ms-high-contrast: none),(-ms-high-contrast: active){.r6o-widget.tag .r6o-taglist li{height:27px}.r6o-widget.tag .r6o-taglist li .r6o-delete-wrapper .r6o-delete{position:relative;top:-4px}}.r6o-widget.r6o-tag{background-color:#ecf0f1;border-bottom:1px solid #e5e5e5;padding:1px 3px;display:flex}.r6o-widget.r6o-tag ul{margin:0;padding:0;list-style-type:none;z-index:1}.r6o-widget.r6o-tag ul.r6o-taglist{flex:0;white-space:nowrap}.r6o-widget.r6o-tag ul.r6o-taglist li{display:inline-block;margin:1px 1px 1px 0;padding:0;vertical-align:middle;overflow:hidden;font-size:12px;background-color:#fff;border:1px solid #d6d7d9;cursor:pointer;position:relative;line-height:180%;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-border-radius:2px;-khtml-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 0 4px rgba(0,0,0,.1);-moz-box-shadow:0 0 4px rgba(0,0,0,.1);box-shadow:0 0 4px #0000001a}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-label{padding:2px 8px;display:inline-block}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-delete-wrapper{display:inline-block;padding:2px 0;color:#fff;width:0;height:100%;background-color:#4483c4;-webkit-border-top-right-radius:2px;-webkit-border-bottom-right-radius:2px;-khtml-border-radius-topright:2px;-khtml-border-radius-bottomright:2px;-moz-border-radius-topright:2px;-moz-border-radius-bottomright:2px;border-top-right-radius:2px;border-bottom-right-radius:2px}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-delete-wrapper .r6o-delete{padding:2px 6px}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-delete-wrapper svg{vertical-align:text-top}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-delete-enter-active{width:24px;transition:width .2s}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-delete-enter-done,.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-delete-exit{width:24px}.r6o-widget.r6o-tag ul.r6o-taglist li .r6o-delete-exit-active{width:0;transition:width .2s}.r6o-widget.r6o-tag .r6o-autocomplete{flex:1;position:relative}.r6o-widget.r6o-tag .r6o-autocomplete li{font-size:14px}.r6o-widget.r6o-tag input{width:100%;padding:0 3px;min-width:80px;outline:none;border:none;line-height:170%;background-color:transparent;color:#3f3f3f}.r6o-widget.r6o-tag input::-webkit-input-placeholder{color:#c2c2c2}.r6o-widget.r6o-tag input::-moz-placeholder{color:#c2c2c2}.r6o-widget.r6o-tag input:-moz-placeholder{color:#c2c2c2}.r6o-widget.r6o-tag input:-ms-input-placeholder{color:#c2c2c2}.r6o-editor{position:absolute;z-index:99999;width:400px;color:#3f3f3f;opacity:0;font-family:Lato,sans-serif;font-size:17px;line-height:27px;-webkit-transition:opacity .2s ease-in;-moz-transition:opacity .2s ease-in;transition:opacity .2s ease-in}.r6o-editor .r6o-arrow{position:absolute;overflow:hidden;top:-12px;left:12px;width:28px;height:12px;display:none}.r6o-editor .r6o-arrow:after{content:"";position:absolute;top:5px;left:5px;width:18px;height:18px;background-color:#fff;-webkit-backface-visibility:hidden;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.r6o-editor .r6o-editor-inner{background-color:#fff;-webkit-border-radius:2px;-khtml-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-webkit-box-shadow:2px 2px 42px rgba(0,0,0,.4);-moz-box-shadow:2px 2px 42px rgba(0,0,0,.4);box-shadow:2px 2px 42px #0006}.r6o-editor .r6o-editor-inner .r6o-widget:first-child{-webkit-border-top-left-radius:2px;-webkit-border-top-right-radius:2px;-khtml-border-radius-topleft:2px;-khtml-border-radius-topright:2px;-moz-border-radius-topleft:2px;-moz-border-radius-topright:2px;border-top-left-radius:2px;border-top-right-radius:2px}.r6o-editor .r6o-editor-inner .r6o-widget{border-bottom:1px solid #e5e5e5}.r6o-editor .r6o-footer{position:relative;text-align:right;padding:8px 0}.r6o-editor .r6o-footer .r6o-btn{margin-right:8px}.r6o-editor .r6o-footer .r6o-btn.delete-annotation{position:absolute;top:7px;left:7px;background-color:transparent;border:none;color:#4483c4;width:32px;height:32px;min-width:0;border-radius:100%;padding:0;display:flex;justify-content:center;align-items:center;-webkit-transition:all .1s ease-in;-moz-transition:all .1s ease-in;-o-transition:all .1s ease-in;transition:all .1s ease-in}.r6o-editor .r6o-footer .r6o-btn.delete-annotation:hover{color:#fff;background-color:#ef352c}@media (max-width: 640px){.r6o-editor{width:260px}}.r6o-editor.r6o-arrow-top .r6o-arrow{display:block}.r6o-editor.r6o-arrow-right{margin-left:8px}.r6o-editor.r6o-arrow-right .r6o-arrow{left:auto;right:12px}.r6o-editor.r6o-arrow-bottom .r6o-arrow{display:block;top:auto;bottom:-12px}.r6o-editor.r6o-arrow-bottom .r6o-arrow:after{top:-11px;box-shadow:none}.r6o-editor.pushed .r6o-arrow,.r6o-editor.dragged .r6o-arrow{display:none}.r6o-editor .r6o-draggable{cursor:move}.r6o-purposedropdown{width:150px;display:inline-block}.r6o-noselect{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.a9s-annotation.hover .a9s-inner{stroke:#fff000}.a9s-annotation:not(.hover):hover .a9s-inner{stroke:#fff}.a9s-osd-crosshair-container{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1;pointer-events:none}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "med-viewer-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "dist/med-viewer-sdk.umd.js",
|
|
5
|
+
"module": "dist/med-viewer-sdk.mjs",
|
|
6
|
+
"types": "dist/med-viewer-sdk.d.ts",
|
|
7
|
+
"files": ["dist", "README.md", "src"],
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "npx vite build && tsc -p tsconfig.dts.json && powershell -Command \"if (Test-Path -Path 'dist\\med-viewer-sdk.d.ts') { Remove-Item -Path 'dist\\med-viewer-sdk.d.ts' } ; Move-Item -Path 'dist\\index.d.ts' -Destination 'dist\\med-viewer-sdk.d.ts'\"",
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"konva": ">=8.0.0",
|
|
14
|
+
"openseadragon": "^3.1.0",
|
|
15
|
+
"vue": ">=2.7.0 || >=3.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^25.2.0",
|
|
19
|
+
"@types/openseadragon": "^5.0.1",
|
|
20
|
+
"terser": "^5.46.0",
|
|
21
|
+
"typescript": "^5.0.0",
|
|
22
|
+
"vite": "^4.0.0",
|
|
23
|
+
"vue-demi": "*"
|
|
24
|
+
},
|
|
25
|
+
"volta": {
|
|
26
|
+
"node": "22.21.1"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@picturae/openseadragonselection": "^2.1.0",
|
|
30
|
+
"@silence_top/annotorious-openseadragon-ld": "^1.0.7",
|
|
31
|
+
"annotorious-openseadragon-ld": "^2.7.19",
|
|
32
|
+
"openseadragonselection": "^1.8.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// 渲染函数写的通用组件
|
|
2
|
+
import { defineComponent, h, onBeforeUnmount, onMounted, shallowRef } from 'vue-demi';
|
|
3
|
+
import type { MedEngineOptions } from '../../core/Engine';
|
|
4
|
+
import { MedViewerEngine } from '../../core/Engine';
|
|
5
|
+
|
|
6
|
+
export default defineComponent({
|
|
7
|
+
name: 'MedViewer',
|
|
8
|
+
props: {
|
|
9
|
+
options: {
|
|
10
|
+
type: Object as () => Omit<MedEngineOptions, 'element'>,
|
|
11
|
+
required: true
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
emits: ['ready'],
|
|
15
|
+
setup(props, { slots, expose, emit }) {
|
|
16
|
+
const containerRef = shallowRef<HTMLElement | null>(null);
|
|
17
|
+
const engineRef = shallowRef<MedViewerEngine | null>(null);
|
|
18
|
+
|
|
19
|
+
onMounted(() => {
|
|
20
|
+
if (!containerRef.value) return;
|
|
21
|
+
engineRef.value = new MedViewerEngine({
|
|
22
|
+
element: containerRef.value,
|
|
23
|
+
...props.options
|
|
24
|
+
});
|
|
25
|
+
emit('ready', engineRef.value);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
onBeforeUnmount(() => {
|
|
29
|
+
engineRef.value?.destroy();
|
|
30
|
+
engineRef.value = null;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expose({ engine: engineRef });
|
|
34
|
+
|
|
35
|
+
return () => h('div', { class: 'med-viewer', ref: containerRef }, slots.default?.());
|
|
36
|
+
}
|
|
37
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// 使用本地下载的 Annotorious v2.7.17 文件
|
|
2
|
+
import Annotorious from "@silence_top/annotorious-openseadragon-ld";
|
|
3
|
+
import "annotorious-openseadragon-ld/dist/annotorious.min.css";
|
|
4
|
+
import { MedViewerEngine } from "./Engine";
|
|
5
|
+
import { BaseAnnotator } from "./BaseAnnotator";
|
|
6
|
+
|
|
7
|
+
// // 全局类型声明
|
|
8
|
+
// declare global {
|
|
9
|
+
// interface Window {
|
|
10
|
+
// Annotorious: any;
|
|
11
|
+
// }
|
|
12
|
+
// }
|
|
13
|
+
|
|
14
|
+
export class AnnoAnnotator extends BaseAnnotator {
|
|
15
|
+
public anno: any;
|
|
16
|
+
|
|
17
|
+
constructor(engine: MedViewerEngine, config: any = {}) {
|
|
18
|
+
super(engine);
|
|
19
|
+
|
|
20
|
+
console.log(
|
|
21
|
+
"[AnnoAnnotator] Initializing Annotorious v2.7.17...",
|
|
22
|
+
Annotorious,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// 使用 v2.7.17 的 Annotorious (全局变量)
|
|
26
|
+
this.anno = Annotorious(this.engine.viewer, {
|
|
27
|
+
...config,
|
|
28
|
+
// 可以在此配置样式、偏好等
|
|
29
|
+
});
|
|
30
|
+
console.log("[AnnoAnnotator] Annotorious v2.7.17 initialized.", this.anno);
|
|
31
|
+
console.log("tools", this.anno.listDrawingTools());
|
|
32
|
+
|
|
33
|
+
this.injectStyles();
|
|
34
|
+
this.initEvents();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public setEnabled(enabled: boolean): void {
|
|
38
|
+
this.anno.setDrawingEnabled(enabled);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public setTool(
|
|
42
|
+
tool:
|
|
43
|
+
| "rect"
|
|
44
|
+
| "polygon"
|
|
45
|
+
| "line"
|
|
46
|
+
| "point"
|
|
47
|
+
| "circle"
|
|
48
|
+
| "ellipse"
|
|
49
|
+
| "freehand"
|
|
50
|
+
| null,
|
|
51
|
+
color?: string,
|
|
52
|
+
): void {
|
|
53
|
+
if (!tool) {
|
|
54
|
+
this.setEnabled(false);
|
|
55
|
+
} else {
|
|
56
|
+
this.anno.setDrawingTool(tool, color);
|
|
57
|
+
this.setEnabled(true);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public getAnnotations(): any[] {
|
|
62
|
+
// 获取当前所有标注
|
|
63
|
+
return this.anno.getAnnotations();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public setAnnotations(data: any[]): void {
|
|
67
|
+
this.anno.setAnnotations(data);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public clear(): void {
|
|
71
|
+
this.anno.clearAnnotations();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public destroy(): void {
|
|
75
|
+
this.anno.destroy();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private initEvents() {
|
|
79
|
+
this.anno.on("createAnnotation", (anno: any) => {
|
|
80
|
+
console.log("新标注已创建:", anno);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private injectStyles() {
|
|
85
|
+
// 适配医学影像的样式(v2.7.17 默认样式有时在深色背景下不明显)
|
|
86
|
+
const styleId = "med-anno-v2-7-17-overrides";
|
|
87
|
+
if (document.getElementById(styleId)) return;
|
|
88
|
+
const style = document.createElement("style");
|
|
89
|
+
style.id = styleId;
|
|
90
|
+
style.innerHTML = `
|
|
91
|
+
.a9s-handle .a9s-handle-inner {
|
|
92
|
+
stroke: #FFEB3B;
|
|
93
|
+
fill: #FF9800;
|
|
94
|
+
}
|
|
95
|
+
.a9s-handle .a9s-handle-outer {
|
|
96
|
+
stroke: #000;
|
|
97
|
+
fill: #fff;
|
|
98
|
+
}
|
|
99
|
+
`;
|
|
100
|
+
document.head.appendChild(style);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { MedViewerEngine } from './Engine';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 标注插件的基类
|
|
5
|
+
*/
|
|
6
|
+
export abstract class BaseAnnotator {
|
|
7
|
+
protected engine: MedViewerEngine;
|
|
8
|
+
protected isEnabled: boolean = false;
|
|
9
|
+
|
|
10
|
+
constructor(engine: MedViewerEngine) {
|
|
11
|
+
this.engine = engine;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 启用/禁用标注功能
|
|
16
|
+
*/
|
|
17
|
+
public abstract setEnabled(enabled: boolean): void;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 设置当前的工具模式 (例如:矩形、箭头、多边形)
|
|
21
|
+
*/
|
|
22
|
+
public abstract setTool(tool: string | null): void;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 获取所有标注数据 (建议在此处进行格式标准化)
|
|
26
|
+
*/
|
|
27
|
+
public abstract getAnnotations(): any[];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 加载标注数据
|
|
31
|
+
*/
|
|
32
|
+
public abstract setAnnotations(data: any[]): void;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 清除所有标注
|
|
36
|
+
*/
|
|
37
|
+
public abstract clear(): void;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 销毁插件,移除事件监听
|
|
41
|
+
*/
|
|
42
|
+
public abstract destroy(): void;
|
|
43
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import type { MedViewerEngine } from "./Engine";
|
|
2
|
+
import OpenSeadragon from "openseadragon";
|
|
3
|
+
|
|
4
|
+
export interface ColorAdjustments {
|
|
5
|
+
brightness?: number; // 0.0 ~ 2.0
|
|
6
|
+
contrast?: number; // 0.0 ~ 2.0
|
|
7
|
+
saturation?: number; // 0.0 ~ 3.0
|
|
8
|
+
gamma?: number; // 0.1 ~ 3.0
|
|
9
|
+
sharpen?: number; // 0.0 ~ 1.0
|
|
10
|
+
edgeEnhance?: number; // 0.0 ~ 1.0
|
|
11
|
+
pseudoColor?: boolean; // 伪彩色
|
|
12
|
+
invert?: boolean; // 反色
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ColorAdjustOptions {
|
|
16
|
+
initial?: ColorAdjustments;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class ColorAdjustPlugin {
|
|
20
|
+
private engine: MedViewerEngine;
|
|
21
|
+
private adjustments: ColorAdjustments;
|
|
22
|
+
private options: ColorAdjustOptions;
|
|
23
|
+
|
|
24
|
+
constructor(engine: MedViewerEngine, options: ColorAdjustOptions = {}) {
|
|
25
|
+
this.engine = engine;
|
|
26
|
+
this.options = options;
|
|
27
|
+
this.adjustments = options.initial ?? {};
|
|
28
|
+
|
|
29
|
+
engine.viewer.addOnceHandler("open", () => {
|
|
30
|
+
const isWebGL = this.isWebGLRenderer();
|
|
31
|
+
console.log("当前渲染模式:", isWebGL ? "WebGL" : "Canvas");
|
|
32
|
+
|
|
33
|
+
// 强制使用 WebGL 渲染器
|
|
34
|
+
if (!isWebGL) {
|
|
35
|
+
console.warn("ColorAdjustPlugin 需要 WebGL 渲染器,尝试切换...");
|
|
36
|
+
this.forceWebGLRenderer();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.apply(this.adjustments, true); // 强制走 WebGL 路径
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public setAdjustments(adjustments: ColorAdjustments): void {
|
|
44
|
+
this.adjustments = { ...this.adjustments, ...adjustments };
|
|
45
|
+
this.apply(this.adjustments, true); // 强制走 WebGL 路径
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public getAdjustments(): ColorAdjustments {
|
|
49
|
+
return { ...this.adjustments };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public destroy(): void {
|
|
53
|
+
this.adjustments = {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private apply(adjustments: ColorAdjustments, isWebGL: boolean = true): void {
|
|
57
|
+
if (!isWebGL) {
|
|
58
|
+
console.warn("Canvas 模式不支持色彩调节,请使用 WebGL 渲染器");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log("apply color adjustments:", adjustments);
|
|
63
|
+
|
|
64
|
+
// WebGL 模式:直接实现 Shader 调整逻辑
|
|
65
|
+
this.applyWebGLFilters(adjustments);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private isWebGLRenderer(): boolean {
|
|
69
|
+
const drawer = (this.engine.viewer as any)?.drawer;
|
|
70
|
+
return drawer?.constructor?.name === "WebGLDrawer";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private forceWebGLRenderer(): void {
|
|
74
|
+
// 尝试重新初始化 viewer 强制使用 WebGL
|
|
75
|
+
const viewer = this.engine.viewer;
|
|
76
|
+
const currentSource = (viewer as any).source;
|
|
77
|
+
const currentOptions = currentSource ? {
|
|
78
|
+
tileSource: currentSource,
|
|
79
|
+
drawer: ["webgl"], // 强制只使用 WebGL
|
|
80
|
+
} : {};
|
|
81
|
+
|
|
82
|
+
// 如果已经有图像,重新打开
|
|
83
|
+
if (currentSource) {
|
|
84
|
+
viewer.open(currentOptions);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private applyWebGLFilters(adjustments: ColorAdjustments): void {
|
|
89
|
+
const viewer = this.engine.viewer;
|
|
90
|
+
const drawer = (viewer as any).drawer;
|
|
91
|
+
|
|
92
|
+
if (!drawer) {
|
|
93
|
+
console.warn("WebGL drawer not available");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 尝试设置 WebGL Shader uniforms
|
|
98
|
+
// 这里需要根据 OpenSeadragon 5.0 的具体 API 来实现
|
|
99
|
+
// 可能的 API(需要查阅文档确认):
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
console.log("applyWebGLFilters", drawer);
|
|
103
|
+
console.log("drawer keys:", Object.keys(drawer));
|
|
104
|
+
console.log("drawer._gl:", drawer._gl);
|
|
105
|
+
console.log("drawer.canvas:", drawer.canvas);
|
|
106
|
+
// 方法1:直接设置 shader uniforms
|
|
107
|
+
if (drawer.setShaderUniforms) {
|
|
108
|
+
drawer.setShaderUniforms({
|
|
109
|
+
uBrightness: adjustments.brightness ?? 1.0,
|
|
110
|
+
uContrast: adjustments.contrast ?? 1.0,
|
|
111
|
+
uSaturation: adjustments.saturation ?? 1.0,
|
|
112
|
+
uGamma: adjustments.gamma ?? 1.0,
|
|
113
|
+
uSharpen: adjustments.sharpen ?? 0.0,
|
|
114
|
+
uEdgeEnhance: adjustments.edgeEnhance ?? 0.0,
|
|
115
|
+
uPseudoColor: adjustments.pseudoColor ?? false,
|
|
116
|
+
uInvert: adjustments.invert ?? false,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// 方法2:通过 WebGL context 直接操作
|
|
120
|
+
else if (drawer._gl) {
|
|
121
|
+
this.setupCustomShader(drawer._gl, adjustments);
|
|
122
|
+
}
|
|
123
|
+
// 方法2.5:尝试从 canvas 获取 WebGL context
|
|
124
|
+
else if (drawer.canvas) {
|
|
125
|
+
const gl = drawer.canvas.getContext('webgl') || drawer.canvas.getContext('experimental-webgl');
|
|
126
|
+
if (gl) {
|
|
127
|
+
this.setupCustomShader(gl as WebGLRenderingContext, adjustments);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// 方法3:通过滤镜 API
|
|
131
|
+
else if ((viewer as any).setFilterOptions) {
|
|
132
|
+
(viewer as any).setFilterOptions({
|
|
133
|
+
brightness: adjustments.brightness,
|
|
134
|
+
contrast: adjustments.contrast,
|
|
135
|
+
saturation: adjustments.saturation,
|
|
136
|
+
// ... 其他参数
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.warn("No WebGL shader API found, filters not applied");
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error("Error applying WebGL filters:", error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private setupCustomShader(gl: WebGLRenderingContext, adjustments: ColorAdjustments): void {
|
|
148
|
+
// 自定义 Shader 实现
|
|
149
|
+
console.log("Setting up custom WebGL shader with adjustments:", adjustments);
|
|
150
|
+
|
|
151
|
+
// 顶点着色器(简单的传递纹理坐标)
|
|
152
|
+
const vertexShaderSource = `
|
|
153
|
+
attribute vec2 a_position;
|
|
154
|
+
attribute vec2 a_texCoord;
|
|
155
|
+
varying vec2 v_texCoord;
|
|
156
|
+
|
|
157
|
+
void main() {
|
|
158
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
159
|
+
v_texCoord = a_texCoord;
|
|
160
|
+
}
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
// 片段着色器(实现色彩调节)
|
|
164
|
+
const fragmentShaderSource = `
|
|
165
|
+
precision mediump float;
|
|
166
|
+
|
|
167
|
+
uniform sampler2D u_texture;
|
|
168
|
+
uniform float u_brightness;
|
|
169
|
+
uniform float u_contrast;
|
|
170
|
+
uniform float u_saturation;
|
|
171
|
+
uniform float u_gamma;
|
|
172
|
+
uniform bool u_invert;
|
|
173
|
+
|
|
174
|
+
varying vec2 v_texCoord;
|
|
175
|
+
|
|
176
|
+
void main() {
|
|
177
|
+
vec4 color = texture2D(u_texture, v_texCoord);
|
|
178
|
+
|
|
179
|
+
// 亮度调节
|
|
180
|
+
color.rgb += u_brightness;
|
|
181
|
+
|
|
182
|
+
// 对比度调节
|
|
183
|
+
color.rgb = (color.rgb - 0.5) * u_contrast + 0.5;
|
|
184
|
+
|
|
185
|
+
// 饱和度调节
|
|
186
|
+
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
|
187
|
+
color.rgb = mix(vec3(gray), color.rgb, u_saturation);
|
|
188
|
+
|
|
189
|
+
// 伽马校正
|
|
190
|
+
color.rgb = pow(color.rgb, vec3(1.0 / u_gamma));
|
|
191
|
+
|
|
192
|
+
// 反色
|
|
193
|
+
if (u_invert) {
|
|
194
|
+
color.rgb = 1.0 - color.rgb;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
gl_FragColor = color;
|
|
198
|
+
}
|
|
199
|
+
`;
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
// 创建和编译着色器
|
|
203
|
+
const vertexShader = this.createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
|
|
204
|
+
const fragmentShader = this.createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
|
|
205
|
+
|
|
206
|
+
// 创建程序
|
|
207
|
+
const program = gl.createProgram();
|
|
208
|
+
gl.attachShader(program, vertexShader);
|
|
209
|
+
gl.attachShader(program, fragmentShader);
|
|
210
|
+
gl.linkProgram(program);
|
|
211
|
+
|
|
212
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
213
|
+
throw new Error('Failed to link shader program: ' + gl.getProgramInfoLog(program));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 设置 uniform 值
|
|
217
|
+
gl.useProgram(program);
|
|
218
|
+
|
|
219
|
+
// 设置纹理
|
|
220
|
+
gl.uniform1i(gl.getUniformLocation(program, 'u_texture'), 0);
|
|
221
|
+
|
|
222
|
+
// 设置调节参数
|
|
223
|
+
gl.uniform1f(gl.getUniformLocation(program, 'u_brightness'), (adjustments.brightness ?? 1.0) - 1.0);
|
|
224
|
+
gl.uniform1f(gl.getUniformLocation(program, 'u_contrast'), adjustments.contrast ?? 1.0);
|
|
225
|
+
gl.uniform1f(gl.getUniformLocation(program, 'u_saturation'), adjustments.saturation ?? 1.0);
|
|
226
|
+
gl.uniform1f(gl.getUniformLocation(program, 'u_gamma'), adjustments.gamma ?? 1.0);
|
|
227
|
+
gl.uniform1i(gl.getUniformLocation(program, 'u_invert'), (adjustments.invert ?? false) ? 1 : 0);
|
|
228
|
+
|
|
229
|
+
// 保存程序引用以便后续使用
|
|
230
|
+
(gl as any)._colorAdjustProgram = program;
|
|
231
|
+
|
|
232
|
+
console.log('WebGL shader setup completed');
|
|
233
|
+
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error('Error setting up WebGL shader:', error);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader {
|
|
240
|
+
const shader = gl.createShader(type);
|
|
241
|
+
if (!shader) {
|
|
242
|
+
throw new Error('Failed to create shader');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
gl.shaderSource(shader, source);
|
|
246
|
+
gl.compileShader(shader);
|
|
247
|
+
|
|
248
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
249
|
+
const info = gl.getShaderInfoLog(shader);
|
|
250
|
+
gl.deleteShader(shader);
|
|
251
|
+
throw new Error('Failed to compile shader: ' + info);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return shader;
|
|
255
|
+
}
|
|
256
|
+
}
|