Spaces:
Running
Running
| <template> | |
| <div class="export-view"> | |
| <div class="export-content"> | |
| <div class="export-header"> | |
| <h3>Export</h3> | |
| </div> | |
| <div class="export-info"> | |
| <div class="info-row"> | |
| <span>{{ objectCount }} objets, {{ annotationCount }} annotations</span> | |
| </div> | |
| <div class="info-row filename"> | |
| <span>{{ fileName }}</span> | |
| </div> | |
| </div> | |
| <div class="export-actions"> | |
| <button | |
| class="export-button" | |
| @click="exportAnnotations" | |
| :disabled="!hasAnnotations" | |
| > | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"> | |
| <path d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z"/> | |
| </svg> | |
| Télécharger | |
| </button> | |
| <p v-if="!hasAnnotations" class="no-data-message"> | |
| Aucune donnée à exporter | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| </template> | |
| <script> | |
| import { useAnnotationStore } from '@/stores/annotationStore' | |
| import { useVideoStore } from '@/stores/videoStore' | |
| import { computed } from 'vue' | |
| export default { | |
| name: 'EmptyView', | |
| setup() { | |
| const annotationStore = useAnnotationStore() | |
| const videoStore = useVideoStore() | |
| const videoName = computed(() => { | |
| if (videoStore.selectedVideo?.name) { | |
| return videoStore.selectedVideo.name.replace(/\.[^/.]+$/, '') // Enlever l'extension | |
| } | |
| return 'default_video' | |
| }) | |
| const fileName = computed(() => { | |
| return `${videoName.value}_objects.json` | |
| }) | |
| const objectCount = computed(() => { | |
| return Object.keys(annotationStore.objects).length | |
| }) | |
| const annotationCount = computed(() => { | |
| let count = 0 | |
| Object.values(annotationStore.frameAnnotations).forEach(frameAnnotations => { | |
| count += frameAnnotations.length | |
| }) | |
| return count | |
| }) | |
| const hasAnnotations = computed(() => { | |
| return annotationCount.value > 0 | |
| }) | |
| const exportAnnotations = () => { | |
| // Créer la structure objects selon le format demandé | |
| const objects = Object.values(annotationStore.objects).map(obj => { | |
| const objData = { | |
| obj_id: parseInt(obj.id) | |
| } | |
| // Analyser le label pour extraire le type et l'équipe | |
| if (obj.label) { | |
| const label = obj.label.toLowerCase() | |
| if (label.includes('ball')) { | |
| objData.obj_type = 'ball' | |
| objData.team = null | |
| } else if (label.includes('player')) { | |
| objData.obj_type = 'player' | |
| // Extraire le numéro d'équipe | |
| if (label.includes('team 1') || label.includes('team1')) { | |
| objData.team = 1 | |
| } else if (label.includes('team 2') || label.includes('team2')) { | |
| objData.team = 2 | |
| } else { | |
| objData.team = null | |
| } | |
| } else { | |
| objData.obj_type = null | |
| objData.team = null | |
| } | |
| } else { | |
| objData.obj_type = null | |
| objData.team = null | |
| } | |
| return objData | |
| }) | |
| // Créer la structure initial_annotations selon le format demandé | |
| const initial_annotations = [] | |
| // Parcourir toutes les frames qui ont des annotations | |
| Object.keys(annotationStore.frameAnnotations).forEach(frameNumber => { | |
| const frameAnnotations = annotationStore.frameAnnotations[frameNumber] | |
| const frameData = { | |
| frame: parseInt(frameNumber), | |
| annotations: [] | |
| } | |
| // Grouper les annotations par objet pour cette frame | |
| const annotationsByObject = {} | |
| frameAnnotations.forEach(annotation => { | |
| if (!annotationsByObject[annotation.objectId]) { | |
| annotationsByObject[annotation.objectId] = [] | |
| } | |
| // Extraire les points selon le type d'annotation | |
| if (annotation.type === 'point') { | |
| annotationsByObject[annotation.objectId].push({ | |
| x: Math.round(annotation.x), | |
| y: Math.round(annotation.y), | |
| label: annotation.pointType === 'positive' ? 1 : 0 | |
| }) | |
| } else if (annotation.type === 'mask' && annotation.points) { | |
| // Ajouter tous les points du masque | |
| annotation.points.forEach(point => { | |
| annotationsByObject[annotation.objectId].push({ | |
| x: Math.round(point.x), | |
| y: Math.round(point.y), | |
| label: point.type === 'positive' ? 1 : 0 | |
| }) | |
| }) | |
| } | |
| }) | |
| // Créer les annotations pour cette frame | |
| Object.keys(annotationsByObject).forEach(objectId => { | |
| const points = annotationsByObject[objectId] | |
| if (points.length > 0) { | |
| frameData.annotations.push({ | |
| obj_id: parseInt(objectId), | |
| points: points | |
| }) | |
| } | |
| }) | |
| // Ajouter la frame seulement si elle a des annotations | |
| if (frameData.annotations.length > 0) { | |
| initial_annotations.push(frameData) | |
| } | |
| }) | |
| // Structure finale selon le format demandé | |
| const exportData = { | |
| objects: objects, | |
| initial_annotations: initial_annotations | |
| } | |
| // Créer le blob et le télécharger | |
| const jsonString = JSON.stringify(exportData, null, 2) | |
| const blob = new Blob([jsonString], { type: 'application/json' }) | |
| const url = URL.createObjectURL(blob) | |
| const link = document.createElement('a') | |
| link.href = url | |
| link.download = fileName.value | |
| document.body.appendChild(link) | |
| link.click() | |
| document.body.removeChild(link) | |
| URL.revokeObjectURL(url) | |
| console.log('Annotations exportées:', fileName.value) | |
| } | |
| return { | |
| videoName, | |
| fileName, | |
| objectCount, | |
| annotationCount, | |
| hasAnnotations, | |
| exportAnnotations | |
| } | |
| } | |
| } | |
| </script> | |
| <style scoped> | |
| .export-view { | |
| height: 100%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: white; | |
| padding: 20px; | |
| } | |
| .export-content { | |
| text-align: center; | |
| width: 100%; | |
| } | |
| .export-header { | |
| margin-bottom: 20px; | |
| } | |
| .export-header h3 { | |
| margin: 0; | |
| font-size: 1rem; | |
| color: #fff; | |
| font-weight: 500; | |
| } | |
| .export-info { | |
| margin-bottom: 20px; | |
| } | |
| .info-row { | |
| margin-bottom: 8px; | |
| color: #ccc; | |
| font-size: 0.9rem; | |
| } | |
| .info-row.filename { | |
| color: #fff; | |
| font-family: monospace; | |
| font-size: 0.8rem; | |
| } | |
| .export-actions { | |
| text-align: center; | |
| } | |
| .export-button { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 8px 16px; | |
| background: #4a4a4a; | |
| color: white; | |
| border: none; | |
| border-radius: 4px; | |
| font-size: 0.8rem; | |
| cursor: pointer; | |
| transition: background 0.2s; | |
| } | |
| .export-button:hover:not(:disabled) { | |
| background: #5a5a5a; | |
| } | |
| .export-button:disabled { | |
| background: #3c3c3c; | |
| color: #666; | |
| cursor: not-allowed; | |
| } | |
| .no-data-message { | |
| margin: 12px 0 0 0; | |
| color: #666; | |
| font-size: 0.8rem; | |
| font-style: italic; | |
| } | |
| </style> |