|
@@ -300,10 +300,10 @@ export default class TTAMeasurementTool extends AnnotationTool {
|
|
|
},
|
|
},
|
|
|
textBoxPosition: annotation.data.textBox,
|
|
textBoxPosition: annotation.data.textBox,
|
|
|
metadata: {
|
|
metadata: {
|
|
|
- viewPlaneNormal: annotation.metadata?.viewPlaneNormal || [0, 0, 1],
|
|
|
|
|
- viewUp: annotation.metadata?.viewUp || [0, 1, 0],
|
|
|
|
|
- FrameOfReferenceUID: annotation.metadata?.FrameOfReferenceUID || '',
|
|
|
|
|
- referencedImageId: annotation.metadata?.referencedImageId || '',
|
|
|
|
|
|
|
+ viewPlaneNormal: annotation.metadata.viewPlaneNormal || [0, 0, 1],
|
|
|
|
|
+ viewUp: annotation.metadata.viewUp || [0, 1, 0],
|
|
|
|
|
+ FrameOfReferenceUID: annotation.metadata.FrameOfReferenceUID || '',
|
|
|
|
|
+ referencedImageId: annotation.metadata.referencedImageId || '',
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
@@ -673,11 +673,11 @@ export default class TTAMeasurementTool extends AnnotationTool {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 优先级3: 检测线段悬停
|
|
// 优先级3: 检测线段悬停
|
|
|
- if (cachedStats?.tibialCircle && cachedStats?.condyleCircle1) {
|
|
|
|
|
- const M1 = cachedStats.tibialCircle.center;
|
|
|
|
|
|
|
+ if (cachedStats?.condyleCircle1 && cachedStats?.condyleCircle2) {
|
|
|
const M2 = cachedStats.condyleCircle1.center;
|
|
const M2 = cachedStats.condyleCircle1.center;
|
|
|
-
|
|
|
|
|
- if (this._isPointNearLine(canvasCoords, M1, M2, viewport, 10)) {
|
|
|
|
|
|
|
+ const M3 = cachedStats.condyleCircle2.center;
|
|
|
|
|
+
|
|
|
|
|
+ if (this._isPointNearLine(canvasCoords, M2, M3, viewport, 10)) {
|
|
|
element.style.cursor = cursorUrl;
|
|
element.style.cursor = cursorUrl;
|
|
|
customAnn.highlighted = true;
|
|
customAnn.highlighted = true;
|
|
|
isHovering = true;
|
|
isHovering = true;
|
|
@@ -687,13 +687,37 @@ export default class TTAMeasurementTool extends AnnotationTool {
|
|
|
|
|
|
|
|
if (cachedStats?.parallelLine && customAnn.data.handles.points.length >= 10) {
|
|
if (cachedStats?.parallelLine && customAnn.data.handles.points.length >= 10) {
|
|
|
const point10 = customAnn.data.handles.points[9];
|
|
const point10 = customAnn.data.handles.points[9];
|
|
|
- const lineEnd = vectorAdd(point10, cachedStats.parallelLine.direction);
|
|
|
|
|
-
|
|
|
|
|
- if (this._isPointNearLine(canvasCoords, point10, lineEnd, viewport, 10)) {
|
|
|
|
|
- element.style.cursor = cursorUrl;
|
|
|
|
|
- customAnn.highlighted = true;
|
|
|
|
|
- isHovering = true;
|
|
|
|
|
- break;
|
|
|
|
|
|
|
+ const lineDirection = cachedStats.parallelLine.direction;
|
|
|
|
|
+
|
|
|
|
|
+ // 计算方向向量的长度
|
|
|
|
|
+ const directionLength = Math.sqrt(
|
|
|
|
|
+ lineDirection[0] * lineDirection[0] + lineDirection[1] * lineDirection[1]
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (directionLength > 0) {
|
|
|
|
|
+ // 归一化方向向量
|
|
|
|
|
+ const normalizedDirection = [
|
|
|
|
|
+ lineDirection[0] / directionLength,
|
|
|
|
|
+ lineDirection[1] / directionLength,
|
|
|
|
|
+ 0
|
|
|
|
|
+ ] as CoreTypes.Point3;
|
|
|
|
|
+
|
|
|
|
|
+ // 计算平行线的端点(从point10开始,沿着方向延伸100px)
|
|
|
|
|
+ const lineLength = 100; // 像素长度
|
|
|
|
|
+ const worldLineLength = lineLength / viewport.getZoom(); // 转换为世界坐标长度
|
|
|
|
|
+
|
|
|
|
|
+ const lineEndPoint = [
|
|
|
|
|
+ point10[0] + normalizedDirection[0] * worldLineLength,
|
|
|
|
|
+ point10[1] + normalizedDirection[1] * worldLineLength,
|
|
|
|
|
+ point10[2]
|
|
|
|
|
+ ] as CoreTypes.Point3;
|
|
|
|
|
+
|
|
|
|
|
+ if (this._isPointNearLine(canvasCoords, point10, lineEndPoint, viewport, 10)) {
|
|
|
|
|
+ element.style.cursor = cursorUrl;
|
|
|
|
|
+ customAnn.highlighted = true;
|
|
|
|
|
+ isHovering = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -872,12 +896,12 @@ export default class TTAMeasurementTool extends AnnotationTool {
|
|
|
const cachedStats = targetId ? customAnn.data.cachedStats?.[targetId] : undefined;
|
|
const cachedStats = targetId ? customAnn.data.cachedStats?.[targetId] : undefined;
|
|
|
const points = customAnn.data.handles.points;
|
|
const points = customAnn.data.handles.points;
|
|
|
|
|
|
|
|
- // 检测 M1M2 连线
|
|
|
|
|
- if (cachedStats?.tibialCircle && cachedStats?.condyleCircle1) {
|
|
|
|
|
- const M1 = cachedStats.tibialCircle.center;
|
|
|
|
|
|
|
+ // 检测 M2M3 连线
|
|
|
|
|
+ if (cachedStats?.condyleCircle1 && cachedStats?.condyleCircle2) {
|
|
|
const M2 = cachedStats.condyleCircle1.center;
|
|
const M2 = cachedStats.condyleCircle1.center;
|
|
|
-
|
|
|
|
|
- if (this._isPointNearLine(canvasCoords, M1, M2, viewport, 10)) {
|
|
|
|
|
|
|
+ const M3 = cachedStats.condyleCircle2.center;
|
|
|
|
|
+
|
|
|
|
|
+ if (this._isPointNearLine(canvasCoords, M2, M3, viewport, 10)) {
|
|
|
this._setupWholeToolMove(customAnn, element, viewport, canvasCoords, points[0]);
|
|
this._setupWholeToolMove(customAnn, element, viewport, canvasCoords, points[0]);
|
|
|
anyToolClicked = true;
|
|
anyToolClicked = true;
|
|
|
evt.preventDefault();
|
|
evt.preventDefault();
|
|
@@ -1318,8 +1342,8 @@ export default class TTAMeasurementTool extends AnnotationTool {
|
|
|
const M2 = condyleCircle1.center;
|
|
const M2 = condyleCircle1.center;
|
|
|
const M3 = condyleCircle2.center;
|
|
const M3 = condyleCircle2.center;
|
|
|
|
|
|
|
|
- // 计算M1M2的中点M
|
|
|
|
|
- const M = calculateMidpoint(M1, M2);
|
|
|
|
|
|
|
+ // 计算M2M3的中点M
|
|
|
|
|
+ const M = calculateMidpoint(M2, M3);
|
|
|
|
|
|
|
|
// 计算MM1的方向向量
|
|
// 计算MM1的方向向量
|
|
|
const MM1_direction = vectorSubtract(M1, M);
|
|
const MM1_direction = vectorSubtract(M1, M);
|
|
@@ -1384,7 +1408,7 @@ export default class TTAMeasurementTool extends AnnotationTool {
|
|
|
condyle2: 'rgb(0, 255, 0)', // 绿色
|
|
condyle2: 'rgb(0, 255, 0)', // 绿色
|
|
|
point10: 'rgb(255, 255, 0)', // 黄色
|
|
point10: 'rgb(255, 255, 0)', // 黄色
|
|
|
point11: 'rgb(255, 0, 255)', // 紫色
|
|
point11: 'rgb(255, 0, 255)', // 紫色
|
|
|
- m1m2Line: 'rgb(255, 165, 0)', // 橙色
|
|
|
|
|
|
|
+ m3m2Line: 'rgb(255, 165, 0)', // 橙色
|
|
|
parallelLine: 'rgb(255, 255, 0)', // 黄色
|
|
parallelLine: 'rgb(255, 255, 0)', // 黄色
|
|
|
perpLine: 'rgb(255, 0, 255)', // 紫色
|
|
perpLine: 'rgb(255, 0, 255)', // 紫色
|
|
|
center: 'rgb(255, 255, 255)', // 白色
|
|
center: 'rgb(255, 255, 255)', // 白色
|
|
@@ -1519,18 +1543,18 @@ export default class TTAMeasurementTool extends AnnotationTool {
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 绘制M1M2线段和中点
|
|
|
|
|
- if (cachedStats?.tibialCircle && cachedStats?.condyleCircle1 && annotationUID) {
|
|
|
|
|
- const M1Canvas = viewport.worldToCanvas(cachedStats.tibialCircle.center);
|
|
|
|
|
|
|
+ // 绘制M2M3线段和中点
|
|
|
|
|
+ if (cachedStats?.condyleCircle1 && cachedStats?.condyleCircle2 && annotationUID) {
|
|
|
const M2Canvas = viewport.worldToCanvas(cachedStats.condyleCircle1.center);
|
|
const M2Canvas = viewport.worldToCanvas(cachedStats.condyleCircle1.center);
|
|
|
|
|
+ const M3Canvas = viewport.worldToCanvas(cachedStats.condyleCircle2.center);
|
|
|
|
|
|
|
|
drawLineSvg(
|
|
drawLineSvg(
|
|
|
svgDrawingHelper,
|
|
svgDrawingHelper,
|
|
|
annotationUID,
|
|
annotationUID,
|
|
|
- `${annotationUID}-M1M2Line`,
|
|
|
|
|
- M1Canvas,
|
|
|
|
|
|
|
+ `${annotationUID}-M2M3Line`,
|
|
|
M2Canvas,
|
|
M2Canvas,
|
|
|
- { color: colors.m1m2Line, width: 2 }
|
|
|
|
|
|
|
+ M3Canvas,
|
|
|
|
|
+ { color: colors.m3m2Line, width: 2 }
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
if (cachedStats.midPoint) {
|
|
if (cachedStats.midPoint) {
|
|
@@ -1541,7 +1565,7 @@ export default class TTAMeasurementTool extends AnnotationTool {
|
|
|
`${annotationUID}-midPoint`,
|
|
`${annotationUID}-midPoint`,
|
|
|
MCanvas,
|
|
MCanvas,
|
|
|
5,
|
|
5,
|
|
|
- { color: colors.m1m2Line, fill: colors.m1m2Line }
|
|
|
|
|
|
|
+ { color: colors.m3m2Line, fill: colors.m3m2Line }
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -1562,35 +1586,51 @@ export default class TTAMeasurementTool extends AnnotationTool {
|
|
|
`${annotationUID}-referenceLine`,
|
|
`${annotationUID}-referenceLine`,
|
|
|
startCanvas,
|
|
startCanvas,
|
|
|
endCanvas,
|
|
endCanvas,
|
|
|
- { color: colors.m1m2Line, width: 2 }
|
|
|
|
|
|
|
+ { color: colors.m3m2Line, width: 2 }
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 绘制平行线L(从点10到垂足)
|
|
|
|
|
|
|
+ // 绘制平行线L(从点10开始,沿着MM1方向延伸固定长度)
|
|
|
if (cachedStats?.parallelLine && points.length >= 11 && annotationUID) {
|
|
if (cachedStats?.parallelLine && points.length >= 11 && annotationUID) {
|
|
|
const point10Canvas = canvasPoints[9];
|
|
const point10Canvas = canvasPoints[9];
|
|
|
const point10 = points[9];
|
|
const point10 = points[9];
|
|
|
const lineDirection = cachedStats.parallelLine.direction;
|
|
const lineDirection = cachedStats.parallelLine.direction;
|
|
|
|
|
|
|
|
- // 计算垂足
|
|
|
|
|
- const AP = vectorSubtract(points[10], point10);
|
|
|
|
|
- const dotProduct = AP[0] * lineDirection[0] + AP[1] * lineDirection[1];
|
|
|
|
|
- const directionMagSq = lineDirection[0] * lineDirection[0] + lineDirection[1] * lineDirection[1];
|
|
|
|
|
- const t = dotProduct / directionMagSq;
|
|
|
|
|
|
|
+ // 计算方向向量的长度
|
|
|
|
|
+ const directionLength = Math.sqrt(
|
|
|
|
|
+ lineDirection[0] * lineDirection[0] + lineDirection[1] * lineDirection[1]
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
- const footPoint = [
|
|
|
|
|
- point10[0] + t * lineDirection[0],
|
|
|
|
|
- point10[1] + t * lineDirection[1],
|
|
|
|
|
|
|
+ // 如果方向向量长度为0,则不绘制
|
|
|
|
|
+ if (directionLength === 0) {
|
|
|
|
|
+ return renderStatus;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 归一化方向向量
|
|
|
|
|
+ const normalizedDirection = [
|
|
|
|
|
+ lineDirection[0] / directionLength,
|
|
|
|
|
+ lineDirection[1] / directionLength,
|
|
|
|
|
+ 0
|
|
|
|
|
+ ] as CoreTypes.Point3;
|
|
|
|
|
+
|
|
|
|
|
+ // 计算平行线的端点(从point10开始,沿着方向延伸100px)
|
|
|
|
|
+ const lineLength = 100; // 像素长度
|
|
|
|
|
+ const worldLineLength = lineLength / viewport.getZoom(); // 转换为世界坐标长度
|
|
|
|
|
+
|
|
|
|
|
+ const lineEndPoint = [
|
|
|
|
|
+ point10[0] + normalizedDirection[0] * worldLineLength,
|
|
|
|
|
+ point10[1] + normalizedDirection[1] * worldLineLength,
|
|
|
point10[2]
|
|
point10[2]
|
|
|
] as CoreTypes.Point3;
|
|
] as CoreTypes.Point3;
|
|
|
- const footPointCanvas = viewport.worldToCanvas(footPoint);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const lineEndPointCanvas = viewport.worldToCanvas(lineEndPoint);
|
|
|
|
|
|
|
|
drawLineSvg(
|
|
drawLineSvg(
|
|
|
svgDrawingHelper,
|
|
svgDrawingHelper,
|
|
|
annotationUID,
|
|
annotationUID,
|
|
|
`${annotationUID}-parallelLine`,
|
|
`${annotationUID}-parallelLine`,
|
|
|
point10Canvas,
|
|
point10Canvas,
|
|
|
- footPointCanvas,
|
|
|
|
|
|
|
+ lineEndPointCanvas,
|
|
|
{
|
|
{
|
|
|
color: colors.parallelLine,
|
|
color: colors.parallelLine,
|
|
|
width: 2,
|
|
width: 2,
|