From c5dd47767561f809608b3e1d3a5f9d725adfa2c3 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Thu, 5 Feb 2026 15:32:19 +0100 Subject: [PATCH] Refactor KmerSet and FrequencyFilter to use immutable K parameter and consistent Copy/Clone methods This commit refactors the KmerSet and related structures to use an immutable K parameter and introduces consistent Copy methods instead of Clone. It also adds attribute API support for KmerSet and KmerSetGroup, and updates persistence logic to handle IDs and metadata correctly. --- pkg/obikmer/frequency_filter.go | 12 +- pkg/obikmer/kmer_set.go | 49 ++-- pkg/obikmer/kmer_set_attributes.go | 362 ++++++++++++++++++++++++++++ pkg/obikmer/kmer_set_group.go | 69 +++--- pkg/obikmer/kmer_set_persistence.go | 58 ++++- pkg/obioptions/version.go | 2 +- 6 files changed, 489 insertions(+), 63 deletions(-) create mode 100644 pkg/obikmer/kmer_set_attributes.go diff --git a/pkg/obikmer/frequency_filter.go b/pkg/obikmer/frequency_filter.go index 7caacf5..3bc7ddf 100644 --- a/pkg/obikmer/frequency_filter.go +++ b/pkg/obikmer/frequency_filter.go @@ -26,7 +26,7 @@ func NewFrequencyFilter(k, minFreq int) *FrequencyFilter { // Utilise un itérateur pour éviter l'allocation d'un vecteur intermédiaire func (ff *FrequencyFilter) AddSequence(seq *obiseq.BioSequence) { rawSeq := seq.Sequence() - for canonical := range IterNormalizedKmers(rawSeq, ff.K) { + for canonical := range IterNormalizedKmers(rawSeq, ff.K()) { ff.addKmer(canonical) } } @@ -48,7 +48,7 @@ func (ff *FrequencyFilter) addKmer(kmer uint64) { // GetFilteredSet retourne un KmerSet des k-mers avec fréquence ≥ minFreq func (ff *FrequencyFilter) GetFilteredSet() *KmerSet { // Les k-mers filtrés sont dans le dernier niveau - return ff.Get(ff.MinFreq - 1).Clone() + return ff.Get(ff.MinFreq - 1).Copy() } // GetKmersAtLevel retourne un KmerSet des k-mers vus au moins (level+1) fois @@ -56,9 +56,9 @@ func (ff *FrequencyFilter) GetFilteredSet() *KmerSet { func (ff *FrequencyFilter) GetKmersAtLevel(level int) *KmerSet { ks := ff.Get(level) if ks == nil { - return NewKmerSet(ff.K) + return NewKmerSet(ff.K()) } - return ks.Clone() + return ks.Copy() } // Stats retourne des statistiques sur les niveaux de fréquence @@ -161,14 +161,14 @@ func (ff *FrequencyFilter) Load(path string) error { // Contains vérifie si un k-mer a atteint la fréquence minimale func (ff *FrequencyFilter) Contains(kmer uint64) bool { - canonical := NormalizeKmer(kmer, ff.K) + canonical := NormalizeKmer(kmer, ff.K()) return ff.Get(ff.MinFreq - 1).Contains(canonical) } // GetFrequency retourne la fréquence approximative d'un k-mer // Retourne le niveau maximum atteint (freq ≥ niveau) func (ff *FrequencyFilter) GetFrequency(kmer uint64) int { - canonical := NormalizeKmer(kmer, ff.K) + canonical := NormalizeKmer(kmer, ff.K()) freq := 0 for i := 0; i < ff.MinFreq; i++ { diff --git a/pkg/obikmer/kmer_set.go b/pkg/obikmer/kmer_set.go index 0eec9df..49bebe7 100644 --- a/pkg/obikmer/kmer_set.go +++ b/pkg/obikmer/kmer_set.go @@ -10,7 +10,8 @@ import ( // KmerSet encapsule un ensemble de k-mers stockés dans un Roaring Bitmap // Fournit des méthodes utilitaires pour manipuler des ensembles de k-mers type KmerSet struct { - K int // Taille des k-mers + id string // Identifiant unique du KmerSet + k int // Taille des k-mers (immutable) bitmap *roaring64.Bitmap // Bitmap contenant les k-mers Metadata map[string]interface{} // Métadonnées utilisateur (clé=valeur atomique) } @@ -18,7 +19,7 @@ type KmerSet struct { // NewKmerSet crée un nouveau KmerSet vide func NewKmerSet(k int) *KmerSet { return &KmerSet{ - K: k, + k: k, bitmap: roaring64.New(), Metadata: make(map[string]interface{}), } @@ -27,12 +28,17 @@ func NewKmerSet(k int) *KmerSet { // NewKmerSetFromBitmap crée un KmerSet à partir d'un bitmap existant func NewKmerSetFromBitmap(k int, bitmap *roaring64.Bitmap) *KmerSet { return &KmerSet{ - K: k, + k: k, bitmap: bitmap, Metadata: make(map[string]interface{}), } } +// K retourne la taille des k-mers (immutable) +func (ks *KmerSet) K() int { + return ks.k +} + // Add ajoute un k-mer à l'ensemble func (ks *KmerSet) Add(kmer uint64) { ks.bitmap.Add(kmer) @@ -42,7 +48,7 @@ func (ks *KmerSet) Add(kmer uint64) { // Utilise un itérateur pour éviter l'allocation d'un vecteur intermédiaire func (ks *KmerSet) AddSequence(seq *obiseq.BioSequence) { rawSeq := seq.Sequence() - for canonical := range IterNormalizedKmers(rawSeq, ks.K) { + for canonical := range IterNormalizedKmers(rawSeq, ks.k) { ks.bitmap.Add(canonical) } } @@ -74,8 +80,8 @@ func (ks *KmerSet) Clear() { ks.bitmap.Clear() } -// Clone crée une copie de l'ensemble -func (ks *KmerSet) Clone() *KmerSet { +// Copy crée une copie de l'ensemble (cohérent avec BioSequence.Copy) +func (ks *KmerSet) Copy() *KmerSet { // Copier les métadonnées metadata := make(map[string]interface{}, len(ks.Metadata)) for k, v := range ks.Metadata { @@ -83,40 +89,51 @@ func (ks *KmerSet) Clone() *KmerSet { } return &KmerSet{ - K: ks.K, + id: ks.id, + k: ks.k, bitmap: ks.bitmap.Clone(), Metadata: metadata, } } +// Id retourne l'identifiant du KmerSet (cohérent avec BioSequence.Id) +func (ks *KmerSet) Id() string { + return ks.id +} + +// SetId définit l'identifiant du KmerSet (cohérent avec BioSequence.SetId) +func (ks *KmerSet) SetId(id string) { + ks.id = id +} + // Union retourne l'union de cet ensemble avec un autre func (ks *KmerSet) Union(other *KmerSet) *KmerSet { - if ks.K != other.K { - panic(fmt.Sprintf("Cannot union KmerSets with different k values: %d vs %d", ks.K, other.K)) + if ks.k != other.k { + panic(fmt.Sprintf("Cannot union KmerSets with different k values: %d vs %d", ks.k, other.k)) } result := ks.bitmap.Clone() result.Or(other.bitmap) - return NewKmerSetFromBitmap(ks.K, result) + return NewKmerSetFromBitmap(ks.k, result) } // Intersect retourne l'intersection de cet ensemble avec un autre func (ks *KmerSet) Intersect(other *KmerSet) *KmerSet { - if ks.K != other.K { - panic(fmt.Sprintf("Cannot intersect KmerSets with different k values: %d vs %d", ks.K, other.K)) + if ks.k != other.k { + panic(fmt.Sprintf("Cannot intersect KmerSets with different k values: %d vs %d", ks.k, other.k)) } result := ks.bitmap.Clone() result.And(other.bitmap) - return NewKmerSetFromBitmap(ks.K, result) + return NewKmerSetFromBitmap(ks.k, result) } // Difference retourne la différence de cet ensemble avec un autre (this - other) func (ks *KmerSet) Difference(other *KmerSet) *KmerSet { - if ks.K != other.K { - panic(fmt.Sprintf("Cannot subtract KmerSets with different k values: %d vs %d", ks.K, other.K)) + if ks.k != other.k { + panic(fmt.Sprintf("Cannot subtract KmerSets with different k values: %d vs %d", ks.k, other.k)) } result := ks.bitmap.Clone() result.AndNot(other.bitmap) - return NewKmerSetFromBitmap(ks.K, result) + return NewKmerSetFromBitmap(ks.k, result) } // Iterator retourne un itérateur sur tous les k-mers de l'ensemble diff --git a/pkg/obikmer/kmer_set_attributes.go b/pkg/obikmer/kmer_set_attributes.go new file mode 100644 index 0000000..dc60f76 --- /dev/null +++ b/pkg/obikmer/kmer_set_attributes.go @@ -0,0 +1,362 @@ +package obikmer + +import ( + "fmt" + "strconv" + + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiutils" +) + +// ================================== +// KMER SET ATTRIBUTE API +// Mimic BioSequence attribute API from obiseq/attributes.go +// ================================== + +// HasAttribute vérifie si une clé d'attribut existe +func (ks *KmerSet) HasAttribute(key string) bool { + _, ok := ks.Metadata[key] + return ok +} + +// GetAttribute récupère la valeur d'un attribut +// Cas particuliers: "id" utilise Id(), "k" utilise K() +func (ks *KmerSet) GetAttribute(key string) (interface{}, bool) { + switch key { + case "id": + return ks.Id(), true + case "k": + return ks.K(), true + default: + value, ok := ks.Metadata[key] + return value, ok + } +} + +// SetAttribute définit la valeur d'un attribut +// Cas particuliers: "id" utilise SetId(), "k" est immutable (panique) +func (ks *KmerSet) SetAttribute(key string, value interface{}) { + switch key { + case "id": + if id, ok := value.(string); ok { + ks.SetId(id) + } else { + panic(fmt.Sprintf("id must be a string, got %T", value)) + } + case "k": + panic("k is immutable and cannot be modified via SetAttribute") + default: + ks.Metadata[key] = value + } +} + +// DeleteAttribute supprime un attribut +func (ks *KmerSet) DeleteAttribute(key string) { + delete(ks.Metadata, key) +} + +// RemoveAttribute supprime un attribut (alias de DeleteAttribute) +func (ks *KmerSet) RemoveAttribute(key string) { + ks.DeleteAttribute(key) +} + +// RenameAttribute renomme un attribut +func (ks *KmerSet) RenameAttribute(newName, oldName string) { + if value, ok := ks.Metadata[oldName]; ok { + ks.Metadata[newName] = value + delete(ks.Metadata, oldName) + } +} + +// GetIntAttribute récupère un attribut en tant qu'entier +func (ks *KmerSet) GetIntAttribute(key string) (int, bool) { + value, ok := ks.Metadata[key] + if !ok { + return 0, false + } + + switch v := value.(type) { + case int: + return v, true + case int64: + return int(v), true + case float64: + return int(v), true + case string: + if i, err := strconv.Atoi(v); err == nil { + return i, true + } + } + return 0, false +} + +// GetFloatAttribute récupère un attribut en tant que float64 +func (ks *KmerSet) GetFloatAttribute(key string) (float64, bool) { + value, ok := ks.Metadata[key] + if !ok { + return 0, false + } + + switch v := value.(type) { + case float64: + return v, true + case float32: + return float64(v), true + case int: + return float64(v), true + case int64: + return float64(v), true + case string: + if f, err := strconv.ParseFloat(v, 64); err == nil { + return f, true + } + } + return 0, false +} + +// GetNumericAttribute récupère un attribut numérique (alias de GetFloatAttribute) +func (ks *KmerSet) GetNumericAttribute(key string) (float64, bool) { + return ks.GetFloatAttribute(key) +} + +// GetStringAttribute récupère un attribut en tant que chaîne +func (ks *KmerSet) GetStringAttribute(key string) (string, bool) { + value, ok := ks.Metadata[key] + if !ok { + return "", false + } + + switch v := value.(type) { + case string: + return v, true + default: + return fmt.Sprintf("%v", v), true + } +} + +// GetBoolAttribute récupère un attribut en tant que booléen +func (ks *KmerSet) GetBoolAttribute(key string) (bool, bool) { + value, ok := ks.Metadata[key] + if !ok { + return false, false + } + + switch v := value.(type) { + case bool: + return v, true + case int: + return v != 0, true + case string: + if b, err := strconv.ParseBool(v); err == nil { + return b, true + } + } + return false, false +} + +// AttributeKeys retourne l'ensemble des clés d'attributs +func (ks *KmerSet) AttributeKeys() obiutils.Set[string] { + keys := obiutils.MakeSet[string]() + for key := range ks.Metadata { + keys.Add(key) + } + return keys +} + +// Keys retourne l'ensemble des clés d'attributs (alias de AttributeKeys) +func (ks *KmerSet) Keys() obiutils.Set[string] { + return ks.AttributeKeys() +} + +// ================================== +// KMER SET GROUP ATTRIBUTE API +// Métadonnées du groupe + accès via Get() pour les sets individuels +// ================================== + +// HasAttribute vérifie si une clé d'attribut existe pour le groupe +func (ksg *KmerSetGroup) HasAttribute(key string) bool { + _, ok := ksg.Metadata[key] + return ok +} + +// GetAttribute récupère la valeur d'un attribut du groupe +// Cas particuliers: "id" utilise Id(), "k" utilise K() +func (ksg *KmerSetGroup) GetAttribute(key string) (interface{}, bool) { + switch key { + case "id": + return ksg.Id(), true + case "k": + return ksg.K(), true + default: + value, ok := ksg.Metadata[key] + return value, ok + } +} + +// SetAttribute définit la valeur d'un attribut du groupe +// Cas particuliers: "id" utilise SetId(), "k" est immutable (panique) +func (ksg *KmerSetGroup) SetAttribute(key string, value interface{}) { + switch key { + case "id": + if id, ok := value.(string); ok { + ksg.SetId(id) + } else { + panic(fmt.Sprintf("id must be a string, got %T", value)) + } + case "k": + panic("k is immutable and cannot be modified via SetAttribute") + default: + ksg.Metadata[key] = value + } +} + +// DeleteAttribute supprime un attribut du groupe +func (ksg *KmerSetGroup) DeleteAttribute(key string) { + delete(ksg.Metadata, key) +} + +// RemoveAttribute supprime un attribut du groupe (alias) +func (ksg *KmerSetGroup) RemoveAttribute(key string) { + ksg.DeleteAttribute(key) +} + +// RenameAttribute renomme un attribut du groupe +func (ksg *KmerSetGroup) RenameAttribute(newName, oldName string) { + if value, ok := ksg.Metadata[oldName]; ok { + ksg.Metadata[newName] = value + delete(ksg.Metadata, oldName) + } +} + +// GetIntAttribute récupère un attribut entier du groupe +func (ksg *KmerSetGroup) GetIntAttribute(key string) (int, bool) { + value, ok := ksg.GetAttribute(key) + if !ok { + return 0, false + } + + switch v := value.(type) { + case int: + return v, true + case int64: + return int(v), true + case float64: + return int(v), true + case string: + if i, err := strconv.Atoi(v); err == nil { + return i, true + } + } + return 0, false +} + +// GetFloatAttribute récupère un attribut float64 du groupe +func (ksg *KmerSetGroup) GetFloatAttribute(key string) (float64, bool) { + value, ok := ksg.GetAttribute(key) + if !ok { + return 0, false + } + + switch v := value.(type) { + case float64: + return v, true + case float32: + return float64(v), true + case int: + return float64(v), true + case int64: + return float64(v), true + case string: + if f, err := strconv.ParseFloat(v, 64); err == nil { + return f, true + } + } + return 0, false +} + +// GetNumericAttribute récupère un attribut numérique du groupe +func (ksg *KmerSetGroup) GetNumericAttribute(key string) (float64, bool) { + return ksg.GetFloatAttribute(key) +} + +// GetStringAttribute récupère un attribut chaîne du groupe +func (ksg *KmerSetGroup) GetStringAttribute(key string) (string, bool) { + value, ok := ksg.GetAttribute(key) + if !ok { + return "", false + } + + switch v := value.(type) { + case string: + return v, true + default: + return fmt.Sprintf("%v", v), true + } +} + +// GetBoolAttribute récupère un attribut booléen du groupe +func (ksg *KmerSetGroup) GetBoolAttribute(key string) (bool, bool) { + value, ok := ksg.GetAttribute(key) + if !ok { + return false, false + } + + switch v := value.(type) { + case bool: + return v, true + case int: + return v != 0, true + case string: + if b, err := strconv.ParseBool(v); err == nil { + return b, true + } + } + return false, false +} + +// AttributeKeys retourne l'ensemble des clés d'attributs du groupe +func (ksg *KmerSetGroup) AttributeKeys() obiutils.Set[string] { + keys := obiutils.MakeSet[string]() + for key := range ksg.Metadata { + keys.Add(key) + } + return keys +} + +// Keys retourne l'ensemble des clés d'attributs du groupe (alias) +func (ksg *KmerSetGroup) Keys() obiutils.Set[string] { + return ksg.AttributeKeys() +} + +// ================================== +// MÉTHODES POUR ACCÉDER AUX ATTRIBUTS DES SETS INDIVIDUELS VIA Get() +// Architecture zero-copy: ksg.Get(i).SetAttribute(...) +// ================================== + +// Exemple d'utilisation: +// Pour accéder aux métadonnées d'un KmerSet individuel dans un groupe: +// ks := ksg.Get(0) +// ks.SetAttribute("level", 1) +// hasLevel := ks.HasAttribute("level") +// +// Pour les métadonnées du groupe: +// ksg.SetAttribute("name", "FrequencyFilter") +// name, ok := ksg.GetStringAttribute("name") + +// AllAttributeKeys retourne toutes les clés d'attributs uniques du groupe ET de tous ses sets +func (ksg *KmerSetGroup) AllAttributeKeys() obiutils.Set[string] { + keys := obiutils.MakeSet[string]() + + // Ajouter les clés du groupe + for key := range ksg.Metadata { + keys.Add(key) + } + + // Ajouter les clés de chaque set + for _, ks := range ksg.sets { + for key := range ks.Metadata { + keys.Add(key) + } + } + + return keys +} diff --git a/pkg/obikmer/kmer_set_group.go b/pkg/obikmer/kmer_set_group.go index 6bbf39e..3d1b30c 100644 --- a/pkg/obikmer/kmer_set_group.go +++ b/pkg/obikmer/kmer_set_group.go @@ -9,9 +9,10 @@ import ( // KmerSetGroup représente un vecteur de KmerSet // Utilisé pour gérer plusieurs ensembles de k-mers (par exemple, par niveau de fréquence) type KmerSetGroup struct { - K int // Taille des k-mers - sets []*KmerSet // Vecteur de KmerSet - Metadata []map[string]interface{} // Métadonnées par KmerSet (même longueur que sets) + id string // Identifiant unique du KmerSetGroup + k int // Taille des k-mers (immutable) + sets []*KmerSet // Vecteur de KmerSet + Metadata map[string]interface{} // Métadonnées du groupe (pas des sets individuels) } // NewKmerSetGroup crée un nouveau groupe de n KmerSets @@ -21,19 +22,22 @@ func NewKmerSetGroup(k int, n int) *KmerSetGroup { } sets := make([]*KmerSet, n) - metadata := make([]map[string]interface{}, n) for i := range sets { sets[i] = NewKmerSet(k) - metadata[i] = make(map[string]interface{}) } return &KmerSetGroup{ - K: k, + k: k, sets: sets, - Metadata: metadata, + Metadata: make(map[string]interface{}), } } +// K retourne la taille des k-mers (immutable) +func (ksg *KmerSetGroup) K() int { + return ksg.k +} + // Size retourne le nombre de KmerSet dans le groupe func (ksg *KmerSetGroup) Size() int { return len(ksg.sets) @@ -54,8 +58,8 @@ func (ksg *KmerSetGroup) Set(index int, ks *KmerSet) { if index < 0 || index >= len(ksg.sets) { panic(fmt.Sprintf("Index out of bounds: %d (size: %d)", index, len(ksg.sets))) } - if ks.K != ksg.K { - panic(fmt.Sprintf("KmerSet k mismatch: expected %d, got %d", ksg.K, ks.K)) + if ks.k != ksg.k { + panic(fmt.Sprintf("KmerSet k mismatch: expected %d, got %d", ksg.k, ks.k)) } ksg.sets[index] = ks } @@ -93,28 +97,37 @@ func (ksg *KmerSetGroup) Clear() { } } -// Clone crée une copie complète du groupe -func (ksg *KmerSetGroup) Clone() *KmerSetGroup { - clonedSets := make([]*KmerSet, len(ksg.sets)) - clonedMetadata := make([]map[string]interface{}, len(ksg.Metadata)) - +// Copy crée une copie complète du groupe (cohérent avec BioSequence.Copy) +func (ksg *KmerSetGroup) Copy() *KmerSetGroup { + copiedSets := make([]*KmerSet, len(ksg.sets)) for i, ks := range ksg.sets { - clonedSets[i] = ks.Clone() + copiedSets[i] = ks.Copy() // Copy chaque KmerSet avec ses métadonnées + } - // Copier les métadonnées du groupe - clonedMetadata[i] = make(map[string]interface{}, len(ksg.Metadata[i])) - for k, v := range ksg.Metadata[i] { - clonedMetadata[i][k] = v - } + // Copier les métadonnées du groupe + groupMetadata := make(map[string]interface{}, len(ksg.Metadata)) + for k, v := range ksg.Metadata { + groupMetadata[k] = v } return &KmerSetGroup{ - K: ksg.K, - sets: clonedSets, - Metadata: clonedMetadata, + id: ksg.id, + k: ksg.k, + sets: copiedSets, + Metadata: groupMetadata, } } +// Id retourne l'identifiant du KmerSetGroup (cohérent avec BioSequence.Id) +func (ksg *KmerSetGroup) Id() string { + return ksg.id +} + +// SetId définit l'identifiant du KmerSetGroup (cohérent avec BioSequence.SetId) +func (ksg *KmerSetGroup) SetId(id string) { + ksg.id = id +} + // AddSequence ajoute tous les k-mers d'une séquence à un KmerSet spécifique func (ksg *KmerSetGroup) AddSequence(seq *obiseq.BioSequence, index int) { if index < 0 || index >= len(ksg.sets) { @@ -134,10 +147,10 @@ func (ksg *KmerSetGroup) AddSequences(sequences *obiseq.BioSequenceSlice, index // Union retourne l'union de tous les KmerSet du groupe func (ksg *KmerSetGroup) Union() *KmerSet { if len(ksg.sets) == 0 { - return NewKmerSet(ksg.K) + return NewKmerSet(ksg.k) } - result := ksg.sets[0].Clone() + result := ksg.sets[0].Copy() for i := 1; i < len(ksg.sets); i++ { result = result.Union(ksg.sets[i]) } @@ -147,10 +160,10 @@ func (ksg *KmerSetGroup) Union() *KmerSet { // Intersect retourne l'intersection de tous les KmerSet du groupe func (ksg *KmerSetGroup) Intersect() *KmerSet { if len(ksg.sets) == 0 { - return NewKmerSet(ksg.K) + return NewKmerSet(ksg.k) } - result := ksg.sets[0].Clone() + result := ksg.sets[0].Copy() for i := 1; i < len(ksg.sets); i++ { result = result.Intersect(ksg.sets[i]) } @@ -173,7 +186,7 @@ type KmerSetStats struct { func (ksg *KmerSetGroup) Stats() KmerSetGroupStats { stats := KmerSetGroupStats{ - K: ksg.K, + K: ksg.k, Size: len(ksg.sets), Sets: make([]KmerSetStats, len(ksg.sets)), } diff --git a/pkg/obikmer/kmer_set_persistence.go b/pkg/obikmer/kmer_set_persistence.go index 531d59f..391bc1e 100644 --- a/pkg/obikmer/kmer_set_persistence.go +++ b/pkg/obikmer/kmer_set_persistence.go @@ -36,12 +36,14 @@ func (f MetadataFormat) String() string { // KmerSetMetadata contient les métadonnées d'un KmerSet ou KmerSetGroup type KmerSetMetadata struct { - K int `toml:"k" yaml:"k" json:"k"` // Taille des k-mers - Type string `toml:"type" yaml:"type" json:"type"` // "KmerSet" ou "KmerSetGroup" - Size int `toml:"size" yaml:"size" json:"size"` // 1 pour KmerSet, n pour KmerSetGroup - Files []string `toml:"files" yaml:"files" json:"files"` // Liste des fichiers .roaring - UserMetadata map[string]interface{} `toml:"user_metadata,omitempty" yaml:"user_metadata,omitempty" json:"user_metadata,omitempty"` // Métadonnées KmerSet unique - SetsMetadata []map[string]interface{} `toml:"sets_metadata,omitempty" yaml:"sets_metadata,omitempty" json:"sets_metadata,omitempty"` // Métadonnées par set (KmerSetGroup) + ID string `toml:"id,omitempty" yaml:"id,omitempty" json:"id,omitempty"` // Identifiant unique + K int `toml:"k" yaml:"k" json:"k"` // Taille des k-mers + Type string `toml:"type" yaml:"type" json:"type"` // "KmerSet" ou "KmerSetGroup" + Size int `toml:"size" yaml:"size" json:"size"` // 1 pour KmerSet, n pour KmerSetGroup + Files []string `toml:"files" yaml:"files" json:"files"` // Liste des fichiers .roaring + SetsIDs []string `toml:"sets_ids,omitempty" yaml:"sets_ids,omitempty" json:"sets_ids,omitempty"` // IDs des KmerSet individuels + UserMetadata map[string]interface{} `toml:"user_metadata,omitempty" yaml:"user_metadata,omitempty" json:"user_metadata,omitempty"` // Métadonnées KmerSet ou KmerSetGroup + SetsMetadata []map[string]interface{} `toml:"sets_metadata,omitempty" yaml:"sets_metadata,omitempty" json:"sets_metadata,omitempty"` // Métadonnées des KmerSet individuels dans un KmerSetGroup } // SaveKmerSet sauvegarde un KmerSet dans un répertoire @@ -54,7 +56,8 @@ func (ks *KmerSet) Save(directory string, format MetadataFormat) error { // Métadonnées metadata := KmerSetMetadata{ - K: ks.K, + ID: ks.id, + K: ks.k, Type: "KmerSet", Size: 1, Files: []string{"set_0.roaring"}, @@ -109,6 +112,9 @@ func LoadKmerSet(directory string) (*KmerSet, error) { ks := NewKmerSet(metadata.K) + // Charger l'ID + ks.id = metadata.ID + // Charger les métadonnées utilisateur if metadata.UserMetadata != nil { ks.Metadata = metadata.UserMetadata @@ -135,12 +141,23 @@ func (ksg *KmerSetGroup) Save(directory string, format MetadataFormat) error { files[i] = fmt.Sprintf("set_%d.roaring", i) } + // Collecter les IDs et métadonnées de chaque KmerSet individuel + setsIDs := make([]string, len(ksg.sets)) + setsMetadata := make([]map[string]interface{}, len(ksg.sets)) + for i, ks := range ksg.sets { + setsIDs[i] = ks.id + setsMetadata[i] = ks.Metadata + } + metadata := KmerSetMetadata{ - K: ksg.K, + ID: ksg.id, + K: ksg.k, Type: "KmerSetGroup", Size: len(ksg.sets), Files: files, - SetsMetadata: ksg.Metadata, // Sauvegarder les métadonnées de chaque set + SetsIDs: setsIDs, // IDs de chaque set + UserMetadata: ksg.Metadata, // Métadonnées du groupe + SetsMetadata: setsMetadata, // Métadonnées de chaque set } // Sauvegarder les métadonnées @@ -187,12 +204,29 @@ func LoadKmerSetGroup(directory string) (*KmerSetGroup, error) { // Créer le groupe ksg := NewKmerSetGroup(metadata.K, metadata.Size) - // Charger les métadonnées de chaque set + // Charger l'ID du groupe + ksg.id = metadata.ID + + // Charger les métadonnées du groupe + if metadata.UserMetadata != nil { + ksg.Metadata = metadata.UserMetadata + } + + // Charger les IDs de chaque KmerSet + if metadata.SetsIDs != nil && len(metadata.SetsIDs) == metadata.Size { + for i := range ksg.sets { + ksg.sets[i].id = metadata.SetsIDs[i] + } + } + + // Charger les métadonnées de chaque KmerSet individuel if metadata.SetsMetadata != nil { if len(metadata.SetsMetadata) != metadata.Size { - return nil, fmt.Errorf("metadata size mismatch: expected %d, got %d", metadata.Size, len(metadata.SetsMetadata)) + return nil, fmt.Errorf("sets metadata size mismatch: expected %d, got %d", metadata.Size, len(metadata.SetsMetadata)) + } + for i := range ksg.sets { + ksg.sets[i].Metadata = metadata.SetsMetadata[i] } - ksg.Metadata = metadata.SetsMetadata } // Charger chaque bitmap diff --git a/pkg/obioptions/version.go b/pkg/obioptions/version.go index adccd28..8bacc19 100644 --- a/pkg/obioptions/version.go +++ b/pkg/obioptions/version.go @@ -8,7 +8,7 @@ import ( // corresponds to the last commit, and not the one when the file will be // commited -var _Commit = "b26b76c" +var _Commit = "afcb43b" var _Version = "Release 4.4.0" // Version returns the version of the obitools package.