dicom阅片比例尺修改
continuous-integration/drone/push Build is passing Details

main
wangxiaoshuang 2025-10-30 10:48:54 +08:00
parent 8590808cb9
commit a7b9218952
4 changed files with 455 additions and 24 deletions

View File

@ -661,6 +661,7 @@ export default {
},
sliderMouseup(e) {
this.sliderInfo.isMove = false
this.$emit('contentMouseup', e)
},
sliderMousedown(e) {
const boxHeight = this.$refs['sliderBox'].clientHeight

View File

@ -295,7 +295,7 @@
<Viewport :ref="`viewport-${index}`" :data-viewport-uid="`viewport-${index}`"
:rendering-engine-id="renderingEngineId" :viewport-id="`viewport-${index}`" :viewport-index="index"
@activeViewport="activeViewport" @toggleTaskByViewport="toggleTaskByViewport" @previewCD="previewCD"
@renderAnnotations="renderAnnotations" />
@renderAnnotations="renderAnnotations" @contentMouseup="contentMouseup" />
</div>
</div>
<div v-if="readingTool === 2" :class="['viewports-box', !isFusion ? 'viewports-box-down' : '']"
@ -308,7 +308,8 @@
:rendering-engine-id="renderingEngineId" :viewport-id="`viewport-fusion-${index}`"
:viewport-index="index" @activeViewport="activeViewport"
@toggleTaskByViewport="toggleTaskByViewport" @previewCD="previewCD"
@renderAnnotations="renderAnnotations" @upperRangeChange="upperRangeChange" />
@renderAnnotations="renderAnnotations" @upperRangeChange="upperRangeChange"
@contentMouseup="contentMouseup" />
</div>
</div>
</div>
@ -361,7 +362,8 @@
<!-- 手册 -->
<span>{{ $t('trials:reading:button:handbooks') }}</span>
<span style="position: absolute;right: 20px;font-size: 20px;">
<svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" style="margin-right:10px;cursor: pointer;"
<svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
style="margin-right:10px;cursor: pointer;"
@click="manualsDialog.isFullscreen = !manualsDialog.isFullscreen" />
<svg-icon icon-class="close" style="cursor: pointer;" @click="manualsDialog.visible = false" />
</span>
@ -460,6 +462,7 @@ import ClinicalData from '@/views/trials/trials-panel/reading/clinical-data'
import FusionForm from './FusionForm.vue'
import colorMap from './colorMap.vue'
import RectangleROITool from './tools/RectangleROITool'
import ScaleOverlayTool from './tools/ScaleOverlayTool'
import uploadDicomAndNonedicom from '@/components/uploadDicomAndNonedicom'
import downloadDicomAndNonedicom from '@/components/downloadDicomAndNonedicom'
import { getNetWorkSpeed, setNetWorkSpeedSizeAll, workSpeedclose } from "@/utils"
@ -470,7 +473,7 @@ const {
ToolGroupManager,
Enums: csToolsEnums,
StackScrollTool,
ScaleOverlayTool,
// ScaleOverlayTool,
PanTool,
ZoomTool,
WindowLevelTool,
@ -625,7 +628,12 @@ export default {
uploadStatus: 'upload',
taskId: '',
isReadingTaskViewInOrder: null,
trialCriterion: {}
trialCriterion: {},
curOperation: {
type: '',
annotation: null
}
}
},
computed: {
@ -1479,11 +1487,8 @@ export default {
if (isBound || this.isNumber(operateStateEnum)) {
this.$refs[`ecrf_${series.TaskInfo.VisitTaskId}`][0].updateAnnotationToQuestion(annotation)
} else {
if (this.saveCustomAnnotationTimer) {
clearTimeout(this.saveCustomAnnotationTimer)
this.saveCustomAnnotationTimer = null
}
this.saveCustomAnnotationTimer = setTimeout(() => { this.saveCustomAnnotation(annotation) }, 1000)
this.curOperation.type = 'Modified'
this.curOperation.annotation = annotation
}
}
this.setToolsPassive()
@ -1553,6 +1558,17 @@ export default {
console.log(e)
}
},
contentMouseup(e) {
console.log('contentMouseup')
if (this.curOperation.type === 'Modified') {
let annotation = this.curOperation.annotation
this.saveCustomAnnotation(annotation)
}
this.curOperation = {
type: '',
annotation: null
}
},
removeAnnotation(annotation) {
cornerstoneTools.annotation.state.removeAnnotation(annotation.annotationUID)
const renderingEngine = getRenderingEngine(renderingEngineId)
@ -3032,21 +3048,25 @@ export default {
let volumeId = null; let volume = null
const key = isFusion ? `fusion_${serie.SeriesInstanceUid}` : serie.SeriesInstanceUid
if (!this.volumeData[key] || !cache.getVolume(this.volumeData[key].volumeId)) {
if (serie.Modality === 'PT' && !isFusion) {
serie.ImageIds.forEach(async id => {
const imageLoadObject = cache.getImage(id)
if (imageLoadObject) {
await new Promise(res => {
cache.removeImageLoadObject(id, { force: true }) //
res()
})
}
})
// if (serie.Modality === 'PT' && !isFusion) {
// serie.ImageIds.forEach(async id => {
// const imageLoadObject = cache.getImage(id)
// if (imageLoadObject) {
// await new Promise(res => {
// cache.removeImageLoadObject(id, { force: true }) //
// res()
// })
// }
// })
// }
volumeId = `${isFusion ? 'fusion' : serie.Modality}Volume` + ':' + serie.SeriesInstanceUid
if (cache.getVolume(volumeId)) {
volume = cache.getVolume(volumeId)
} else {
await this.$refs[`viewport-fusion-0`][0].createImageIdsAndCacheMetaData(serie)
volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: serie.ImageIds })
volume.load()
}
await this.$refs[`viewport-fusion-0`][0].createImageIdsAndCacheMetaData(serie)
volumeId = `${isFusion ? 'fusion' : serie.Modality}Volume` + ':' + csUtils.uuidv4()
volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: serie.ImageIds })
volume.load()
this.volumeData[key] = {}
this.volumeData[key].volumeId = volumeId
this.volumeData[key].volume = volume

View File

@ -481,6 +481,7 @@ export default {
},
sliderMouseup(e) {
this.sliderInfo.isMove = false
this.$emit('contentMouseup', e)
},
sliderMousedown(e) {
const boxHeight = this.$refs['sliderBox'].clientHeight

View File

@ -0,0 +1,409 @@
// import AnnotationDisplayTool from './base/AnnotationDisplayTool';
import { vec3 } from 'gl-matrix';
import { getEnabledElementByIds, getRenderingEngines, utilities as csUtils, } from '@cornerstonejs/core';
// import { addAnnotation, getAnnotations, } from '../stateManagement/annotation/annotationState';
// import { drawLine as drawLineSvg, drawTextBox as drawTextBoxSvg, } from '../drawingSvg';
// import { getToolGroup } from '../store/ToolGroupManager';
import * as cornerstoneTools from '@cornerstonejs/tools'
const {
annotation,
drawing,
AnnotationDisplayTool,
ToolGroupManager,
} = cornerstoneTools
const { getAnnotations, addAnnotation } = annotation.state
const { getToolGroup } = ToolGroupManager
const { drawLine, drawTextBox } = drawing
const drawLineSvg = drawLine
const drawTextBoxSvg = drawTextBox
const viewportsWithAnnotations = [];
class ScaleOverlayTool extends AnnotationDisplayTool {
constructor(toolProps = {}, defaultToolProps = {
configuration: {
viewportId: '',
scaleLocation: 'bottom',
},
}) {
super(toolProps, defaultToolProps);
this.editData = null;
this._init = () => {
const renderingEngines = getRenderingEngines();
const renderingEngine = renderingEngines[0];
if (!renderingEngine) {
return;
}
const viewportIds = getToolGroup(this.toolGroupId).viewportsInfo;
if (!viewportIds) {
return;
}
const enabledElements = viewportIds.map((e) => getEnabledElementByIds(e.viewportId, e.renderingEngineId));
let { viewport } = enabledElements[0];
const { FrameOfReferenceUID } = enabledElements[0];
if (this.configuration.viewportId) {
enabledElements.forEach((element) => {
if (element.viewport.id == this.configuration.viewportId) {
viewport = element.viewport;
}
});
}
if (!viewport) {
return;
}
const { viewUp, viewPlaneNormal } = viewport.getCamera();
const viewportCanvasCornersInWorld = csUtils.getViewportImageCornersInWorld(viewport);
let annotation = this.editData?.annotation;
const annotations = getAnnotations(this.getToolName(), viewport.element);
if (annotations.length) {
annotation = annotations.filter((thisAnnotation) => thisAnnotation.data.viewportId == viewport.id)[0];
}
enabledElements.forEach((element) => {
const { viewport } = element;
if (!viewportsWithAnnotations.includes(viewport.id) || annotations.length === 0) {
const newAnnotation = {
metadata: {
toolName: this.getToolName(),
viewPlaneNormal: [...viewPlaneNormal],
viewUp: [...viewUp],
FrameOfReferenceUID,
referencedImageId: null,
},
data: {
handles: {
points: csUtils.getViewportImageCornersInWorld(viewport),
},
viewportId: viewport.id,
},
};
viewportsWithAnnotations.push(viewport.id);
addAnnotation(newAnnotation, viewport.element);
annotation = newAnnotation;
}
});
if (this.editData?.annotation &&
this.editData.annotation.data.viewportId == viewport.id) {
this.editData.annotation.data.handles.points =
viewportCanvasCornersInWorld;
this.editData.annotation.data.viewportId = viewport.id;
}
this.editData = {
viewport,
renderingEngine,
annotation,
};
};
this.onSetToolEnabled = () => {
this._init();
};
this.onCameraModified = (evt) => {
this.configuration.viewportId = evt.detail.viewportId;
this._init();
};
this.computeScaleSize = (worldWidthViewport, worldHeightViewport, location) => {
const scaleSizes = [
16000, 8000, 4000, 2000, 1000, 500, 250, 100, 50, 25, 10, 5, 2,
];
let currentScaleSize;
if (location == 'top' || location == 'bottom') {
currentScaleSize = scaleSizes.filter((scaleSize) => scaleSize < worldWidthViewport * 0.6 &&
scaleSize > worldWidthViewport * 0.2);
}
else {
currentScaleSize = scaleSizes.filter((scaleSize) => scaleSize < worldHeightViewport * 0.6 &&
scaleSize > worldHeightViewport * 0.2);
}
return currentScaleSize[0];
};
this.computeEndScaleTicks = (canvasCoordinates, location) => {
const locationTickOffset = {
bottom: [
[0, -10],
[0, -10],
],
top: [
[0, 10],
[0, 10],
],
left: [
[0, 0],
[10, 0],
],
right: [
[0, 0],
[-10, 0],
],
};
const endTick1 = [
[
canvasCoordinates[1][0] + locationTickOffset[location][0][0],
canvasCoordinates[1][1] + locationTickOffset[location][0][0],
],
[
canvasCoordinates[1][0] + locationTickOffset[location][1][0],
canvasCoordinates[1][1] + locationTickOffset[location][1][1],
],
];
const endTick2 = [
[
canvasCoordinates[0][0] + locationTickOffset[location][0][0],
canvasCoordinates[0][1] + locationTickOffset[location][0][0],
],
[
canvasCoordinates[0][0] + locationTickOffset[location][1][0],
canvasCoordinates[0][1] + locationTickOffset[location][1][1],
],
];
return {
endTick1: endTick1,
endTick2: endTick2,
};
};
this.computeInnerScaleTicks = (scaleSize, location, annotationUID, leftTick, rightTick) => {
let canvasScaleSize;
if (location == 'bottom' || location == 'top') {
canvasScaleSize = rightTick[0][0] - leftTick[0][0];
}
else if (location == 'left' || location == 'right') {
canvasScaleSize = rightTick[0][1] - leftTick[0][1];
}
const tickIds = [];
const tickUIDs = [];
const tickCoordinates = [];
let numberSmallTicks = scaleSize;
if (scaleSize >= 50) {
numberSmallTicks = scaleSize / 10;
}
const tickSpacing = canvasScaleSize / numberSmallTicks;
for (let i = 0; i < numberSmallTicks - 1; i++) {
const locationOffset = {
bottom: [
[tickSpacing * (i + 1), 0],
[tickSpacing * (i + 1), 5],
],
top: [
[tickSpacing * (i + 1), 0],
[tickSpacing * (i + 1), -5],
],
left: [
[0, tickSpacing * (i + 1)],
[-5, tickSpacing * (i + 1)],
],
right: [
[0, tickSpacing * (i + 1)],
[5, tickSpacing * (i + 1)],
],
};
tickIds.push(`${annotationUID}-tick${i}`);
tickUIDs.push(`tick${i}`);
if ((i + 1) % 5 == 0) {
tickCoordinates.push([
[
leftTick[0][0] + locationOffset[location][0][0],
leftTick[0][1] + locationOffset[location][0][1],
],
[
leftTick[1][0] + locationOffset[location][0][0],
leftTick[1][1] + locationOffset[location][0][1],
],
]);
}
else {
tickCoordinates.push([
[
leftTick[0][0] + locationOffset[location][0][0],
leftTick[0][1] + locationOffset[location][0][1],
],
[
leftTick[1][0] + locationOffset[location][1][0],
leftTick[1][1] + locationOffset[location][1][1],
],
]);
}
}
return { tickIds, tickUIDs, tickCoordinates };
};
this.computeWorldScaleCoordinates = (scaleSize, location, pointSet) => {
let worldCoordinates;
let topBottomVec = vec3.subtract(vec3.create(), pointSet[0], pointSet[1]);
topBottomVec = vec3.normalize(vec3.create(), topBottomVec);
let topRightVec = vec3.subtract(vec3.create(), pointSet[2], pointSet[0]);
topRightVec = vec3.normalize(vec3.create(), topRightVec);
const midpointLocation = {
bottom: [pointSet[1], pointSet[2]],
top: [pointSet[0], pointSet[3]],
right: [pointSet[2], pointSet[3]],
left: [pointSet[0], pointSet[1]],
};
const midpoint = vec3
.add(vec3.create(), midpointLocation[location][0], midpointLocation[location][0])
.map((i) => i / 2);
const offset = scaleSize /
2 /
Math.sqrt(Math.pow(topBottomVec[0], 2) +
Math.pow(topBottomVec[1], 2) +
Math.pow(topBottomVec[2], 2));
if (location == 'top' || location == 'bottom') {
worldCoordinates = [
vec3.subtract(vec3.create(), midpoint, topRightVec.map((i) => i * offset)),
vec3.add(vec3.create(), midpoint, topRightVec.map((i) => i * offset)),
];
}
else if (location == 'left' || location == 'right') {
worldCoordinates = [
vec3.add(vec3.create(), midpoint, topBottomVec.map((i) => i * offset)),
vec3.subtract(vec3.create(), midpoint, topBottomVec.map((i) => i * offset)),
];
}
return worldCoordinates;
};
this.computeCanvasScaleCoordinates = (canvasSize, canvasCoordinates, vscaleBounds, hscaleBounds, location) => {
let scaleCanvasCoordinates;
if (location == 'top' || location == 'bottom') {
const worldDistanceOnCanvas = canvasCoordinates[0][0] - canvasCoordinates[1][0];
scaleCanvasCoordinates = [
[canvasSize.width / 2 - worldDistanceOnCanvas / 2, vscaleBounds.height],
[canvasSize.width / 2 + worldDistanceOnCanvas / 2, vscaleBounds.height],
];
}
else if (location == 'left' || location == 'right') {
const worldDistanceOnCanvas = canvasCoordinates[0][1] - canvasCoordinates[1][1];
scaleCanvasCoordinates = [
[hscaleBounds.width, canvasSize.height / 2 - worldDistanceOnCanvas / 2],
[hscaleBounds.width, canvasSize.height / 2 + worldDistanceOnCanvas / 2],
];
}
return scaleCanvasCoordinates;
};
this.computeScaleBounds = (canvasSize, horizontalReduction, verticalReduction, location) => {
const hReduction = horizontalReduction * Math.min(1000, canvasSize.width);
const vReduction = verticalReduction * Math.min(1000, canvasSize.height);
const locationBounds = {
bottom: [-vReduction, -hReduction],
top: [vReduction, hReduction],
left: [vReduction, hReduction],
right: [-vReduction, -hReduction],
};
const canvasBounds = {
bottom: [canvasSize.height, canvasSize.width],
top: [0, canvasSize.width],
left: [canvasSize.height, 0],
right: [canvasSize.height, canvasSize.width],
};
return {
height: canvasBounds[location][0] + locationBounds[location][0],
width: canvasBounds[location][1] + locationBounds[location][1],
};
};
}
renderAnnotation(enabledElement, svgDrawingHelper) {
if (!this.editData || !this.editData.viewport) {
return;
}
const location = this.configuration.scaleLocation;
const { viewport } = enabledElement;
const annotations = getAnnotations(this.getToolName(), viewport.element);
const annotation = annotations.filter((thisAnnotation) => thisAnnotation.data.viewportId == viewport.id)[0];
const canvas = enabledElement.viewport.canvas;
const renderStatus = false;
if (!viewport) {
return renderStatus;
}
const styleSpecifier = {
toolGroupId: this.toolGroupId,
toolName: this.getToolName(),
viewportId: enabledElement.viewport.id,
};
const canvasSize = {
width: canvas.width / window.devicePixelRatio || 1,
height: canvas.height / window.devicePixelRatio || 1,
};
const topLeft = annotation.data.handles.points[0];
const topRight = annotation.data.handles.points[1];
const bottomLeft = annotation.data.handles.points[2];
const bottomRight = annotation.data.handles.points[3];
const pointSet1 = [topLeft, bottomLeft, topRight, bottomRight];
const worldWidthViewport = vec3.distance(bottomLeft, bottomRight);
const worldHeightViewport = vec3.distance(topLeft, bottomLeft);
const hscaleBounds = this.computeScaleBounds(canvasSize, 0.05, 0.05, location);
const vscaleBounds = this.computeScaleBounds(canvasSize, 0.05, 0.05, location);
const scaleSize = this.computeScaleSize(worldWidthViewport, worldHeightViewport, location);
const canvasCoordinates = this.computeWorldScaleCoordinates(scaleSize, location, pointSet1).map((world) => viewport.worldToCanvas(world));
const scaleCanvasCoordinates = this.computeCanvasScaleCoordinates(canvasSize, canvasCoordinates, vscaleBounds, hscaleBounds, location);
const scaleTicks = this.computeEndScaleTicks(scaleCanvasCoordinates, location);
const { annotationUID } = annotation;
styleSpecifier.annotationUID = annotationUID;
const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
const color = this.getStyle('color', styleSpecifier, annotation);
const shadow = this.getStyle('shadow', styleSpecifier, annotation);
const scaleId = `${annotationUID}-scaleline`;
const scaleLineUID = '1';
drawLineSvg(svgDrawingHelper, annotationUID, scaleLineUID, scaleCanvasCoordinates[0], scaleCanvasCoordinates[1], {
color,
width: lineWidth,
lineDash,
shadow,
}, scaleId);
const leftTickId = `${annotationUID}-left`;
const leftTickUID = '2';
drawLineSvg(svgDrawingHelper, annotationUID, leftTickUID, scaleTicks.endTick1[0], scaleTicks.endTick1[1], {
color,
width: lineWidth,
lineDash,
shadow,
}, leftTickId);
const rightTickId = `${annotationUID}-right`;
const rightTickUID = '3';
drawLineSvg(svgDrawingHelper, annotationUID, rightTickUID, scaleTicks.endTick2[0], scaleTicks.endTick2[1], {
color,
width: lineWidth,
lineDash,
shadow,
}, rightTickId);
const locationTextOffest = {
bottom: [-10, -42],
top: [-12, -35],
left: [-40, -20],
right: [-50, -20],
};
const textCanvasCoordinates = [
scaleCanvasCoordinates[0][0] + locationTextOffest[location][0],
scaleCanvasCoordinates[0][1] + locationTextOffest[location][1],
];
const textBoxLines = this._getTextLines(scaleSize);
const { tickIds, tickUIDs, tickCoordinates } = this.computeInnerScaleTicks(scaleSize, location, annotationUID, scaleTicks.endTick1, scaleTicks.endTick2);
for (let i = 0; i < tickUIDs.length; i++) {
drawLineSvg(svgDrawingHelper, annotationUID, tickUIDs[i], tickCoordinates[i][0], tickCoordinates[i][1], {
color,
width: lineWidth,
lineDash,
shadow,
}, tickIds[i]);
}
const textUID = 'text0';
drawTextBoxSvg(svgDrawingHelper, annotationUID, textUID, textBoxLines, [textCanvasCoordinates[0], textCanvasCoordinates[1]], {
fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
fontSize: '14px',
lineDash: '2,3',
lineWidth: '1',
shadow: true,
color: color,
});
return renderStatus;
}
_getTextLines(scaleSize) {
let scaleSizeDisplayValue;
let scaleSizeUnits;
if (scaleSize >= 50) {
scaleSizeDisplayValue = scaleSize / 10;
scaleSizeUnits = ' cm';
}
else {
scaleSizeDisplayValue = scaleSize;
scaleSizeUnits = ' mm';
}
const textLines = [scaleSizeDisplayValue.toString().concat(scaleSizeUnits)];
return textLines;
}
}
ScaleOverlayTool.toolName = 'ScaleOverlay';
export default ScaleOverlayTool;