Aller au contenu

Métadonnées et meta maps

Traduction assistée par IA - en savoir plus et suggérer des améliorations

Dans toute analyse scientifique, nous travaillons rarement uniquement avec les fichiers de données brutes. Chaque fichier s'accompagne de ses propres informations supplémentaires : ce qu'il est, d'où il vient et ce qui le rend spécial. Ces informations supplémentaires sont ce que nous appelons les métadonnées.

Les métadonnées sont des données décrivant d'autres données. Les métadonnées permettent de suivre les détails importants concernant les fichiers et les conditions expérimentales, et aident à adapter les analyses aux caractéristiques uniques de chaque ensemble de données.

Imaginez cela comme un catalogue de bibliothèque : alors que les livres contiennent le contenu réel (données brutes), les fiches du catalogue fournissent des informations essentielles sur chaque livre—quand il a été publié, qui l'a écrit, où le trouver (métadonnées). Dans les pipelines Nextflow, les métadonnées peuvent être utilisées pour :

  • Suivre les informations spécifiques aux fichiers tout au long du workflow
  • Configurer les processus en fonction des caractéristiques des fichiers
  • Regrouper les fichiers liés pour une analyse conjointe

Objectifs d'apprentissage

Dans cette quête secondaire, nous explorerons comment gérer les métadonnées dans les workflows. En partant d'une simple feuille de données (souvent appelée samplesheet en bioinformatique) contenant des informations de base sur les fichiers, vous apprendrez à :

  • Lire et analyser les métadonnées de fichiers à partir de fichiers CSV
  • Créer et manipuler des maps de métadonnées
  • Ajouter de nouveaux champs de métadonnées pendant l'exécution du workflow
  • Utiliser les métadonnées pour personnaliser le comportement des processus

Ces compétences vous aideront à construire des pipelines plus robustes et flexibles qui peuvent gérer des relations de fichiers et des exigences de traitement complexes.

Prérequis

Avant d'entreprendre cette quête secondaire, vous devriez :

  • Avoir complété le tutoriel Hello Nextflow ou un cours équivalent pour débutants.
  • Être à l'aise avec l'utilisation des concepts et mécanismes de base de Nextflow (processus, canaux, opérateurs)

0. Pour commencer

Ouvrir le codespace de formation

Si vous ne l'avez pas encore fait, assurez-vous d'ouvrir l'environnement de formation comme décrit dans la Configuration de l'environnement.

Open in GitHub Codespaces

Se déplacer dans le répertoire du projet

Déplaçons-nous dans le répertoire où se trouvent les fichiers pour ce tutoriel.

cd side-quests/metadata

Vous pouvez configurer VSCode pour se concentrer sur ce répertoire :

code .

Examiner les ressources

Vous trouverez un fichier de workflow principal et un répertoire data contenant une feuille de données et quelques fichiers de données.

Contenu du répertoire
.
├── data
│   ├── bonjour.txt
│   ├── ciao.txt
│   ├── guten_tag.txt
│   ├── hallo.txt
│   ├── hello.txt
│   ├── hola.txt
│   ├── salut.txt
│   └── datasheet.csv
├── main.nf
└── nextflow.config

Le workflow dans le fichier main.nf est une ébauche que vous développerez progressivement en un workflow pleinement fonctionnel.

La feuille de données répertorie les chemins vers les fichiers de données et certaines métadonnées associées, organisées en 3 colonnes :

  • id : auto-explicatif, un identifiant attribué au fichier
  • character : un nom de personnage, que nous utiliserons plus tard pour dessiner différentes créatures
  • data : chemins vers les fichiers .txt qui contiennent des salutations dans différentes langues
datasheet.csv
id,character,recording
sampleA,squirrel,/workspaces/training/side-quests/metadata/data/bonjour.txt
sampleB,tux,/workspaces/training/side-quests/metadata/data/guten_tag.txt
sampleC,sheep,/workspaces/training/side-quests/metadata/data/hallo.txt
sampleD,turkey,/workspaces/training/side-quests/metadata/data/hello.txt
sampleE,stegosaurus,/workspaces/training/side-quests/metadata/data/hola.txt
sampleF,moose,/workspaces/training/side-quests/metadata/data/salut.txt
sampleG,turtle,/workspaces/training/side-quests/metadata/data/ciao.txt

Chaque fichier de données contient du texte de salutation dans l'une des cinq langues (fr : français, de : allemand, es : espagnol, it : italien, en : anglais).

Nous vous fournirons également un outil d'analyse de langue conteneurisé appelé langid.

Examiner l'assignation

Votre défi est d'écrire un workflow Nextflow qui va :

  1. Identifier automatiquement la langue dans chaque fichier
  2. Regrouper les fichiers par famille de langues (langues germaniques vs langues romanes)
  3. Personnaliser le traitement de chaque fichier en fonction de sa langue et de ses métadonnées
  4. Organiser les sorties par groupe de langues

Cela représente un modèle de workflow typique où les métadonnées spécifiques aux fichiers guident les décisions de traitement ; exactement le type de problème que les meta maps résolvent élégamment.

Liste de vérification de préparation

Pensez-vous être prêt·e à vous lancer ?

  • Je comprends l'objectif de ce cours et ses prérequis
  • Mon codespace est opérationnel
  • J'ai défini mon répertoire de travail de manière appropriée
  • Je comprends l'assignation

Si vous pouvez cocher toutes les cases, vous êtes prêt·e à commencer.


1. Charger les métadonnées depuis une feuille de données

Ouvrez le fichier de workflow main.nf pour examiner l'ébauche de workflow que nous vous donnons comme point de départ.

main.nf
1
2
3
4
5
6
7
#!/usr/bin/env nextflow

workflow  {

    ch_datasheet = channel.fromPath("./data/datasheet.csv")

}

Vous pouvez voir que nous avons configuré une factory de canal de base pour charger l'exemple de feuille de données en tant que fichier, mais cela ne lira pas encore le contenu du fichier. Commençons par ajouter cela.

1.1. Lire le contenu avec splitCsv

Nous devons choisir un opérateur qui analysera le contenu du fichier de manière appropriée avec un effort minimal de notre part. Puisque notre feuille de données est au format CSV, c'est un travail pour l'opérateur splitCsv, qui charge chaque ligne du fichier comme un élément dans le canal.

Effectuez les modifications suivantes pour ajouter une opération splitCsv() au code de construction du canal, plus une opération view() pour vérifier que le contenu du fichier est correctement chargé dans le canal.

main.nf
3
4
5
6
7
8
9
workflow  {

    ch_datasheet = channel.fromPath("./data/datasheet.csv")
        .splitCsv(header: true)
        .view()

}
main.nf
3
4
5
6
7
workflow  {

    ch_datasheet = channel.fromPath("./data/datasheet.csv")

}

Notez que nous utilisons l'option header: true pour indiquer à Nextflow de lire la première ligne du fichier CSV comme ligne d'en-tête.

Voyons ce qui en ressort, d'accord ? Exécutez le workflow :

nextflow run main.nf
Sortie de la commande
 N E X T F L O W   ~  version 25.10.2

Launching `main.nf` [exotic_albattani] DSL2 - revision: c0d03cec83

[id:sampleA, character:squirrel, recording:/workspaces/training/side-quests/metadata/data/bonjour.txt]
[id:sampleB, character:tux, recording:/workspaces/training/side-quests/metadata/data/guten_tag.txt]
[id:sampleC, character:sheep, recording:/workspaces/training/side-quests/metadata/data/hallo.txt]
[id:sampleD, character:turkey, recording:/workspaces/training/side-quests/metadata/data/hello.txt]
[id:sampleE, character:stegosaurus, recording:/workspaces/training/side-quests/metadata/data/hola.txt]
[id:sampleF, character:moose, recording:/workspaces/training/side-quests/metadata/data/salut.txt]
[id:sampleG, character:turtle, recording:/workspaces/training/side-quests/metadata/data/ciao.txt]

Nous pouvons voir que l'opérateur a construit une map de paires clé-valeur pour chaque ligne du fichier CSV, avec les en-têtes de colonnes comme clés pour les valeurs correspondantes.

Chaque entrée de map correspond à une colonne dans notre feuille de données :

  • id
  • character
  • recording

C'est excellent ! Cela facilite l'accès à des champs spécifiques de chaque fichier. Par exemple, nous pourrions accéder à l'identifiant du fichier avec id ou au chemin du fichier txt avec recording.

(Optionnel) En savoir plus sur les maps

Dans Groovy, le langage de programmation sur lequel Nextflow est construit, une map est une structure de données clé-valeur similaire aux dictionnaires en Python, aux objets en JavaScript ou aux hashes en Ruby.

Voici un script exécutable qui montre comment vous pouvez définir une map et accéder à son contenu en pratique :

examples/map_demo.nf
#!/usr/bin/env nextflow

// Créer une map simple
def my_map = [id:'sampleA', character:'squirrel']

// Afficher toute la map
println "map: ${my_map}"

// Accéder aux valeurs individuelles en utilisant la notation par point
println "id: ${my_map.id}"
println "character: ${my_map.character}"

Même s'il n'a pas de bloc workflow approprié, Nextflow peut exécuter cela comme s'il s'agissait d'un workflow :

nextflow run examples/map_demo.nf

Et voici ce que vous pouvez vous attendre à voir dans la sortie :

Sortie
 N E X T F L O W   ~  version 25.10.2

Launching `map_demo.nf` [cheesy_plateau] DSL2 - revision: fae5b8496e

map: [id:sampleA, character:squirrel]
id: sampleA
character: squirrel

1.2. Sélectionner des champs spécifiques avec map

Disons que nous voulons accéder à la colonne character de la feuille de données et l'afficher. Nous pouvons utiliser l'opérateur Nextflow map pour itérer sur chaque élément dans notre canal et sélectionner spécifiquement l'entrée character de l'objet map.

Effectuez les modifications suivantes au workflow :

main.nf
workflow  {

    ch_datasheet = channel.fromPath("./data/datasheet.csv")
        .splitCsv(header: true)
        .map{ row ->
            row.character
        }
        .view()

}
main.nf
3
4
5
6
7
8
9
workflow  {

    ch_datasheet = channel.fromPath("./data/datasheet.csv")
        .splitCsv(header: true)
        .view()

}

Maintenant, exécutez à nouveau le workflow :

nextflow run main.nf
Sortie de la commande
 N E X T F L O W   ~  version 25.10.2

Launching `main.nf` [exotic_albattani] DSL2 - revision: c0d03cec83

squirrel
tux
sheep
turkey
stegosaurus
moose
turtle

Succès ! Nous avons tiré parti de la structure de map dérivée de notre feuille de données pour accéder aux valeurs de colonnes individuelles pour chaque ligne.

Maintenant que nous avons lu avec succès la feuille de données et que nous avons accès aux données de chaque ligne, nous pouvons commencer à implémenter la logique de notre pipeline.

1.3. Organiser les métadonnées dans une 'meta map'

Dans l'état actuel du workflow, les fichiers d'entrée (sous la clé recording) et les métadonnées associées (id, character) sont tous sur le même pied, comme s'ils étaient tous dans un grand sac. La conséquence pratique est que chaque processus qui consomme ce canal devrait être configuré avec cette structure à l'esprit :

    input:
    tuple val(id), val(character), file(recording)

C'est bien tant que le nombre de colonnes dans la feuille de données ne change pas. Cependant, si vous ajoutez ne serait-ce qu'une seule colonne à la feuille de données, la forme du canal ne correspondra plus à ce que le processus attend, et le workflow produira des erreurs. Cela rend également le processus difficile à partager avec d'autres qui pourraient avoir des données d'entrée légèrement différentes, et vous pourriez finir par devoir coder en dur des variables dans le processus qui ne sont pas nécessaires au bloc script.

Pour éviter ce problème, nous devons trouver un moyen de maintenir la structure du canal cohérente quel que soit le nombre de colonnes que contient la feuille de données.

Nous pouvons le faire en collectant toutes les métadonnées dans un élément au sein du tuple, que nous appellerons la map de métadonnées, ou plus simplement 'meta map'.

Effectuez les modifications suivantes à l'opération map :

main.nf
    ch_datasheet = channel.fromPath("./data/datasheet.csv")
        .splitCsv(header: true)
        .map { row ->
                [ [id: row.id, character: row.character], row.recording ]
        }
        .view()
main.nf
    ch_datasheet = channel.fromPath("./data/datasheet.csv")
        .splitCsv(header: true)
        .map{ row ->
            row.character
        }
        .view()

Nous avons restructuré nos éléments de canal en un tuple composé de deux éléments, la meta map et l'objet fichier correspondant.

Exécutons le workflow :

nextflow run main.nf
Sortie de la commande
Afficher la meta map
 N E X T F L O W   ~  version 25.10.2

Launching `main.nf` [lethal_booth] DSL2 - revision: 0d8f844c07

[[id:sampleA, character:squirrel], /workspaces/training/side-quests/metadata/data/bonjour.txt]
[[id:sampleB, character:tux], /workspaces/training/side-quests/metadata/data/guten_tag.txt]
[[id:sampleC, character:sheep], /workspaces/training/side-quests/metadata/data/hallo.txt]
[[id:sampleD, character:turkey], /workspaces/training/side-quests/metadata/data/hello.txt]
[[id:sampleE, character:stegosaurus], /workspaces/training/side-quests/metadata/data/hola.txt]
[[id:sampleF, character:moose], /workspaces/training/side-quests/metadata/data/salut.txt]
[[id:sampleG, character:turtle], /workspaces/training/side-quests/metadata/data/ciao.txt]

Maintenant, chaque élément dans le canal contient la meta map en premier et l'objet fichier correspondant en second :

Exemple de structure de sortie
[
  [id:sampleA, character:squirrel],
  /workspaces/training/side-quests/metadata/data/bonjour.txt
]

En conséquence, l'ajout de plus de colonnes dans la feuille de données rendra plus de métadonnées disponibles dans la map meta, mais ne changera pas la forme du canal. Cela nous permet d'écrire des processus qui consomment le canal sans avoir à coder en dur les éléments de métadonnées dans la spécification d'entrée :

Exemple de syntaxe
    input:
    tuple val(meta), file(recording)

C'est une convention largement utilisée pour organiser les métadonnées dans les workflows Nextflow.

À retenir

Dans cette section, vous avez appris :

  • Pourquoi les métadonnées sont importantes : Garder les métadonnées avec vos données préserve les informations importantes sur les fichiers tout au long du workflow.
  • Comment lire des feuilles de données : Utiliser splitCsv pour lire les fichiers CSV avec des informations d'en-tête et transformer les lignes en données structurées
  • Comment créer une meta map : Séparer les métadonnées des données de fichiers en utilisant la structure de tuple [ [id:value, ...], file ]

2. Manipuler les métadonnées

Maintenant que nous avons chargé nos métadonnées, faisons quelque chose avec !

Nous allons utiliser un outil appelé langid pour identifier la langue contenue dans le fichier d'enregistrement de chaque créature. L'outil est pré-entraîné sur un ensemble de langues, et étant donné un extrait de texte, il produira une prédiction de langue et un score de probabilité associé, tous deux vers stdout.

2.1. Importer le processus et examiner le code

Nous vous fournissons un module de processus pré-écrit appelé IDENTIFY_LANGUAGE qui encapsule l'outil langid, vous devez donc simplement ajouter une instruction include avant le bloc workflow.

Effectuez la modification suivante au workflow :

main.nf
1
2
3
4
5
#!/usr/bin/env nextflow

include { IDENTIFY_LANGUAGE } from './modules/langid.nf'

workflow {
main.nf
1
2
3
#!/usr/bin/env nextflow

workflow {

Vous pouvez ouvrir le fichier de module pour examiner son code :

modules/langid.nf
#!/usr/bin/env nextflow

// Utiliser langid pour prédire la langue de chaque fichier d'entrée
process IDENTIFY_LANGUAGE {

    container 'community.wave.seqera.io/library/pip_langid:b2269f456a5629ff'

    input:
    tuple val(meta), path(file)

    output:
    tuple val(meta), path(file), stdout

    script:
    """
    langid < ${file} -l en,de,fr,es,it | sed -E "s/.*\\('([a-z]+)'.*/\\1/" | tr -d '\\n'
    """
}

Comme vous pouvez le voir, la définition d'entrée utilise la même structure tuple val(meta), path(file) que nous venons d'appliquer à notre canal d'entrée.

La définition de sortie est structurée comme un tuple avec une structure similaire à celle de l'entrée, sauf qu'elle contient également stdout comme troisième élément. Ce modèle tuple val(meta), path(file), <sortie> maintient les métadonnées associées à la fois aux données d'entrée et aux sorties alors qu'elles circulent dans le pipeline.

Notez que nous utilisons le qualificateur de sortie stdout de Nextflow ici parce que l'outil affiche sa sortie directement sur la console plutôt que d'écrire un fichier ; et nous utilisons sed dans la ligne de commande pour supprimer le score de probabilité, nettoyer la chaîne en supprimant les caractères de nouvelle ligne, et ne retourner que la prédiction de langue.

2.2. Ajouter un appel à IDENTIFY_LANGUAGE

Maintenant que le processus est disponible pour le workflow, nous pouvons ajouter un appel au processus IDENTIFY_LANGUAGE pour l'exécuter sur le canal de données.

Effectuez les modifications suivantes au workflow :

main.nf
    ch_datasheet = channel.fromPath("./data/datasheet.csv")
        .splitCsv(header: true)
        .map { row ->
            [[id: row.id, character: row.character], row.recording]
        }

    // Exécuter langid pour identifier la langue de chaque salutation
    IDENTIFY_LANGUAGE(ch_datasheet)
    IDENTIFY_LANGUAGE.out.view()
main.nf
    ch_datasheet = channel.fromPath("./data/datasheet.csv")
        .splitCsv(header: true)
        .map { row ->
            [[id: row.id, character: row.character], row.recording]
        }
        .view()

Notez que nous avons supprimé l'opération .view() originale dans la construction du canal.

Nous pouvons maintenant exécuter le workflow :

nextflow run main.nf
Sortie de la commande
 N E X T F L O W   ~  version 25.10.2

Launching `main.nf` [voluminous_mcnulty] DSL2 - revision: f9bcfebabb

executor >  local (7)
[4e/f722fe] IDENTIFY_LANGUAGE (7) [100%] 7 of 7 ✔
[[id:sampleA, character:squirrel], /workspaces/training/side-quests/metadata/work/eb/f7148ebdd898fbe1136bec6a714acb/bonjour.txt, fr]
[[id:sampleB, character:tux], /workspaces/training/side-quests/metadata/work/16/71d72410952c22cd0086d9bca03680/guten_tag.txt, de]
[[id:sampleD, character:turkey], /workspaces/training/side-quests/metadata/work/c4/b7562adddc1cc0b7d414ec45d436eb/hello.txt, en]
[[id:sampleC, character:sheep], /workspaces/training/side-quests/metadata/work/ea/04f5d979429e4455e14b9242fb3b45/hallo.txt, de]
[[id:sampleF, character:moose], /workspaces/training/side-quests/metadata/work/5a/6c2b84bf8fadb98e28e216426be079/salut.txt, fr]
[[id:sampleE, character:stegosaurus], /workspaces/training/side-quests/metadata/work/af/ee7c69bcab891c40d0529305f6b9e7/hola.txt, es]
[[id:sampleG, character:turtle], /workspaces/training/side-quests/metadata/work/4e/f722fe47271ba7ebcd69afa42964ca/ciao.txt, it]

Excellent ! Nous avons maintenant une prédiction de la langue parlée par chaque personnage.

Et comme noté précédemment, nous avons également inclus le fichier d'entrée et la meta map dans la sortie, ce qui signifie que les deux restent associés aux nouvelles informations que nous venons de produire. Cela s'avérera utile dans l'étape suivante.

Note

Plus généralement, ce modèle consistant à garder la meta map associée aux résultats facilite l'association de résultats liés qui partagent les mêmes identifiants.

Comme vous l'aurez déjà appris, vous ne pouvez pas compter sur l'ordre des éléments dans les canaux pour faire correspondre les résultats entre eux. Au lieu de cela, vous devez utiliser des clés pour associer correctement les données, et les meta maps fournissent une structure idéale à cette fin.

Nous explorons ce cas d'usage en détail dans la quête secondaire Splitting & Grouping.

2.3. Augmenter les métadonnées avec les sorties de processus

Étant donné que les résultats que nous venons de produire sont en eux-mêmes une forme de métadonnées sur le contenu des fichiers, il serait utile de les ajouter à notre meta map.

Cependant, nous ne voulons pas modifier la meta map existante sur place. D'un point de vue technique, il est possible de le faire, mais c'est risqué.

Donc à la place, nous allons créer une nouvelle meta map contenant le contenu de la meta map existante plus une nouvelle paire clé-valeur lang: lang_id contenant les nouvelles informations, en utilisant l'opérateur + (une fonctionnalité Groovy). Et nous combinerons cela avec une opération map pour remplacer l'ancienne map par la nouvelle.

Voici les modifications que vous devez apporter au workflow :

main.nf
    // Exécuter langid pour identifier la langue de chaque salutation
    IDENTIFY_LANGUAGE(ch_datasheet)
    IDENTIFY_LANGUAGE.out
        .map { meta, file, lang_id ->
            [meta + [lang: lang_id], file]
        }
        .view()
main.nf
    // Exécuter langid pour identifier la langue de chaque salutation
    IDENTIFY_LANGUAGE(ch_datasheet)
    IDENTIFY_LANGUAGE.out.view()

Si vous n'êtes pas encore familier avec l'opérateur +, ou si cela semble déroutant, prenez quelques minutes pour parcourir l'explication détaillée ci-dessous.

Création de la nouvelle meta map en utilisant l'opérateur +

D'abord, vous devez savoir que nous pouvons fusionner le contenu de deux maps en utilisant l'opérateur Groovy +.

Disons que nous avons les maps suivantes :

map1 = [id: 'sampleA', character: 'squirrel']
map2 = [lang: 'fr']

Nous pouvons les fusionner ainsi :

new_map = map1 + map2

Le contenu de new_map sera :

[id: 'sampleA', character: 'squirrel', lang: 'fr']

Parfait !

Mais que faire si vous devez ajouter un champ qui ne fait pas déjà partie d'une map ?

Disons que vous repartez de map1, mais la prédiction de langue n'est pas dans sa propre map (il n'y a pas de map2). Au lieu de cela, elle est contenue dans une variable appelée lang_id, et vous savez que vous voulez stocker sa valeur ('fr') avec la clé lang.

Vous pouvez en fait faire ce qui suit :

new_map = [map1 + [lang: lang_id]]

Ici, [lang: new_info] crée une nouvelle map sans nom à la volée, et map1 + fusionne map1 avec la nouvelle map sans nom, produisant le même contenu new_map qu'auparavant.

Élégant, n'est-ce pas ?

Maintenant transposons cela dans le contexte d'une opération Nextflow channel.map().

Le code devient :

.map { map1, lang_id ->
    [map1 + [lang: lang_id]]
}

Cela fait ce qui suit :

  • map1, lang_id -> prend les deux éléments du tuple
  • [map1 + [lang: lang_id]] crée la nouvelle map comme détaillé ci-dessus

La sortie est une seule map sans nom avec le même contenu que new_map dans notre exemple ci-dessus. Donc nous avons effectivement transformé :

[id: 'sampleA', character: 'squirrel'], 'fr'

en :

[id: 'sampleA', character: 'squirrel', lang: 'fr']

Espérons que vous pouvez voir que si nous changeons map1 en meta, c'est essentiellement tout ce dont nous avons besoin pour ajouter la prédiction de langue à notre meta map dans notre workflow.

Sauf pour une chose !

Dans le cas de notre workflow, nous devons également tenir compte de la présence de l'objet file dans le tuple, qui est composé de meta, file, lang_id.

Donc le code ici deviendrait :

.map { meta, file, lang_id ->
    [meta + [lang: lang_id], file]
}

Si vous avez du mal à comprendre pourquoi le file semble se déplacer dans l'opération map, imaginez qu'au lieu de [meta + [lang: lang_id], file], cette ligne lise [new_map, file]. Cela devrait rendre plus clair que nous laissons simplement le file à sa place d'origine en deuxième position dans le tuple. Nous avons juste pris la valeur new_info et l'avons intégrée dans la map qui est en première position.

Et cela nous ramène à la structure de canal tuple val(meta), path(file) !

Une fois que vous êtes sûr de comprendre ce que fait ce code, exécutez le workflow pour voir si cela a fonctionné :

nextflow run main.nf -resume
Sortie de la commande
 N E X T F L O W   ~  version 25.10.2

Launching `main.nf` [cheeky_fermat] DSL2 - revision: d096281ee4

[4e/f722fe] IDENTIFY_LANGUAGE (7) [100%] 7 of 7, cached: 7 ✔
[[id:sampleA, character:squirrel, lang:fr], /workspaces/training/side-quests/metadata/work/eb/f7148ebdd898fbe1136bec6a714acb/bonjour.txt]
[[id:sampleB, character:tux, lang:de], /workspaces/training/side-quests/metadata/work/16/71d72410952c22cd0086d9bca03680/guten_tag.txt]
[[id:sampleC, character:sheep, lang:de], /workspaces/training/side-quests/metadata/work/ea/04f5d979429e4455e14b9242fb3b45/hallo.txt]
[[id:sampleD, character:turkey, lang:en], /workspaces/training/side-quests/metadata/work/c4/b7562adddc1cc0b7d414ec45d436eb/hello.txt]
[[id:sampleF, character:moose, lang:fr], /workspaces/training/side-quests/metadata/work/5a/6c2b84bf8fadb98e28e216426be079/salut.txt]
[[id:sampleE, character:stegosaurus, lang:es], /workspaces/training/side-quests/metadata/work/af/ee7c69bcab891c40d0529305f6b9e7/hola.txt]
[[id:sampleG, character:turtle, lang:it], /workspaces/training/side-quests/metadata/work/4e/f722fe47271ba7ebcd69afa42964ca/ciao.txt]

Oui, ça fonctionne ! Nous avons soigneusement réorganisé la sortie du processus de meta, file, lang_id pour que lang_id soit maintenant l'une des clés dans la meta map, et les tuples du canal correspondent à nouveau au modèle meta, file.

2.4. Attribuer un groupe de langue en utilisant des conditions

Maintenant que nous avons nos prédictions de langue, utilisons les informations pour attribuer de nouveaux regroupements.

Dans nos données d'exemple, les langues utilisées par nos personnages peuvent être regroupées en langues germaniques (anglais, allemand) et langues romanes (français, espagnol, italien). Il pourrait être utile d'avoir cette classification facilement disponible quelque part plus tard dans le pipeline, alors ajoutons cette information dans la meta map.

Et, bonne nouvelle, c'est encore un autre cas qui se prête parfaitement à l'utilisation de l'opérateur map !

Spécifiquement, nous allons définir une variable appelée lang_group, utiliser une logique conditionnelle simple pour déterminer quelle valeur attribuer au lang_group pour chaque donnée.

La syntaxe générale va ressembler à ceci :

.map { meta, file ->

    // la logique conditionnelle définissant lang_group va ici

    [meta + [lang_group: lang_group], file]
}

Vous pouvez voir que c'est très similaire à l'opération de fusion de map à la volée que nous avons utilisée à l'étape précédente. Nous devons juste écrire les instructions conditionnelles.

Voici la logique conditionnelle que nous voulons appliquer :

  • Définir une variable appelée lang_group avec la valeur par défaut 'unknown'.
  • Si lang est soit allemand ('de') soit anglais ('en'), changer lang_group en germanic.
  • Sinon si lang est inclus dans une liste contenant français ('fr'), espagnol ('es') et italien ('it'), changer lang_group en romance.

Essayez de l'écrire vous-même si vous savez déjà comment écrire des instructions conditionnelles dans Nextflow.

Astuce

Vous pouvez accéder à la valeur de lang dans l'opération map avec meta.lang.

Vous devriez finir par apporter les modifications suivantes au workflow :

main.nf
    // Exécuter langid pour identifier la langue de chaque salutation
    IDENTIFY_LANGUAGE(ch_datasheet)
    IDENTIFY_LANGUAGE.out
        .map { meta, file, lang_id ->
            [meta + [lang: lang_id], file]
        }
        .map { meta, file ->

            def lang_group = "unknown"
            if (meta.lang.equals("de") || meta.lang.equals('en')) {
                lang_group = "germanic"
            }
            else if (meta.lang in ["fr", "es", "it"]) {
                lang_group = "romance"
            }

            [meta + [lang_group: lang_group], file]
        }
        .view()
main.nf
    // Exécuter langid pour identifier la langue de chaque salutation
    IDENTIFY_LANGUAGE(ch_datasheet)
    IDENTIFY_LANGUAGE.out
        .map { meta, file, lang_id ->
            [meta + [lang: lang_id], file]
        }
        .view()

Voici les points clés :

  • Nous utilisons def lang_group = "unknown" pour créer la variable lang_group avec une valeur par défaut définie sur unknown.
  • Nous utilisons une structure if {} else if {} pour la logique conditionnelle, avec des tests .equals() alternatifs pour les deux langues germaniques, et un test d'existence dans une liste pour les trois langues romanes.
  • Nous utilisons l'opération de fusion meta + [lang_group:lang_group] comme précédemment pour générer la meta map mise à jour.

Une fois que tout cela a du sens, exécutez à nouveau le workflow pour voir le résultat :

nextflow run main.nf -resume
Sortie de la commande
 N E X T F L O W   ~  version 25.10.2

Launching `main.nf` [wise_almeida] DSL2 - revision: 46778c3cd0

[da/652cc6] IDENTIFY_LANGUAGE (7) [100%] 7 of 7, cached: 7 ✔
[[id:sampleA, character:squirrel, lang:fr, lang_group:romance], /workspaces/training/side-quests/metadata/data/bonjour.txt]
[[id:sampleB, character:tux, lang:de, lang_group:germanic], /workspaces/training/side-quests/metadata/data/guten_tag.txt]
[[id:sampleC, character:sheep, lang:de, lang_group:germanic], /workspaces/training/side-quests/metadata/data/hallo.txt]
[[id:sampleD, character:turkey, lang:en, lang_group:germanic], /workspaces/training/side-quests/metadata/data/hello.txt]
[[id:sampleE, character:stegosaurus, lang:es, lang_group:romance], /workspaces/training/side-quests/metadata/data/hola.txt]
[[id:sampleF, character:moose, lang:fr, lang_group:romance], /workspaces/training/side-quests/metadata/data/salut.txt]
[[id:sampleG, character:turtle, lang:it, lang_group:romance], /workspaces/training/side-quests/metadata/data/ciao.txt]

Comme vous pouvez le voir, les éléments du canal maintiennent leur structure [meta, file], mais la meta map inclut maintenant cette nouvelle classification.

À retenir

Dans cette section, vous avez appris comment :

  • Appliquer les métadonnées d'entrée aux canaux de sortie : Copier les métadonnées de cette manière nous permet d'associer les résultats ultérieurement en fonction du contenu des métadonnées.
  • Créer des clés personnalisées : Vous avez créé deux nouvelles clés dans votre meta map, les fusionnant avec meta + [new_key:value] dans la meta map existante. L'une basée sur une valeur calculée à partir d'un processus, et une basée sur une condition que vous avez définie dans l'opérateur map.

Celles-ci vous permettent d'associer des métadonnées nouvelles et existantes avec des fichiers au fur et à mesure que vous progressez dans votre pipeline. Même si vous n'utilisez pas les métadonnées dans le cadre d'un processus, garder la meta map associée aux données comme ceci facilite le maintien de toutes les informations pertinentes ensemble.


3. Utiliser les informations de la meta map dans un processus

Maintenant que vous savez comment créer et mettre à jour la meta map, nous pouvons passer à la partie vraiment amusante : utiliser réellement les métadonnées dans un processus.

Plus spécifiquement, nous allons ajouter une deuxième étape à notre workflow pour dessiner chaque animal en art ASCII et le faire dire le texte enregistré dans une bulle de dialogue. Nous allons faire cela en utilisant un outil appelé cowpy.

Que fait cowpy ?

cowpy est un outil en ligne de commande qui génère de l'art ASCII pour afficher des entrées de texte arbitraires de manière amusante. C'est une implémentation python du classique outil cowsay par Tony Monroe.

cowpy "Hello Nextflow"
______________________________________________________
< Hello Nextflow >
------------------------------------------------------
    \   ^__^
      \  (oo)\_______
        (__)\       )\/\
          ||----w |
          ||     ||

Optionnellement, vous pouvez sélectionner un personnage (ou 'cowacter') à utiliser au lieu de la vache par défaut.

cowpy "Hello Nextflow" -c tux
__________________
< Hello Nextflow >
------------------
  \
    \
        .--.
      |o_o |
      |:_/ |
      //   \ \
    (|     | )
    /'\_   _/`\
    \___)=(___/

Si vous avez suivi le cours Hello Nextflow, vous avez déjà vu cet outil en action. Sinon, ne vous inquiétez pas ; nous couvrirons tout ce que vous devez savoir au fur et à mesure.

3.1. Importer le processus et examiner le code

Nous vous fournissons un module de processus pré-écrit appelé COWPY qui encapsule l'outil cowpy, vous devez donc simplement ajouter une instruction include avant le bloc workflow.

Effectuez la modification suivante au workflow :

main.nf
1
2
3
4
5
6
#!/usr/bin/env nextflow

include { IDENTIFY_LANGUAGE } from './modules/langid.nf'
include { COWPY } from './modules/cowpy.nf'

workflow {
main.nf
1
2
3
4
5
#!/usr/bin/env nextflow

include { IDENTIFY_LANGUAGE } from './modules/langid.nf'

workflow {

Vous pouvez ouvrir le fichier de module pour examiner son code :

modules/cowpy.nf
#!/usr/bin/env nextflow

// Générer de l'art ASCII avec cowpy
process COWPY {

    publishDir "results/", mode: 'copy'

    container 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273'

    input:
    path input_file
    val character

    output:
    path "cowpy-${input_file}"

    script:
    """
    cat ${input_file} | cowpy -c ${character} > cowpy-${input_file}
    """
}

Comme vous pouvez le voir, ce processus est actuellement conçu pour prendre un fichier d'entrée (contenant le texte à afficher) et une valeur spécifiant le personnage qui doit être dessiné en art ASCII, généralement fournie au niveau du workflow par un paramètre de ligne de commande.

3.2. Passer un champ de meta map comme entrée

Lorsque nous avons utilisé l'outil cowpy dans le cours Hello Nextflow, nous avons utilisé un paramètre de ligne de commande pour déterminer quel personnage utiliser pour dessiner l'image finale. Cela avait du sens, car nous ne générions qu'une seule image par exécution du pipeline.

Cependant, dans ce tutoriel, nous voulons générer une image appropriée pour chaque sujet que nous traitons, donc utiliser un paramètre de ligne de commande serait trop limitant.

Bonne nouvelle : nous avons une colonne character dans notre feuille de données et donc, dans notre meta map. Utilisons cela pour définir le personnage que le processus doit utiliser pour chaque entrée.

À cette fin, nous devrons faire trois choses :

  1. Donner un nom au canal de sortie provenant du processus précédent afin de pouvoir l'utiliser plus commodément.
  2. Déterminer comment accéder aux informations qui nous intéressent
  3. Ajouter un appel au deuxième processus et fournir les informations de manière appropriée.

Commençons.

3.2.1. Nommer le canal de sortie précédent

Nous avons appliqué les manipulations précédentes directement sur le canal de sortie du premier processus, IDENTIFY_LANGUAGE.out. Afin de fournir le contenu de ce canal au processus suivant (et de le faire d'une manière claire et facile à lire) nous voulons lui donner son propre nom, ch_languages.

Nous pouvons le faire en utilisant l'opérateur set.

Dans le workflow principal, remplacez l'opérateur .view() par .set { ch_languages }, et ajoutez une ligne testant que nous pouvons référer au canal par nom.

main.nf
    // Exécuter langid pour identifier la langue de chaque salutation
    IDENTIFY_LANGUAGE(ch_datasheet)
    IDENTIFY_LANGUAGE.out
        .map { meta, file, lang_id ->
            [meta + [lang: lang_id], file]
        }
        .map { meta, file ->

            def lang_group = "unknown"
            if (meta.lang.equals("de") || meta.lang.equals('en')) {
                lang_group = "germanic"
            }
            else if (meta.lang in ["fr", "es", "it"]) {
                lang_group = "romance"
            }

            [meta + [lang_group: lang_group], file]
        }
        .set { ch_languages }

    // Temporaire : jeter un œil dans ch_languages
    ch_languages.view()
main.nf
    // Exécuter langid pour identifier la langue de chaque salutation
    IDENTIFY_LANGUAGE(ch_datasheet)
    IDENTIFY_LANGUAGE.out
        .map { meta, file, lang_id ->
            [meta + [lang: lang_id], file]
        }
        .map { meta, file ->

            def lang_group = "unknown"
            if (meta.lang.equals("de") || meta.lang.equals('en')) {
                lang_group = "germanic"
            }
            else if (meta.lang in ["fr", "es", "it"]) {
                lang_group = "romance"
            }

            [meta + [lang_group: lang_group], file]
        }
        .view()

Exécutons ceci :

nextflow run main.nf
Sortie de la commande
 N E X T F L O W   ~  version 25.10.2

Launching `./main.nf` [friendly_austin] DSL2 - revision: 3dbe460fd6

[36/cca6a7] IDENTIFY_LANGUAGE (7) | 7 of 7 ✔
[[id:sampleB, character:tux, lang:de, lang_group:germanic], /workspaces/training/side-quests/metadata/work/e2/6db2402d83cf72081bcd2d11784714/guten_tag.txt]
[[id:sampleA, character:squirrel, lang:fr, lang_group:romance], /workspaces/training/side-quests/metadata/work/6c/114c818317d169457d6e7336d5d55b/bonjour.txt]
[[id:sampleC, character:sheep, lang:de, lang_group:germanic], /workspaces/training/side-quests/metadata/work/55/68c69c5efb527f3604ddb3daab8057/hallo.txt]
[[id:sampleD, character:turkey, lang:en, lang_group:germanic], /workspaces/training/side-quests/metadata/work/2a/4752055ccb5d1370b0ef9da41d3993/hello.txt]
[[id:sampleE, character:stegosaurus, lang:es, lang_group:romance], /workspaces/training/side-quests/metadata/work/f4/fcd3186dc666d5d239ffa6c37d125d/hola.txt]
[[id:sampleF, character:moose, lang:fr, lang_group:romance], /workspaces/training/side-quests/metadata/work/c3/3b2627f733f278a7088332a5806108/salut.txt]
[[id:sampleG, character:turtle, lang:it, lang_group:romance], /workspaces/training/side-quests/metadata/work/36/cca6a7dbfa26ac24f9329787a32e9d/ciao.txt]

Cela confirme que nous pouvons maintenant référer au canal par nom.

3.2.2. Accéder au fichier et aux métadonnées de personnage

Nous savons en regardant le code du module que le processus COWPY s'attend à recevoir un fichier texte et une valeur character. Pour écrire l'appel au processus COWPY, nous devons simplement savoir comment extraire l'objet fichier correspondant et les métadonnées de chaque élément du canal.

Comme c'est souvent le cas, la façon la plus simple de le faire est d'utiliser une opération map.

Notre canal contient des tuples structurés comme [meta, file], donc nous pouvons accéder à l'objet file directement, et nous pouvons accéder à la valeur character stockée à l'intérieur de la meta map en y faisant référence comme meta.character.

Dans le workflow principal, effectuez les modifications de code suivantes :

main.nf
    // Temporaire : accéder au fichier et au personnage
    ch_languages.map { meta, file -> file }.view { file -> "Fichier : " + file }
    ch_languages.map { meta, file -> meta.character }.view { character -> "Personnage : " + character }
main.nf
    // Temporaire : jeter un œil dans ch_languages
    ch_languages.view()

Notez que nous utilisons des closures (telles que { file -> "Fichier : " + file }) pour rendre la sortie des opérations .view plus lisible.

Exécutons ceci :

nextflow run main.nf -resume
Sortie de la commande
 N E X T F L O W   ~  version 25.10.2

Launching `./main.nf` [cheesy_cantor] DSL2 - revision: 15af9c1ec7

[43/05df08] IDENTIFY_LANGUAGE (7) [100%] 7 of 7, cached: 7 ✔
Personnage : squirrel
Fichier : /workspaces/training/side-quests/metadata/work/8d/4b9498bbccb7a74f04e41877cdc3e5/bonjour.txt
Fichier : /workspaces/training/side-quests/metadata/work/d3/604274985406e40d79021dea658e60/guten_tag.txt
Personnage : tux
Personnage : turkey
Fichier : /workspaces/training/side-quests/metadata/work/d4/fafcc9415b61d2b0fea872e6a05e8a/hello.txt
Fichier : /workspaces/training/side-quests/metadata/work/02/468ac9efb27f636715e8144b37e9a7/hallo.txt
Personnage : sheep
Personnage : moose
Personnage : stegosaurus
Fichier : /workspaces/training/side-quests/metadata/work/d4/61a7e1188b4f2742bc72004e226eca/salut.txt
Fichier : /workspaces/training/side-quests/metadata/work/ae/68364be238c11149c588bf6fc858b1/hola.txt
Fichier : /workspaces/training/side-quests/metadata/work/43/05df081af5d879ab52e5828fa0357e/ciao.txt
Personnage : turtle

Les chemins de fichiers et les valeurs de personnages peuvent sortir dans un ordre différent dans votre sortie.

Cela confirme que nous sommes capables d'accéder au fichier et au personnage pour chaque élément du canal.

3.2.3. Appeler le processus COWPY

Maintenant, assemblons tout et appelons réellement le processus COWPY sur le canal ch_languages.

Dans le workflow principal, effectuez les modifications de code suivantes :

main.nf
    // Exécuter cowpy pour générer de l'art ASCII
    COWPY(
        ch_languages.map { meta, file -> file },
        ch_languages.map { meta, file -> meta.character }
    )
main.nf
    // Temporaire : accéder au fichier et au personnage
    ch_languages.map { meta, file -> [file, meta.character] }
        .view()

Vous voyez que nous copions simplement les deux opérations map (moins les instructions .view()) comme entrées de l'appel de processus. Assurez-vous simplement de ne pas oublier la virgule entre elles !

C'est un peu maladroit, mais nous verrons comment améliorer cela dans la section suivante.

Exécutons ceci :

nextflow run main.nf -resume
Sortie de la commande
 N E X T F L O W   ~  version 25.10.2

Launching `main.nf` [suspicious_crick] DSL2 - revision: 25541014c5

executor >  local (7)
[43/05df08] IDENTIFY_LANGUAGE (7) [100%] 7 of 7, cached: 7 ✔
[e7/317c18] COWPY (6)             [100%] 7 of 7 ✔

Si vous regardez dans le répertoire results, vous devriez voir les fichiers individuels contenant l'art ASCII de chaque salutation prononcée par le personnage correspondant.

Répertoire et exemple de contenu de fichier
results/
├── cowpy-bonjour.txt
├── cowpy-ciao.txt
├── cowpy-guten_tag.txt
├── cowpy-hallo.txt
├── cowpy-hello.txt
├── cowpy-hola.txt
└── cowpy-salut.txt
results/cowpy-bonjour.txt
 _________________
/ Bonjour         \
\ Salut, à demain /
-----------------
  \
    \
                  _ _
      | \__/|  .~    ~.
      /oo `./      .'
      {o__,   \    {
        / .  . )    \
        `-` '-' \    }
      .(   _(   )_.'
      '---.~_ _ _|

Cela montre que nous avons pu utiliser les informations dans la meta map pour paramétrer la commande dans la deuxième étape du pipeline.

Cependant, comme noté ci-dessus, une partie du code impliqué était un peu maladroite, puisque nous avons dû déballer les métadonnées tout en étant encore dans le contexte du corps du workflow. Cette approche fonctionne bien pour utiliser un petit nombre de champs de la meta map, mais évoluerait mal si nous voulions en utiliser beaucoup plus.

Il existe un autre opérateur appelé multiMap() qui nous permet de rationaliser cela un peu, mais même alors ce n'est pas idéal.

(Optionnel) Version alternative avec multiMap()

Au cas où vous vous poseriez la question, nous ne pouvions pas simplement écrire une seule opération map() qui sort à la fois le file et le character, parce que cela les retournerait comme un tuple. Nous avons dû écrire deux opérations map() séparées afin de fournir les éléments file et character au processus séparément.

Techniquement, il existe une autre façon de le faire à travers une seule opération de mapping, en utilisant l'opérateur multiMap(), qui est capable d'émettre plusieurs canaux. Par exemple, vous pourriez remplacer l'appel à COWPY ci-dessus par le code suivant :

main.nf
    // Exécuter cowpy pour générer de l'art ASCII
    COWPY(
        ch_languages.multiMap { meta, file ->
            file: file
            character: meta.character
        }
    )
main.nf
    // Exécuter cowpy pour générer de l'art ASCII
    COWPY(
        ch_languages.map { meta, file -> file },
        ch_languages.map { meta, file -> meta.character }
    )

Cela produit exactement le même résultat.

Dans les deux cas, il est gênant de devoir faire un peu de déballage au niveau du workflow.

Il serait préférable de pouvoir fournir la meta map entière au processus et choisir ce dont nous avons besoin une fois là-bas.

3.3. Passer et utiliser la meta map entière

Le but de la meta map est après tout de passer toutes les métadonnées ensemble comme un ensemble. La seule raison pour laquelle nous ne pouvions pas le faire ci-dessus est que le processus n'est pas configuré pour accepter une meta map. Mais puisque nous contrôlons le code du processus, nous pouvons changer cela.

Modifions le processus COWPY pour accepter la structure de tuple [meta, file] que nous avons utilisée dans le premier processus afin de pouvoir rationaliser le workflow.

À cette fin, nous devrons faire trois choses :

  1. Modifier les définitions d'entrée du module de processus COWPY
  2. Mettre à jour la commande du processus pour utiliser la meta map
  3. Mettre à jour l'appel de processus dans le corps du workflow

Prêt ? Allons-y !

3.3.1. Modifier l'entrée du module COWPY

Effectuez les modifications suivantes au fichier de module cowpy.nf :

cowpy.nf
input:
tuple val(meta), path(input_file)
cowpy.nf
input:
path(input_file)
val character

Cela nous permet d'utiliser la structure de tuple [meta, file] que nous avons couverte plus tôt dans le tutoriel.

Notez que nous n'avons pas mis à jour la définition de sortie du processus pour produire la meta map, afin de garder le tutoriel concis, mais n'hésitez pas à le faire vous-même comme exercice en suivant le modèle du processus IDENTIFY_LANGUAGE.

3.3.2. Mettre à jour la commande pour utiliser le champ de la meta map

La meta map entière est maintenant disponible à l'intérieur du processus, nous pouvons donc nous référer aux informations qu'elle contient directement depuis le bloc de commande.

Effectuez les modifications suivantes au fichier de module cowpy.nf :

cowpy.nf
script:
"""
cat ${input_file} | cowpy -c ${meta.character} > cowpy-${input_file}
"""
cowpy.nf
script:
"""
cat ${input_file} | cowpy -c ${character} > cowpy-${input_file}
"""

Nous avons remplacé la référence à la valeur character précédemment passée comme entrée autonome par la valeur contenue dans la meta map, à laquelle nous nous référons en utilisant meta.character.

Maintenant, mettons à jour l'appel de processus en conséquence.

3.3.3. Mettre à jour l'appel de processus et l'exécuter

Le processus s'attend maintenant à ce que son entrée utilise la structure de tuple [meta, file], qui est ce que le processus précédent produit, nous pouvons donc simplement passer le canal ch_languages entier au processus COWPY.

Effectuez les modifications suivantes au workflow principal :

main.nf
// Exécuter cowpy pour générer de l'art ASCII
COWPY(ch_languages)
main.nf
// Exécuter cowpy pour générer de l'art ASCII
COWPY(
    ch_languages.map { meta, file -> file },
    ch_languages.map { meta, file -> meta.character }
)

Cela simplifie considérablement l'appel !

Supprimons les résultats de l'exécution précédente et exécutons :

rm -r results
nextflow run main.nf
Sortie de la commande
 N E X T F L O W   ~  version 25.10.2

Launching `main.nf` [wise_sammet] DSL2 - revision: 99797b1e92

executor >  local (14)
[5d/dffd4e] process > IDENTIFY_LANGUAGE (7) [100%] 7 of 7 ✔
[25/9243df] process > COWPY (7)             [100%] 7 of 7 ✔

Si vous regardez dans le répertoire results, vous devriez voir les mêmes sorties qu'auparavant, c'est-à-dire des fichiers individuels contenant l'art ASCII de chaque salutation prononcée par le personnage correspondant.

Contenu du répertoire
./results/
├── cowpy-bonjour.txt
├── cowpy-ciao.txt
├── cowpy-guten_tag.txt
├── cowpy-hallo.txt
├── cowpy-hello.txt
├── cowpy-hola.txt
└── cowpy-salut.txt

Cela produit donc les mêmes résultats qu'avant avec un code plus simple.

Bien sûr, cela suppose que vous êtes en mesure de modifier le code du processus. Dans certains cas, vous devrez peut-être vous fier à des processus existants que vous n'êtes pas libre de modifier, ce qui limite vos options. La bonne nouvelle, si vous prévoyez d'utiliser des modules du projet nf-core, est que les modules nf-core sont tous configurés pour utiliser la structure de tuple [meta, file] comme standard.

3.4. Dépannage des entrées requises manquantes

La valeur character est requise pour que le processus COWPY s'exécute avec succès. Si nous ne définissons pas de valeur par défaut dans un fichier de configuration, nous DEVONS fournir une valeur dans la feuille de données.

Que se passe-t-il si nous ne le faisons pas ? Cela dépend de ce que contient la feuille de données d'entrée et de la version du workflow que nous exécutons.

3.4.1. La colonne character existe mais est vide

Disons que nous supprimons la valeur character pour l'un des enregistrements de notre feuille de données pour simuler une erreur de collecte de données :

datasheet.csv
1
2
3
4
5
6
7
8
id,character,recording
sampleA,,/workspaces/training/side-quests/metadata/data/bonjour.txt
sampleB,tux,/workspaces/training/side-quests/metadata/data/guten_tag.txt
sampleC,sheep,/workspaces/training/side-quests/metadata/data/hallo.txt
sampleD,turkey,/workspaces/training/side-quests/metadata/data/hello.txt
sampleE,stegosaurus,/workspaces/training/side-quests/metadata/data/hola.txt
sampleF,moose,/workspaces/training/side-quests/metadata/data/salut.txt
sampleG,turtle,/workspaces/training/side-quests/metadata/data/ciao.txt

Pour l'une ou l'autre des versions du workflow que nous avons utilisées ci-dessus, la clé character sera créée pour tous les enregistrements lorsque la feuille de données est lue, mais pour sampleA la valeur sera une chaîne vide.

Cela provoquera une erreur.

Sortie de la commande
 N E X T F L O W   ~  version 25.10.2

Launching `main.nf` [marvelous_hirsch] DSL2 - revision: 0dfeee3cc1

executor >  local (9)
[c1/c5dd4f] process > IDENTIFY_LANGUAGE (7) [ 85%] 6 of 7
[d3/b7c415] process > COWPY (2)             [  0%] 0 of 6
ERROR ~ Error executing process > 'COWPY (1)'

Caused by:
  Process `COWPY (1)` terminated with an error exit status (2)


Command executed:

  cat bonjour.txt | cowpy -c  > cowpy-bonjour.txt

Command exit status:
  2

Command output:
  (empty)

Command error:
  usage: cowpy [-h] [-l] [-L] [-t] [-u] [-e EYES] [-c COWACTER] [-E] [-r] [-x]
              [-C]
              [msg ...]
  cowpy: error: argument -c/--cowacter: expected one argument

Work dir:
  /workspaces/training/side-quests/metadata/work/ca/9d49796612a54dec5ed466063c809b

Container:
  community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273

Tip: you can try to figure out what's wrong by changing to the process work dir and showing the script file named `.command.sh`

-- Check '.nextflow.log' file for details

Lorsque Nextflow exécute la ligne de commande cowpy pour cet échantillon, ${meta.character} est rempli avec une chaîne vide dans la ligne de commande cowpy, donc l'outil cowpy génère une erreur disant qu'aucune valeur n'a été fournie pour l'argument -c.

3.4.2. La colonne character n'existe pas dans la feuille de données

Disons maintenant que nous supprimons la colonne character entièrement de notre feuille de données :

datasheet.csv
1
2
3
4
5
6
7
8
id,recording
sampleA,/workspaces/training/side-quests/metadata/data/bonjour.txt
sampleB,/workspaces/training/side-quests/metadata/data/guten_tag.txt
sampleC,/workspaces/training/side-quests/metadata/data/hallo.txt
sampleD,/workspaces/training/side-quests/metadata/data/hello.txt
sampleE,/workspaces/training/side-quests/metadata/data/hola.txt
sampleF,/workspaces/training/side-quests/metadata/data/salut.txt
sampleG,/workspaces/training/side-quests/metadata/data/ciao.txt

Dans ce cas, la clé character ne sera pas du tout créée lors de la lecture de la feuille de données.

3.4.2.1. Valeur accédée au niveau du workflow

Si nous utilisons la version du code que nous avons écrite dans la section 3.2, Nextflow tentera d'accéder à la clé character dans la meta map AVANT d'appeler le processus COWPY.

Il ne trouvera aucun élément correspondant à l'instruction, donc il n'exécutera pas du tout COWPY.

Sortie de la commande
 N E X T F L O W   ~  version 25.10.2

Launching `main.nf` [desperate_montalcini] DSL2 - revision: 0dfeee3cc1

executor >  local (7)
[1a/df2544] process > IDENTIFY_LANGUAGE (7) [100%] 7 of 7 ✔
[-        ] process > COWPY                 -

En ce qui concerne Nextflow, ce workflow s'est exécuté avec succès ! Cependant, aucune des sorties souhaitées ne sera produite.

3.4.2.2. Valeur accédée au niveau du processus

Si nous utilisons la version de la section 3.3, Nextflow passera la meta map entière au processus COWPY et tentera d'exécuter la commande.

Cela provoquera une erreur, mais une erreur différente par rapport au premier cas.

Sortie de la commande
 N E X T F L O W   ~  version 25.10.2

Launching `main.nf` [jovial_bohr] DSL2 - revision: eaaf375827

executor >  local (9)
[0d/ada9db] process > IDENTIFY_LANGUAGE (5) [ 85%] 6 of 7
[06/28065f] process > COWPY (2)             [  0%] 0 of 6
ERROR ~ Error executing process > 'COWPY (2)'

Caused by:
  Process `COWPY (2)` terminated with an error exit status (1)


Command executed:

  cat guten_tag.txt | cowpy -c null > cowpy-guten_tag.txt

Command exit status:
  1

Command output:
  (empty)

Command error:
  Traceback (most recent call last):
    File "/opt/conda/bin/cowpy", line 10, in <module>
      sys.exit(main())
              ~~~~^^
    File "/opt/conda/lib/python3.13/site-packages/cowpy/cow.py", line 1215, in main
      print(cow(eyes=args.eyes,
            ~~~^^^^^^^^^^^^^^^^
            tongue=args.tongue,
            ^^^^^^^^^^^^^^^^^^^
            thoughts=args.thoughts
            ^^^^^^^^^^^^^^^^^^^^^^
                ).milk(msg)
                ^
  TypeError: 'str' object is not callable

Work dir:
  /workspaces/training/side-quests/metadata/work/06/28065f7d9fd7d22bba084aa941b6d6

Container:
  community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273

Tip: you can replicate the issue by changing to the process work dir and entering the command `bash .command.run`

-- Check '.nextflow.log' file for details

Cela se produit parce que meta.character n'existe pas, donc notre tentative d'y accéder retourne null. En conséquence, Nextflow insère littéralement null dans la ligne de commande, ce qui n'est bien sûr pas reconnu par l'outil cowpy.

3.4.3. Solutions

En plus de fournir une valeur par défaut dans la configuration du workflow, il y a deux choses que nous pouvons faire pour gérer cela de manière plus robuste :

  1. Implémenter la validation des entrées dans votre workflow pour s'assurer que la feuille de données contient toutes les informations requises. Vous pouvez trouver une introduction à la validation des entrées dans le cours de formation Hello nf-core.

  2. Si vous voulez vous assurer que quiconque utilise votre module de processus peut immédiatement identifier les entrées requises, vous pouvez également faire de la propriété de métadonnées requise une entrée explicite.

Voici un exemple de la façon dont cela fonctionnerait.

D'abord, au niveau du processus, mettez à jour la définition d'entrée comme suit :

cowpy.nf
    input:
    tuple val(meta), val(character), path(input_file)
cowpy.nf
    input:
    tuple val(meta), path(input_file)

Ensuite, au niveau du workflow, utilisez une opération de mapping pour extraire la propriété character des métadonnées et en faire un composant explicite du tuple d'entrée :

main.nf
    COWPY(ch_languages.map{meta, file -> [meta, meta.character, file]})
main.nf
    COWPY(ch_languages)

Cette approche a l'avantage de montrer explicitement que character est requis, et rend le processus plus facile à redéployer dans d'autres contextes.

Cela met en évidence un principe de conception important :

Utilisez la meta map pour les informations optionnelles et descriptives, mais extrayez les valeurs requises comme entrées explicites.

La meta map est excellente pour garder les structures de canaux propres et prévenir les structures de canaux arbitraires, mais pour les éléments obligatoires qui sont directement référencés dans un processus, les extraire comme entrées explicites crée un code plus robuste et maintenable.

À retenir

Dans cette section, vous avez appris comment utiliser les métadonnées pour personnaliser l'exécution d'un processus, en y accédant soit au niveau du workflow soit au niveau du processus.


Exercice supplémentaire

Si vous souhaitez vous exercer à utiliser les informations de la meta map depuis l'intérieur d'un processus, essayez d'utiliser d'autres informations de la meta map telles que lang et lang_group pour personnaliser la façon dont les sorties sont nommées et/ou organisées.

Par exemple, essayez de modifier le code pour produire ce résultat :

Contenu du répertoire de résultats
results/
├── germanic
│   ├── de-guten_tag.txt
│   ├── de-hallo.txt
│   └── en-hello.txt
└── romance
    ├── es-hola.txt
    ├── fr-bonjour.txt
    ├── fr-salut.txt
    └── it-ciao.txt

Résumé

Dans cette quête secondaire, vous avez exploré comment travailler efficacement avec les métadonnées dans les workflows Nextflow.

Ce modèle consistant à garder les métadonnées explicites et attachées aux données est une bonne pratique fondamentale dans Nextflow, offrant plusieurs avantages par rapport au codage en dur des informations de fichiers :

  • Les métadonnées de fichiers restent associées aux fichiers tout au long du workflow
  • Le comportement des processus peut être personnalisé par fichier
  • L'organisation des sorties peut refléter les métadonnées des fichiers
  • Les informations de fichiers peuvent être étendues pendant l'exécution du pipeline

Appliquer ce modèle dans votre propre travail vous permettra de construire des workflows de bioinformatique robustes et maintenables.

Modèles clés

  1. Lecture et structuration des métadonnées : Lire des fichiers CSV et créer des maps de métadonnées organisées qui restent associées à vos fichiers de données.

    channel.fromPath('datasheet.csv')
      .splitCsv(header: true)
      .map { row ->
          [ [id:row.id, character:row.character], row.recording ]
      }
    
  2. Extension des métadonnées pendant le workflow : Ajouter de nouvelles informations à vos métadonnées au fur et à mesure que votre pipeline progresse en ajoutant des sorties de processus et en dérivant des valeurs par logique conditionnelle.

    • Ajouter de nouvelles clés basées sur les sorties de processus
    .map { meta, file, lang ->
      [ meta + [lang:lang], file ]
    }
    
    • Ajouter de nouvelles clés en utilisant une clause conditionnelle
    .map{ meta, file ->
        if ( meta.lang.equals("de") || meta.lang.equals('en') ){
            lang_group = "germanic"
        } else if ( meta.lang in ["fr", "es", "it"] ) {
            lang_group = "romance"
        } else {
            lang_group = "unknown"
        }
    }
    
  3. Personnalisation du comportement du processus : Utiliser les métadonnées à l'intérieur du processus.

    cat $input_file | cowpy -c ${meta.character} > cowpy-${input_file}
    

Ressources supplémentaires


Quelle est la suite ?

Retournez au menu des Quêtes secondaires ou cliquez sur le bouton en bas à droite de la page pour passer au sujet suivant dans la liste.