Spaces:
Running
Running
| <template> | |
| <div class="annotations-raw-view"> | |
| <div class="raw-header"> | |
| <h4>Annotations Brutes</h4> | |
| <div class="frame-info"> | |
| <span>Frame {{ currentFrameNumber }}</span> | |
| <span v-if="selectedObject">{{ selectedObject.name }}</span> | |
| </div> | |
| </div> | |
| <div class="raw-content" v-if="selectedAnnotations.length > 0"> | |
| <div | |
| v-for="annotation in selectedAnnotations" | |
| :key="annotation.id" | |
| class="annotation-item" | |
| :class="`annotation-${annotation.type}`" | |
| > | |
| <div class="annotation-header"> | |
| <span class="annotation-type">{{ getAnnotationTypeLabel(annotation.type) }}</span> | |
| <span class="annotation-id">ID: {{ annotation.id.slice(0, 8) }}...</span> | |
| </div> | |
| <div class="annotation-data"> | |
| <div v-if="annotation.type === 'rectangle'" class="rectangle-data"> | |
| <div class="data-row"> | |
| <span class="data-label">Position:</span> | |
| <span class="data-value">{{ Math.round(annotation.x) }}, {{ Math.round(annotation.y) }}</span> | |
| </div> | |
| <div class="data-row"> | |
| <span class="data-label">Dimensions:</span> | |
| <span class="data-value">{{ Math.round(annotation.width) }} × {{ Math.round(annotation.height) }}</span> | |
| </div> | |
| </div> | |
| <div v-else-if="annotation.type === 'point'" class="point-data"> | |
| <div class="data-row"> | |
| <span class="data-label">Position:</span> | |
| <span class="data-value">{{ Math.round(annotation.x) }}, {{ Math.round(annotation.y) }}</span> | |
| </div> | |
| <div class="data-row"> | |
| <span class="data-label">Type:</span> | |
| <span class="data-value" :class="`point-${annotation.pointType}`"> | |
| {{ annotation.pointType === 'positive' ? 'Positif (+)' : 'Négatif (-)' }} | |
| </span> | |
| </div> | |
| </div> | |
| <div v-else-if="annotation.type === 'mask'" class="mask-data"> | |
| <div class="data-row"> | |
| <span class="data-label">Score:</span> | |
| <span class="data-value">{{ (annotation.maskScore * 100).toFixed(1) }}%</span> | |
| </div> | |
| <div class="data-row"> | |
| <span class="data-label">Taille image:</span> | |
| <span class="data-value">{{ annotation.maskImageSize?.width }} × {{ annotation.maskImageSize?.height }}</span> | |
| </div> | |
| <div class="data-row"> | |
| <span class="data-label">Points:</span> | |
| <span class="data-value">{{ annotation.points?.length || 0 }} points</span> | |
| </div> | |
| <div v-if="annotation.points && annotation.points.length > 0" class="points-details"> | |
| <div v-for="(point, pointIndex) in annotation.points" :key="pointIndex" class="point-detail"> | |
| <span class="point-coords">{{ Math.round(point.x) }}, {{ Math.round(point.y) }}</span> | |
| <span class="point-type" :class="`point-${point.type}`">{{ point.type }}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div v-else class="generic-data"> | |
| <div class="data-row"> | |
| <span class="data-label">Données:</span> | |
| <span class="data-value">{{ JSON.stringify(annotation, null, 2) }}</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div v-else class="no-annotations"> | |
| <div class="no-annotations-content"> | |
| <svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor"> | |
| <path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4M11,16.5L6.5,12L7.91,10.59L11,13.67L16.59,8.09L18,9.5L11,16.5Z" opacity="0.3"/> | |
| </svg> | |
| <p v-if="!selectedObject">Aucun objet sélectionné</p> | |
| <p v-else>Aucune annotation pour cet objet sur cette frame</p> | |
| </div> | |
| </div> | |
| </div> | |
| </template> | |
| <script> | |
| import { useAnnotationStore } from '@/stores/annotationStore' | |
| import { useVideoStore } from '@/stores/videoStore' | |
| import { computed } from 'vue' | |
| export default { | |
| name: 'AnnotationsRawView', | |
| setup() { | |
| const annotationStore = useAnnotationStore() | |
| const videoStore = useVideoStore() | |
| const getCurrentFrameNumber = () => { | |
| const frameRate = annotationStore.currentSession?.frameRate || 30 | |
| return Math.round(videoStore.currentTime * frameRate) | |
| } | |
| const currentFrameNumber = computed(() => getCurrentFrameNumber()) | |
| const selectedObject = computed(() => { | |
| if (!annotationStore.selectedObjectId) return null | |
| return annotationStore.objects[annotationStore.selectedObjectId] | |
| }) | |
| const selectedAnnotations = computed(() => { | |
| const currentFrame = getCurrentFrameNumber() | |
| const frameAnnotations = annotationStore.getAnnotationsForFrame(currentFrame) || [] | |
| return frameAnnotations.filter( | |
| annotation => annotation && annotation.objectId === annotationStore.selectedObjectId | |
| ) | |
| }) | |
| return { | |
| currentFrameNumber, | |
| selectedObject, | |
| selectedAnnotations | |
| } | |
| }, | |
| methods: { | |
| getAnnotationTypeLabel(type) { | |
| const labels = { | |
| 'rectangle': 'Rectangle', | |
| 'point': 'Point', | |
| 'mask': 'Masque', | |
| 'polygon': 'Polygone' | |
| } | |
| return labels[type] || type.charAt(0).toUpperCase() + type.slice(1) | |
| } | |
| } | |
| } | |
| </script> | |
| <style scoped> | |
| .annotations-raw-view { | |
| height: 100%; | |
| display: flex; | |
| flex-direction: column; | |
| color: white; | |
| } | |
| .raw-header { | |
| padding: 12px; | |
| border-bottom: 1px solid #4a4a4a; | |
| } | |
| .raw-header h4 { | |
| margin: 0 0 4px 0; | |
| font-size: 0.9rem; | |
| color: #fff; | |
| } | |
| .frame-info { | |
| display: flex; | |
| gap: 12px; | |
| font-size: 0.8rem; | |
| color: #ccc; | |
| } | |
| .raw-content { | |
| flex: 1; | |
| padding: 8px; | |
| overflow-y: auto; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| } | |
| .annotation-item { | |
| background: #3c3c3c; | |
| border-radius: 6px; | |
| padding: 8px; | |
| border-left: 3px solid #4ecdc4; | |
| } | |
| .annotation-item.annotation-rectangle { | |
| border-left-color: #00ff00; | |
| } | |
| .annotation-item.annotation-point { | |
| border-left-color: #ff6b35; | |
| } | |
| .annotation-item.annotation-mask { | |
| border-left-color: #a55eea; | |
| } | |
| .annotation-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 6px; | |
| } | |
| .annotation-type { | |
| font-weight: 500; | |
| font-size: 0.8rem; | |
| color: #fff; | |
| } | |
| .annotation-id { | |
| font-size: 0.7rem; | |
| color: #999; | |
| font-family: monospace; | |
| } | |
| .annotation-data { | |
| font-size: 0.8rem; | |
| } | |
| .data-row { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 4px; | |
| } | |
| .data-label { | |
| color: #ccc; | |
| font-weight: 500; | |
| } | |
| .data-value { | |
| color: #fff; | |
| font-family: monospace; | |
| } | |
| .point-positive { | |
| color: #00ff00; | |
| } | |
| .point-negative { | |
| color: #ff0000; | |
| } | |
| .points-details { | |
| margin-top: 6px; | |
| padding-left: 8px; | |
| border-left: 1px solid #555; | |
| } | |
| .point-detail { | |
| display: flex; | |
| justify-content: space-between; | |
| font-size: 0.7rem; | |
| margin-bottom: 2px; | |
| } | |
| .point-coords { | |
| color: #ccc; | |
| font-family: monospace; | |
| } | |
| .point-type { | |
| font-weight: 500; | |
| } | |
| .no-annotations { | |
| flex: 1; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .no-annotations-content { | |
| text-align: center; | |
| color: #666; | |
| } | |
| .no-annotations-content svg { | |
| margin-bottom: 16px; | |
| } | |
| .no-annotations-content p { | |
| margin: 0; | |
| font-style: italic; | |
| } | |
| </style> |