fix: resolve TypeScript compilation errors
Fixes: - VideoPlayer.vue: restore allTags, filteredMarks, marksByTag (removed by mistake) - PeopleView.vue: fix frameTime scope in playFaceDetailVideo catch block - Both errors caused white screen on build failure Root cause: 1. visibleTags duplicate declaration (old + new logic) 2. frameTime undefined in catch block (scope issue) Solution: 1. Keep allTags/filteredMarks/marksByTag (needed in template) 2. Keep visibleTags (new logic for processor toggles) 3. Move frameTime calculation to correct scope in catch block
This commit is contained in:
60
src/utils/mockPoseData.ts
Normal file
60
src/utils/mockPoseData.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { PoseResponse, AppearanceResponse } from '@/api/types'
|
||||
|
||||
export const mockPose: PoseResponse = {
|
||||
frame: 100,
|
||||
keypoints: [
|
||||
{ name: 'nose', x: 100, y: 50, confidence: 0.9 },
|
||||
{ name: 'left_eye', x: 90, y: 45, confidence: 0.95 },
|
||||
{ name: 'right_eye', x: 110, y: 45, confidence: 0.95 },
|
||||
{ name: 'left_ear', x: 80, y: 50, confidence: 0.85 },
|
||||
{ name: 'right_ear', x: 120, y: 50, confidence: 0.85 },
|
||||
{ name: 'left_shoulder', x: 60, y: 100, confidence: 0.9 },
|
||||
{ name: 'right_shoulder', x: 140, y: 100, confidence: 0.9 },
|
||||
{ name: 'left_elbow', x: 50, y: 150, confidence: 0.85 },
|
||||
{ name: 'right_elbow', x: 150, y: 150, confidence: 0.85 },
|
||||
{ name: 'left_wrist', x: 45, y: 190, confidence: 0.8 },
|
||||
{ name: 'right_wrist', x: 155, y: 190, confidence: 0.8 },
|
||||
{ name: 'left_hip', x: 70, y: 200, confidence: 0.9 },
|
||||
{ name: 'right_hip', x: 130, y: 200, confidence: 0.9 },
|
||||
{ name: 'left_knee', x: 65, y: 280, confidence: 0.85 },
|
||||
{ name: 'right_knee', x: 135, y: 280, confidence: 0.85 },
|
||||
{ name: 'left_ankle', x: 60, y: 350, confidence: 0.8 },
|
||||
{ name: 'right_ankle', x: 140, y: 350, confidence: 0.8 },
|
||||
],
|
||||
pose_class: 'standing',
|
||||
confidence: 0.92
|
||||
}
|
||||
|
||||
export const mockAppearance: AppearanceResponse = {
|
||||
frame: 100,
|
||||
dominant_colors: [
|
||||
{ rgb: [255, 100, 50], percentage: 0.35 },
|
||||
{ rgb: [50, 150, 200], percentage: 0.25 },
|
||||
{ rgb: [100, 200, 100], percentage: 0.15 },
|
||||
]
|
||||
}
|
||||
|
||||
export const mockSittingPose: PoseResponse = {
|
||||
frame: 200,
|
||||
keypoints: [
|
||||
{ name: 'nose', x: 100, y: 60, confidence: 0.88 },
|
||||
{ name: 'left_eye', x: 90, y: 55, confidence: 0.92 },
|
||||
{ name: 'right_eye', x: 110, y: 55, confidence: 0.92 },
|
||||
{ name: 'left_ear', x: 80, y: 60, confidence: 0.82 },
|
||||
{ name: 'right_ear', x: 120, y: 60, confidence: 0.82 },
|
||||
{ name: 'left_shoulder', x: 60, y: 110, confidence: 0.88 },
|
||||
{ name: 'right_shoulder', x: 140, y: 110, confidence: 0.88 },
|
||||
{ name: 'left_elbow', x: 55, y: 140, confidence: 0.83 },
|
||||
{ name: 'right_elbow', x: 145, y: 140, confidence: 0.83 },
|
||||
{ name: 'left_wrist', x: 50, y: 160, confidence: 0.78 },
|
||||
{ name: 'right_wrist', x: 150, y: 160, confidence: 0.78 },
|
||||
{ name: 'left_hip', x: 70, y: 180, confidence: 0.88 },
|
||||
{ name: 'right_hip', x: 130, y: 180, confidence: 0.88 },
|
||||
{ name: 'left_knee', x: 90, y: 200, confidence: 0.83 },
|
||||
{ name: 'right_knee', x: 110, y: 200, confidence: 0.83 },
|
||||
{ name: 'left_ankle', x: 85, y: 240, confidence: 0.78 },
|
||||
{ name: 'right_ankle', x: 115, y: 240, confidence: 0.78 },
|
||||
],
|
||||
pose_class: 'sitting',
|
||||
confidence: 0.87
|
||||
}
|
||||
264
src/utils/poseRenderer.ts
Normal file
264
src/utils/poseRenderer.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
import type { PoseResponse, AppearanceResponse } from '@/api/types'
|
||||
|
||||
const SKELETON_CONNECTIONS: [string, string][] = [
|
||||
['nose', 'left_eye'],
|
||||
['nose', 'right_eye'],
|
||||
['left_eye', 'left_ear'],
|
||||
['right_eye', 'right_ear'],
|
||||
['left_shoulder', 'right_shoulder'],
|
||||
['left_shoulder', 'left_elbow'],
|
||||
['left_elbow', 'left_wrist'],
|
||||
['right_shoulder', 'right_elbow'],
|
||||
['right_elbow', 'right_wrist'],
|
||||
['left_shoulder', 'left_hip'],
|
||||
['right_shoulder', 'right_hip'],
|
||||
['left_hip', 'right_hip'],
|
||||
['left_hip', 'left_knee'],
|
||||
['left_knee', 'left_ankle'],
|
||||
['right_hip', 'right_knee'],
|
||||
['right_knee', 'right_ankle'],
|
||||
]
|
||||
|
||||
function transformKeypoints(
|
||||
keypoints: Array<{ name: string; x: number; y: number; confidence?: number }>,
|
||||
canvasWidth: number,
|
||||
canvasHeight: number
|
||||
): Map<string, { x: number; y: number; confidence: number }> {
|
||||
console.log('[transformKeypoints] input keypoints:', keypoints?.length)
|
||||
|
||||
const validKeypoints = keypoints.filter(kp => {
|
||||
const conf = kp.confidence ?? 0
|
||||
return conf > 0 && kp.x > 0 && kp.y > 0
|
||||
})
|
||||
|
||||
console.log('[transformKeypoints] valid keypoints:', validKeypoints.length)
|
||||
|
||||
if (validKeypoints.length === 0) {
|
||||
return new Map()
|
||||
}
|
||||
|
||||
let minX = Infinity, maxX = -Infinity
|
||||
let minY = Infinity, maxY = -Infinity
|
||||
for (const kp of validKeypoints) {
|
||||
minX = Math.min(minX, kp.x)
|
||||
maxX = Math.max(maxX, kp.x)
|
||||
minY = Math.min(minY, kp.y)
|
||||
maxY = Math.max(maxY, kp.y)
|
||||
}
|
||||
|
||||
const width = maxX - minX || 1
|
||||
const height = maxY - minY || 1
|
||||
|
||||
console.log('[transformKeypoints] bounding box:', minX, minY, 'to', maxX, maxY)
|
||||
console.log('[transformKeypoints] bbox size:', width, 'x', height)
|
||||
|
||||
const padding = 20
|
||||
const availableWidth = canvasWidth - 2 * padding
|
||||
const availableHeight = canvasHeight - 2 * padding
|
||||
|
||||
const scaleX = availableWidth / width
|
||||
const scaleY = availableHeight / height
|
||||
const scale = Math.min(scaleX, scaleY)
|
||||
|
||||
const scaledWidth = width * scale
|
||||
const scaledHeight = height * scale
|
||||
const offsetX = (canvasWidth - scaledWidth) / 2 - minX * scale
|
||||
const offsetY = (canvasHeight - scaledHeight) / 2 - minY * scale
|
||||
|
||||
console.log('[transformKeypoints] scale:', scale, 'offset:', offsetX, offsetY)
|
||||
|
||||
const keypointMap = new Map<string, { x: number; y: number; confidence: number }>()
|
||||
for (const kp of keypoints) {
|
||||
const conf = kp.confidence ?? 0
|
||||
keypointMap.set(kp.name, {
|
||||
x: kp.x * scale + offsetX,
|
||||
y: kp.y * scale + offsetY,
|
||||
confidence: conf
|
||||
})
|
||||
}
|
||||
|
||||
console.log('[transformKeypoints] First 3 transformed:',
|
||||
[...keypointMap.entries()].slice(0, 3).map(([k, v]) => ({ name: k, ...v })))
|
||||
|
||||
return keypointMap
|
||||
}
|
||||
|
||||
export function drawPoseSkeleton(
|
||||
canvas: HTMLCanvasElement,
|
||||
pose: PoseResponse,
|
||||
options?: {
|
||||
lineWidth?: number
|
||||
pointRadius?: number
|
||||
lineColor?: string
|
||||
pointColor?: string
|
||||
videoWidth?: number
|
||||
videoHeight?: number
|
||||
noTransform?: boolean
|
||||
}
|
||||
) {
|
||||
console.log('[drawPoseSkeleton] canvas size:', canvas.width, 'x', canvas.height)
|
||||
console.log('[drawPoseSkeleton] pose:', pose)
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) {
|
||||
console.error('[drawPoseSkeleton] No context')
|
||||
return
|
||||
}
|
||||
|
||||
const {
|
||||
lineWidth = 2,
|
||||
pointRadius = 4,
|
||||
lineColor = '#00ff00',
|
||||
pointColor = '#ff0000',
|
||||
noTransform = false
|
||||
} = options || {}
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// 不轉換,直接畫原始座標(用於 debug)
|
||||
if (noTransform) {
|
||||
console.log('[drawPoseSkeleton] Drawing raw keypoints (no transform)')
|
||||
|
||||
const validKeypoints = pose.keypoints.filter(kp => kp.confidence > 0 && kp.x > 0 && kp.y > 0)
|
||||
|
||||
if (validKeypoints.length === 0) {
|
||||
ctx.fillStyle = '#9aa0a6'
|
||||
ctx.font = '12px sans-serif'
|
||||
ctx.textAlign = 'center'
|
||||
ctx.fillText('No valid keypoints', canvas.width / 2, canvas.height / 2)
|
||||
return
|
||||
}
|
||||
|
||||
// 找 bounding box
|
||||
let minX = Infinity, maxX = -Infinity
|
||||
let minY = Infinity, maxY = -Infinity
|
||||
for (const kp of validKeypoints) {
|
||||
minX = Math.min(minX, kp.x)
|
||||
maxX = Math.max(maxX, kp.x)
|
||||
minY = Math.min(minY, kp.y)
|
||||
maxY = Math.max(maxY, kp.y)
|
||||
}
|
||||
|
||||
console.log('[drawPoseSkeleton] Raw bbox:', minX, minY, 'to', maxX, maxY)
|
||||
|
||||
// 繪製
|
||||
ctx.fillStyle = pointColor
|
||||
for (const kp of validKeypoints) {
|
||||
ctx.beginPath()
|
||||
ctx.arc(kp.x, kp.y, pointRadius, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const keypointMap = transformKeypoints(pose.keypoints, canvas.width, canvas.height)
|
||||
|
||||
console.log('[drawPoseSkeleton] transformed keypoints:', keypointMap.size)
|
||||
|
||||
if (keypointMap.size === 0) {
|
||||
console.log('[drawPoseSkeleton] No valid keypoints')
|
||||
ctx.fillStyle = '#9aa0a6'
|
||||
ctx.font = '12px sans-serif'
|
||||
ctx.textAlign = 'center'
|
||||
ctx.fillText('No pose detected', canvas.width / 2, canvas.height / 2)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.strokeStyle = lineColor
|
||||
ctx.lineWidth = lineWidth
|
||||
for (const [from, to] of SKELETON_CONNECTIONS) {
|
||||
const fromPt = keypointMap.get(from)
|
||||
const toPt = keypointMap.get(to)
|
||||
if (fromPt && toPt && fromPt.confidence > 0 && toPt.confidence > 0) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(fromPt.x, fromPt.y)
|
||||
ctx.lineTo(toPt.x, toPt.y)
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
ctx.fillStyle = pointColor
|
||||
for (const kp of pose.keypoints) {
|
||||
const transformed = keypointMap.get(kp.name)
|
||||
if (transformed && transformed.confidence > 0) {
|
||||
ctx.beginPath()
|
||||
ctx.arc(transformed.x, transformed.y, pointRadius, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function drawAppearanceOverlay(
|
||||
canvas: HTMLCanvasElement,
|
||||
appearance: AppearanceResponse,
|
||||
pose?: PoseResponse
|
||||
) {
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
if (!appearance.dominant_colors?.length) {
|
||||
if (appearance.hsv_histogram && appearance.hsv_histogram.length > 0) {
|
||||
ctx.fillStyle = '#f8f9fa'
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||
ctx.fillStyle = '#5f6368'
|
||||
ctx.font = '12px sans-serif'
|
||||
ctx.textAlign = 'center'
|
||||
ctx.fillText('HSV histogram', canvas.width / 2, canvas.height / 2 - 10)
|
||||
ctx.fillText(`(${appearance.hsv_histogram.length} channels)`, canvas.width / 2, canvas.height / 2 + 10)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!pose) {
|
||||
const swatchWidth = canvas.width / appearance.dominant_colors.length
|
||||
appearance.dominant_colors.forEach((color, i) => {
|
||||
ctx.fillStyle = `rgb(${color.rgb.join(',')})`
|
||||
ctx.fillRect(i * swatchWidth, 0, swatchWidth, canvas.height)
|
||||
if (color.percentage) {
|
||||
ctx.fillStyle = '#fff'
|
||||
ctx.font = '12px sans-serif'
|
||||
ctx.fillText(`${Math.round(color.percentage * 100)}%`, i * swatchWidth + 4, canvas.height - 8)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const keypointMap = transformKeypoints(pose.keypoints, canvas.width, canvas.height)
|
||||
|
||||
if (keypointMap.size === 0) return
|
||||
|
||||
ctx.globalAlpha = 0.4
|
||||
|
||||
const upperBodyPts = ['left_shoulder', 'right_shoulder', 'left_elbow', 'right_elbow']
|
||||
const lowerBodyPts = ['left_hip', 'right_hip', 'left_knee', 'right_knee']
|
||||
|
||||
if (appearance.dominant_colors[0]) {
|
||||
const color = appearance.dominant_colors[0]
|
||||
ctx.fillStyle = `rgb(${color.rgb.join(',')})`
|
||||
for (const name of upperBodyPts) {
|
||||
const pt = keypointMap.get(name)
|
||||
if (pt && pt.confidence > 0) {
|
||||
ctx.beginPath()
|
||||
ctx.arc(pt.x, pt.y, 15, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (appearance.dominant_colors[1]) {
|
||||
const color = appearance.dominant_colors[1]
|
||||
ctx.fillStyle = `rgb(${color.rgb.join(',')})`
|
||||
for (const name of lowerBodyPts) {
|
||||
const pt = keypointMap.get(name)
|
||||
if (pt && pt.confidence > 0) {
|
||||
ctx.beginPath()
|
||||
ctx.arc(pt.x, pt.y, 15, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.globalAlpha = 1.0
|
||||
}
|
||||
Reference in New Issue
Block a user