|
@@ -25,6 +25,7 @@ import {
|
|
|
vectorAdd,
|
|
vectorAdd,
|
|
|
vectorScale,
|
|
vectorScale,
|
|
|
calculateDistance,
|
|
calculateDistance,
|
|
|
|
|
+ dotProduct,
|
|
|
} from './mathUtils';
|
|
} from './mathUtils';
|
|
|
|
|
|
|
|
import {
|
|
import {
|
|
@@ -49,7 +50,7 @@ interface VHSMeasurementAnnotation extends Types.Annotation {
|
|
|
handles: {
|
|
handles: {
|
|
|
points: CoreTypes.Point3[]; // 6个点的世界坐标
|
|
points: CoreTypes.Point3[]; // 6个点的世界坐标
|
|
|
activeHandleIndex: number | null;
|
|
activeHandleIndex: number | null;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
};
|
|
};
|
|
|
textBox?: CoreTypes.Point2; // 文本框位置(canvas坐标)
|
|
textBox?: CoreTypes.Point2; // 文本框位置(canvas坐标)
|
|
|
cachedStats?: {
|
|
cachedStats?: {
|
|
@@ -144,7 +145,7 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
// 点1、2: 胸椎线(竖直)
|
|
// 点1、2: 胸椎线(竖直)
|
|
|
[spineX, spineY - spineLineLength / 2],
|
|
[spineX, spineY - spineLineLength / 2],
|
|
|
[spineX, spineY + spineLineLength / 2],
|
|
[spineX, spineY + spineLineLength / 2],
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 点3、4: 心脏长轴(可配置角度)
|
|
// 点3、4: 心脏长轴(可配置角度)
|
|
|
[
|
|
[
|
|
|
heartCenterX - (longAxisLength / 2) * Math.cos(angleRad),
|
|
heartCenterX - (longAxisLength / 2) * Math.cos(angleRad),
|
|
@@ -154,7 +155,7 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
heartCenterX + (longAxisLength / 2) * Math.cos(angleRad),
|
|
heartCenterX + (longAxisLength / 2) * Math.cos(angleRad),
|
|
|
heartCenterY + (longAxisLength / 2) * Math.sin(angleRad)
|
|
heartCenterY + (longAxisLength / 2) * Math.sin(angleRad)
|
|
|
],
|
|
],
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 点5、6: 心脏短轴(垂直于长轴)
|
|
// 点5、6: 心脏短轴(垂直于长轴)
|
|
|
[
|
|
[
|
|
|
heartCenterX - (shortAxisLength / 2) * Math.cos(perpAngleRad),
|
|
heartCenterX - (shortAxisLength / 2) * Math.cos(perpAngleRad),
|
|
@@ -173,7 +174,7 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
|
|
|
|
|
const camera = viewport.getCamera();
|
|
const camera = viewport.getCamera();
|
|
|
const { viewPlaneNormal, viewUp } = camera;
|
|
const { viewPlaneNormal, viewUp } = camera;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if (viewPlaneNormal === undefined) {
|
|
if (viewPlaneNormal === undefined) {
|
|
|
throw new Error('viewPlaneNormal is undefined');
|
|
throw new Error('viewPlaneNormal is undefined');
|
|
|
}
|
|
}
|
|
@@ -355,7 +356,7 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
): boolean {
|
|
): boolean {
|
|
|
const [x, y] = canvasPoint;
|
|
const [x, y] = canvasPoint;
|
|
|
const [tx, ty] = textBoxPosition;
|
|
const [tx, ty] = textBoxPosition;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
const margin = 10;
|
|
const margin = 10;
|
|
|
return (
|
|
return (
|
|
|
x >= tx - margin &&
|
|
x >= tx - margin &&
|
|
@@ -422,58 +423,103 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
): void {
|
|
): void {
|
|
|
const points = annotation.data.handles.points;
|
|
const points = annotation.data.handles.points;
|
|
|
|
|
|
|
|
- // 只对短轴点(索引4或5)应用垂直约束
|
|
|
|
|
- if (draggedPointIndex !== 4 && draggedPointIndex !== 5) {
|
|
|
|
|
|
|
+ // 情况1: 拖拽长轴点(索引2或3) - 更新长轴并重新计算短轴保持垂直
|
|
|
|
|
+ if (draggedPointIndex === 2 || draggedPointIndex === 3) {
|
|
|
|
|
+ // 更新被拖拽的长轴点
|
|
|
points[draggedPointIndex] = newPosition;
|
|
points[draggedPointIndex] = newPosition;
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- // 计算长轴方向向量
|
|
|
|
|
- const longAxisVector = vectorSubtract(points[3], points[2]);
|
|
|
|
|
- const longAxisLength = calculateDistance(points[2], points[3]);
|
|
|
|
|
|
|
+ // 计算新的长轴方向向量
|
|
|
|
|
+ const longAxisVector = vectorSubtract(points[3], points[2]);
|
|
|
|
|
+ const longAxisLength = calculateDistance(points[2], points[3]);
|
|
|
|
|
+
|
|
|
|
|
+ if (longAxisLength === 0) {
|
|
|
|
|
+ // 长轴长度为0,无法计算垂直方向
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 归一化长轴向量
|
|
|
|
|
+ const normalizedLongAxis = vectorScale(longAxisVector, 1 / longAxisLength);
|
|
|
|
|
+
|
|
|
|
|
+ // 计算垂直向量(在2D平面内)
|
|
|
|
|
+ const perpendicularVector: Point3 = [
|
|
|
|
|
+ -normalizedLongAxis[1],
|
|
|
|
|
+ normalizedLongAxis[0],
|
|
|
|
|
+ 0
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ // 获取短轴的当前中点和长度
|
|
|
|
|
+ const shortAxisMidpoint = calculateMidpoint(points[4], points[5]);
|
|
|
|
|
+ const currentShortAxisLength = calculateDistance(points[4], points[5]);
|
|
|
|
|
+ const shortAxisHalfLength = currentShortAxisLength / 2;
|
|
|
|
|
+
|
|
|
|
|
+ // 重新计算短轴的两个点,使其垂直于新的长轴方向
|
|
|
|
|
+ points[4] = vectorAdd(
|
|
|
|
|
+ shortAxisMidpoint,
|
|
|
|
|
+ vectorScale(perpendicularVector, -shortAxisHalfLength)
|
|
|
|
|
+ );
|
|
|
|
|
+ points[5] = vectorAdd(
|
|
|
|
|
+ shortAxisMidpoint,
|
|
|
|
|
+ vectorScale(perpendicularVector, shortAxisHalfLength)
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
- if (longAxisLength === 0) {
|
|
|
|
|
- // 长轴长度为0,无法计算垂直方向
|
|
|
|
|
- points[draggedPointIndex] = newPosition;
|
|
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 归一化长轴向量
|
|
|
|
|
- const normalizedLongAxis = vectorScale(longAxisVector, 1 / longAxisLength);
|
|
|
|
|
|
|
+ // 情况2: 拖拽短轴固定端点(索引4) - 只能沿垂直于长轴的方向移动,位置端点不动
|
|
|
|
|
+ if (draggedPointIndex === 4) {
|
|
|
|
|
+ // 计算长轴方向向量
|
|
|
|
|
+ const longAxisVector = vectorSubtract(points[3], points[2]);
|
|
|
|
|
+ const longAxisLength = calculateDistance(points[2], points[3]);
|
|
|
|
|
|
|
|
- // 计算垂直向量(在2D平面内)
|
|
|
|
|
- const perpendicularVector: Point3 = [
|
|
|
|
|
- -normalizedLongAxis[1],
|
|
|
|
|
- normalizedLongAxis[0],
|
|
|
|
|
- 0
|
|
|
|
|
- ];
|
|
|
|
|
|
|
+ if (longAxisLength === 0) {
|
|
|
|
|
+ // 长轴长度为0,无法计算垂直方向
|
|
|
|
|
+ points[draggedPointIndex] = newPosition;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算垂直向量(在2D平面内)
|
|
|
|
|
+ const perpendicularVector: Point3 = [
|
|
|
|
|
+ -longAxisVector[1],
|
|
|
|
|
+ longAxisVector[0],
|
|
|
|
|
+ 0
|
|
|
|
|
+ ];
|
|
|
|
|
|
|
|
- // 计算短轴的当前中点
|
|
|
|
|
- const shortAxisMidpoint = calculateMidpoint(points[4], points[5]);
|
|
|
|
|
|
|
+ // 计算拖拽向量在垂直方向上的投影
|
|
|
|
|
+ const dragVector = vectorSubtract(newPosition, points[4]);
|
|
|
|
|
+ const projection = dotProduct(dragVector, perpendicularVector) / dotProduct(perpendicularVector, perpendicularVector);
|
|
|
|
|
|
|
|
- // 计算新拖拽位置到短轴中点的向量
|
|
|
|
|
- const dragVector = vectorSubtract(newPosition, shortAxisMidpoint);
|
|
|
|
|
|
|
+ // 更新固定端点位置
|
|
|
|
|
+ points[4] = vectorAdd(points[4], vectorScale(perpendicularVector, projection));
|
|
|
|
|
|
|
|
- // 计算沿垂直方向的投影长度(点积)
|
|
|
|
|
- const projectionLength =
|
|
|
|
|
- dragVector[0] * perpendicularVector[0] +
|
|
|
|
|
- dragVector[1] * perpendicularVector[1];
|
|
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 计算新的短轴长度(保持拖拽意图)
|
|
|
|
|
- const newShortAxisHalfLength = Math.abs(projectionLength);
|
|
|
|
|
|
|
+ // 情况3: 拖拽短轴位置端点(索引5) - 固定端点沿平行于长轴的方向移动
|
|
|
|
|
+ if (draggedPointIndex === 5) {
|
|
|
|
|
+ // 旧的 位置端点
|
|
|
|
|
+ const oldPositon = points[5]
|
|
|
|
|
+ // 更新位置端点到新位置
|
|
|
|
|
+ points[5] = newPosition;
|
|
|
|
|
+ // 位置端点的位移向量
|
|
|
|
|
+ const deltaP = vectorAdd(newPosition, vectorScale(oldPositon, -1))
|
|
|
|
|
|
|
|
- // 根据拖拽的方向决定短轴的方向
|
|
|
|
|
- const direction = projectionLength >= 0 ? 1 : -1;
|
|
|
|
|
|
|
|
|
|
- // 更新短轴两个点,使其沿垂直方向
|
|
|
|
|
- points[4] = vectorAdd(
|
|
|
|
|
- shortAxisMidpoint,
|
|
|
|
|
- vectorScale(perpendicularVector, -direction * newShortAxisHalfLength)
|
|
|
|
|
- );
|
|
|
|
|
- points[5] = vectorAdd(
|
|
|
|
|
- shortAxisMidpoint,
|
|
|
|
|
- vectorScale(perpendicularVector, direction * newShortAxisHalfLength)
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ // 计算长轴方向向量
|
|
|
|
|
+ const longAxisVector = vectorSubtract(points[3], points[2]);
|
|
|
|
|
+ const longAxisLength = calculateDistance(points[2], points[3]);
|
|
|
|
|
+ if (longAxisLength === 0) {
|
|
|
|
|
+ // 长轴长度为0,无法计算
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 位置端点在长轴向量 longAxisVector 上的位移向量
|
|
|
|
|
+ const 投影向量 = vectorScale(longAxisVector, dotProduct(deltaP, longAxisVector) / dotProduct(longAxisVector, longAxisVector))
|
|
|
|
|
+ // 把固定端点也按照以上的 位移向量 移动就行了
|
|
|
|
|
+ points[4] = vectorAdd(points[4], 投影向量)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 情况4: 拖拽其他点(胸椎线点0或1) - 直接更新,不影响心脏轴线
|
|
|
|
|
+ points[draggedPointIndex] = newPosition;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -592,7 +638,7 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
const { viewport } = enabledElement;
|
|
const { viewport } = enabledElement;
|
|
|
|
|
|
|
|
const annotations = annotation.state.getAnnotations(this.getToolName(), element);
|
|
const annotations = annotation.state.getAnnotations(this.getToolName(), element);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if (!annotations || annotations.length === 0) {
|
|
if (!annotations || annotations.length === 0) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
@@ -900,7 +946,7 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
} as Types.ToolHandle;
|
|
} as Types.ToolHandle;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
annotation.data.handles.activeHandleIndex = null;
|
|
annotation.data.handles.activeHandleIndex = null;
|
|
|
return undefined;
|
|
return undefined;
|
|
|
}
|
|
}
|
|
@@ -1015,8 +1061,8 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
const canvasPoints = points.map((p) => viewport.worldToCanvas(p));
|
|
const canvasPoints = points.map((p) => viewport.worldToCanvas(p));
|
|
|
|
|
|
|
|
// 确定线段颜色(选中蓝色,未选中白色)
|
|
// 确定线段颜色(选中蓝色,未选中白色)
|
|
|
- const lineColor = annotation.highlighted
|
|
|
|
|
- ? 'rgb(0, 120, 255)'
|
|
|
|
|
|
|
+ const lineColor = annotation.highlighted
|
|
|
|
|
+ ? 'rgb(0, 120, 255)'
|
|
|
: 'rgb(255, 255, 255)';
|
|
: 'rgb(255, 255, 255)';
|
|
|
|
|
|
|
|
// 绘制胸椎长度线(点1-2)
|
|
// 绘制胸椎长度线(点1-2)
|
|
@@ -1096,14 +1142,14 @@ export default class VHSMeasurementTool extends AnnotationTool {
|
|
|
`S: ${cachedStats.shortAxisCount.toFixed(2)}`,
|
|
`S: ${cachedStats.shortAxisCount.toFixed(2)}`,
|
|
|
`L/S: ${cachedStats.ratio.toFixed(2)}`
|
|
`L/S: ${cachedStats.ratio.toFixed(2)}`
|
|
|
];
|
|
];
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 文本框默认位置在长轴右侧
|
|
// 文本框默认位置在长轴右侧
|
|
|
- const defaultTextBoxPosition: CoreTypes.Point2 =
|
|
|
|
|
|
|
+ const defaultTextBoxPosition: CoreTypes.Point2 =
|
|
|
data.textBox || [
|
|
data.textBox || [
|
|
|
canvasPoints[3][0] + 20,
|
|
canvasPoints[3][0] + 20,
|
|
|
canvasPoints[3][1]
|
|
canvasPoints[3][1]
|
|
|
];
|
|
];
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
const textUID = `${annotationUID}-text-box`;
|
|
const textUID = `${annotationUID}-text-box`;
|
|
|
drawLinkedTextBox(
|
|
drawLinkedTextBox(
|
|
|
svgDrawingHelper,
|
|
svgDrawingHelper,
|