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}_config.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> |