Spaces:
Running
Running
// Dans un store Pinia (stores/annotationStore.js) | |
import { defineStore } from 'pinia' | |
import { v4 as uuidv4 } from 'uuid' | |
export const useAnnotationStore = defineStore('annotations', { | |
state: () => ({ | |
// Session courante avec métadonnées vidéo | |
currentSession: { | |
id: 'session-1', | |
name: 'Session d\'annotation', | |
videoId: null, | |
video: null, // Nom du fichier vidéo | |
metadata: { | |
duration: 0, | |
width: 0, | |
height: 0, | |
fps: 30, | |
date: new Date().toISOString() | |
} | |
}, | |
// Dictionnaire des objets | |
objects: { | |
// Format: "1": { id: "1", name: "Objet 1", color: "#4f056f" } | |
}, | |
// Compteur pour les IDs d'objets | |
objectIdCounter: 1, | |
// Annotations par frame | |
frameAnnotations: {}, // Format: { | |
// "0": [ | |
// { | |
// id: "uuid", | |
// objectId: "1", | |
// type: "mask", | |
// mask: "base64...", | |
// maskScore: 0.91, | |
// maskImageSize: { width: 1920, height: 1080 }, | |
// points: [{ x: 1270, y: 405, type: "positive" }] | |
// }, | |
// { | |
// id: "uuid", | |
// objectId: "2", | |
// type: "rectangle", | |
// x: 100, | |
// y: 100, | |
// width: 200, | |
// height: 150 | |
// } | |
// ] | |
// } | |
selectedObjectId: null, | |
temporaryPoints: [] | |
}), | |
getters: { | |
getTemporaryPointsForObject: (state) => (objectId) => { | |
return state.temporaryPoints.filter(point => point.objectId === objectId) | |
} | |
}, | |
actions: { | |
// Sélectionner un objet | |
selectObject(objectId) { | |
this.selectedObjectId = objectId | |
console.log(`Objet sélectionné: ${objectId}`) | |
}, | |
// Désélectionner l'objet actuel | |
deselectObject() { | |
this.selectedObjectId = null | |
}, | |
// Mettre à jour les métadonnées de la vidéo | |
updateVideoMetadata(metadata) { | |
this.currentSession.metadata = { | |
...this.currentSession.metadata, | |
...metadata | |
} | |
}, | |
// Ajouter une annotation pour l'objet sélectionné à la frame actuelle | |
addAnnotation(frameNumber, annotation) { | |
const id = uuidv4() | |
let newAnnotation | |
if (annotation.type === 'mask') { | |
newAnnotation = { | |
id, | |
objectId: this.selectedObjectId, | |
type: 'mask', | |
mask: annotation.mask || '', | |
maskScore: annotation.maskScore || 0, | |
maskImageSize: annotation.maskImageSize || { | |
width: this.currentSession.metadata.width, | |
height: this.currentSession.metadata.height | |
}, | |
points: annotation.points || [] | |
} | |
} else if (annotation.type === 'rectangle') { | |
newAnnotation = { | |
id, | |
objectId: this.selectedObjectId, | |
type: 'rectangle', | |
x: annotation.x, | |
y: annotation.y, | |
width: annotation.width, | |
height: annotation.height | |
} | |
} else if (annotation.type === 'point') { | |
newAnnotation = { | |
id, | |
objectId: this.selectedObjectId, | |
type: 'point', | |
x: annotation.x, | |
y: annotation.y, | |
pointType: annotation.pointType // 'positive' ou 'negative' | |
} | |
} else { | |
// Gestion par défaut pour les autres types | |
newAnnotation = { | |
id, | |
objectId: this.selectedObjectId, | |
...annotation | |
} | |
} | |
// Vérifier que newAnnotation a été créée | |
if (!newAnnotation) { | |
console.error('Erreur: impossible de créer l\'annotation', annotation) | |
return null | |
} | |
if (!this.frameAnnotations[frameNumber]) { | |
this.frameAnnotations[frameNumber] = [] | |
} | |
this.frameAnnotations[frameNumber].push(newAnnotation) | |
console.log('Annotation ajoutée:', newAnnotation) | |
return id | |
}, | |
updateAnnotation(frameNumber, annotationId, updates) { | |
if (!this.frameAnnotations[frameNumber]) return | |
const annotationIndex = this.frameAnnotations[frameNumber].findIndex( | |
a => a.id === annotationId | |
) | |
if (annotationIndex === -1) return | |
// Mettre à jour l'annotation avec les nouvelles propriétés | |
this.frameAnnotations[frameNumber][annotationIndex] = { | |
...this.frameAnnotations[frameNumber][annotationIndex], | |
...updates | |
} | |
}, | |
// Ajouter un nouvel objet | |
addObject(objectData = {}) { | |
const objectId = `${this.objectIdCounter++}` | |
this.objects[objectId] = { | |
id: objectId, | |
name: objectData.name || `Objet ${this.objectIdCounter - 1}`, | |
color: objectData.color || this.getRandomColor(), | |
// Autres propriétés selon vos besoins | |
} | |
// Sélectionner automatiquement le nouvel objet | |
this.selectObject(objectId) | |
return objectId | |
}, | |
// Récupérer les annotations pour une frame | |
getAnnotationsForFrame(frameNumber) { | |
return this.frameAnnotations[frameNumber.toString()] || [] | |
}, | |
// Supprimer une annotation | |
removeAnnotation(frameNumber, annotationId) { | |
const frameKey = frameNumber.toString() | |
if (this.frameAnnotations[frameKey]) { | |
this.frameAnnotations[frameKey] = this.frameAnnotations[frameKey] | |
.filter(a => a.id !== annotationId) | |
} | |
}, | |
// Nouvelle méthode pour supprimer tous les masques d'un objet sur une frame | |
removeMasksForObject(frameNumber, objectId) { | |
const frameKey = frameNumber.toString() | |
if (this.frameAnnotations[frameKey]) { | |
this.frameAnnotations[frameKey] = this.frameAnnotations[frameKey] | |
.filter(a => !(a.objectId === objectId && a.type === 'mask')) | |
} | |
}, | |
// Vérifier si un objet a encore des annotations sur une frame | |
hasAnnotationsForObject(frameNumber, objectId) { | |
const frameKey = frameNumber.toString() | |
if (!this.frameAnnotations[frameKey]) return false | |
return this.frameAnnotations[frameKey] | |
.some(a => a.objectId === objectId) | |
}, | |
// Générer une couleur aléatoire | |
getRandomColor() { | |
return '#' + Math.floor(Math.random()*16777215).toString(16).padStart(6, '0') | |
}, | |
// Sauvegarder les annotations dans le format demandé | |
saveAnnotations() { | |
const data = { | |
video: this.currentSession.video, | |
metadata: this.currentSession.metadata, | |
objects: this.objects, | |
annotations: this.frameAnnotations | |
} | |
localStorage.setItem('annotations', JSON.stringify(data)) | |
}, | |
// Charger les annotations depuis le format demandé | |
loadAnnotations() { | |
const saved = localStorage.getItem('annotations') | |
if (saved) { | |
const data = JSON.parse(saved) | |
this.currentSession.video = data.video | |
this.currentSession.metadata = data.metadata | |
this.objects = data.objects | |
this.frameAnnotations = data.annotations | |
} | |
}, | |
getAnnotation(frameNumber, annotationId) { | |
if (!this.frameAnnotations[frameNumber]) return null | |
return this.frameAnnotations[frameNumber].find(a => a.id === annotationId) || null | |
}, | |
addTemporaryPoint(point) { | |
// Ajouter un ID unique au point | |
const pointWithId = { | |
...point, | |
id: uuidv4() | |
} | |
this.temporaryPoints.push(pointWithId) | |
return pointWithId.id | |
}, | |
removeTemporaryPoint(pointId) { | |
const index = this.temporaryPoints.findIndex(p => p.id === pointId) | |
if (index !== -1) { | |
this.temporaryPoints.splice(index, 1) | |
} | |
}, | |
clearTemporaryPoints() { | |
this.temporaryPoints = [] | |
}, | |
// Supprimer un objet et toutes ses annotations | |
deleteObject(objectId) { | |
// Supprimer l'objet du dictionnaire | |
delete this.objects[objectId] | |
// Supprimer toutes les annotations associées à cet objet | |
Object.keys(this.frameAnnotations).forEach(frameKey => { | |
this.frameAnnotations[frameKey] = this.frameAnnotations[frameKey] | |
.filter(annotation => annotation.objectId !== objectId) | |
}) | |
// Si l'objet supprimé était sélectionné, désélectionner | |
if (this.selectedObjectId === objectId) { | |
this.selectedObjectId = null | |
} | |
// Supprimer les points temporaires associés à cet objet | |
this.temporaryPoints = this.temporaryPoints.filter(point => point.objectId !== objectId) | |
} | |
} | |
}) |