Parte 3: Hello Workflow¶
Traduzione assistita da IA - scopri di più e suggerisci miglioramenti
La maggior parte dei workflow reali coinvolgono più di un passaggio. In questo modulo di formazione, imparerete come connettere i processi insieme in un workflow multi-step.
Questo vi insegnerà il modo Nextflow per ottenere quanto segue:
- Far fluire i dati da un processo al successivo
- Raccogliere gli output da più chiamate di processo in una singola chiamata di processo
- Passare più di un input a un processo
- Gestire output multipli provenienti da un processo
Per dimostrare, continueremo a costruire sull'esempio Hello World indipendente dal dominio delle Parti 1 e 2. Questa volta, apporteremo le seguenti modifiche al nostro workflow per riflettere meglio come le persone costruiscono workflow reali:
- Aggiungere un secondo passaggio che converte il saluto in maiuscolo.
- Aggiungere un terzo passaggio che raccoglie tutti i saluti trasformati e li scrive in un singolo file.
- Aggiungere un parametro per nominare il file di output finale e passarlo come input secondario al passaggio di raccolta.
- Far sì che il passaggio di raccolta riporti anche una semplice statistica su ciò che è stato elaborato.
Come iniziare da questa sezione
Questa sezione del corso presuppone che abbiate completato le Parti 1-2 del corso Hello Nextflow, ma se avete familiarità con i concetti base trattati in quelle sezioni, potete iniziare da qui senza fare nulla di speciale.
0. Riscaldamento: Eseguire hello-workflow.nf¶
Useremo lo script del workflow hello-workflow.nf come punto di partenza.
È equivalente allo script prodotto seguendo la Parte 2 di questo corso di formazione, tranne che abbiamo rimosso le istruzioni view() e cambiato la destinazione dell'output:
Solo per assicurarci che tutto funzioni, eseguite lo script una volta prima di apportare modifiche:
Output del comando
Come in precedenza, troverete i file di output nella posizione specificata nel blocco output.
Per questo capitolo, è sotto results/hello_workflow/.
Contenuti della directory
Se tutto ha funzionato, siete pronti a imparare come assemblare un workflow multi-step.
1. Aggiungere un secondo passaggio al workflow¶
Aggiungeremo un passaggio per convertire ogni saluto in maiuscolo.
A tal fine, dobbiamo fare tre cose:
- Definire il comando che useremo per fare la conversione in maiuscolo.
- Scrivere un nuovo processo che incapsula il comando di conversione in maiuscolo.
- Chiamare il nuovo processo nel blocco workflow e configurarlo per prendere l'output del processo
sayHello()come input.
1.1. Definire il comando di conversione in maiuscolo e testarlo nel terminale¶
Per effettuare la conversione dei saluti in maiuscolo, useremo un classico strumento UNIX chiamato tr per 'text replacement' (sostituzione di testo), con la seguente sintassi:
Questa è una sostituzione di testo molto semplice che non tiene conto delle lettere accentate, quindi per esempio 'Holà' diventerà 'HOLà', ma farà un lavoro abbastanza buono per dimostrare i concetti di Nextflow e questo è ciò che conta.
Per testarlo, possiamo eseguire il comando echo 'Hello World' e fare il pipe del suo output al comando tr:
L'output è un file di testo chiamato UPPER-output.txt che contiene la versione maiuscola della stringa Hello World.
Questo è fondamentalmente ciò che cercheremo di fare con il nostro workflow.
1.2. Scrivere il passaggio di conversione in maiuscolo come processo Nextflow¶
Possiamo modellare il nostro nuovo processo sul primo, dato che vogliamo usare tutti gli stessi componenti.
Aggiungete la seguente definizione di processo allo script del workflow, subito sotto il primo:
| hello-workflow.nf | |
|---|---|
In questo, componiamo il nome del secondo file di output basandoci sul nome del file di input, similmente a quello che abbiamo fatto originariamente per l'output del primo processo.
1.3. Aggiungere una chiamata al nuovo processo nel blocco workflow¶
Ora dobbiamo dire a Nextflow di chiamare effettivamente il processo che abbiamo appena definito.
Nel blocco workflow, effettuate la seguente modifica al codice:
| hello-workflow.nf | |
|---|---|
Questo non è ancora funzionale perché non abbiamo specificato cosa dovrebbe essere l'input al processo convertToUpper().
1.4. Passare l'output del primo processo al secondo processo¶
Ora dobbiamo far fluire l'output del processo sayHello() nel processo convertToUpper().
Convenientemente, Nextflow impacchetta automaticamente l'output di un processo in un channel chiamato <process>.out.
Quindi l'output del processo sayHello è un channel chiamato sayHello.out, che possiamo collegare direttamente alla chiamata di convertToUpper().
Nel blocco workflow, effettuate la seguente modifica al codice:
Per un caso semplice come questo (un output verso un input), questo è tutto ciò che dobbiamo fare per connettere due processi!
1.5. Configurare la pubblicazione dell'output del workflow¶
Infine, aggiorniamo gli output del workflow per pubblicare anche i risultati del secondo processo.
1.5.1. Aggiornare la sezione publish: del blocco workflow¶
Nel blocco workflow, effettui la seguente modifica al codice:
La logica è la stessa di prima.
1.5.2. Aggiornare il blocco output¶
Nel blocco output, effettuate la seguente modifica al codice:
Ancora una volta, la logica è la stessa di prima.
Questo vi mostra che potete controllare le impostazioni di output a un livello molto granulare, per ogni singolo output. Sentitevi liberi di provare a cambiare i percorsi o la modalità di pubblicazione per uno dei processi per vedere cosa succede.
Naturalmente, questo significa che stiamo ripetendo alcune informazioni qui, il che potrebbe diventare scomodo se volessimo aggiornare la posizione per tutti gli output nello stesso modo. Più avanti nel corso, imparerete come configurare queste impostazioni per output multipli in modo strutturato.
1.6. Eseguire il workflow con -resume¶
Testiamo questo usando il flag -resume, dato che abbiamo già eseguito con successo il primo passaggio del workflow.
Output del comando
Ora c'è una riga extra nell'output della console che corrisponde al nuovo processo che abbiamo appena aggiunto.
Troverete gli output nella directory results/hello_workflow come impostato nel blocco output.
Contenuti della directory
Comodo! Ma vale comunque la pena dare un'occhiata dentro la directory di lavoro di una delle chiamate al secondo processo.
Contenuti della directory
Nota che ci sono due file *-output: l'output del primo processo così come l'output del secondo.
L'output del primo processo è lì perché Nextflow lo ha staged lì per avere tutto il necessario per l'esecuzione nella stessa sottodirectory.
Tuttavia, è in realtà un link simbolico che punta al file originale nella sottodirectory della prima chiamata di processo. Per impostazione predefinita, quando si esegue su una singola macchina come stiamo facendo qui, Nextflow usa link simbolici piuttosto che copie per fare lo staging dei file di input e intermedi.
Ora, prima di procedere, pensate a come tutto ciò che abbiamo fatto è stato connettere l'output di sayHello all'input di convertToUpper e i due processi hanno potuto essere eseguiti in serie.
Nextflow ha fatto il lavoro duro di gestire i singoli file di input e output e passarli tra i due comandi per noi.
Questa è una delle ragioni per cui i channel di Nextflow sono così potenti: si occupano del lavoro noioso coinvolto nel connettere insieme i passaggi del workflow.
Takeaway¶
Sapete come concatenare processi insieme fornendo l'output di un passaggio come input al passaggio successivo.
Cosa c'è dopo?¶
Imparare come raccogliere gli output da chiamate di processo in batch e alimentarli in un singolo processo.
2. Aggiungere un terzo passaggio per raccogliere tutti i saluti¶
Quando usiamo un processo per applicare una trasformazione a ciascuno degli elementi in un channel, come stiamo facendo qui ai saluti multipli, a volte vogliamo raccogliere elementi dal channel di output di quel processo, e alimentarli in un altro processo che esegue qualche tipo di analisi o somma.
Per dimostrare, aggiungeremo un nuovo passaggio alla nostra pipeline che raccoglie tutti i saluti maiuscoli prodotti dal processo convertToUpper e li scrive in un singolo file.
Senza rovinare la sorpresa, questo coinvolgerà un operatore molto utile.
2.1. Definire il comando di raccolta e testarlo nel terminale¶
Il passaggio di raccolta che vogliamo aggiungere al nostro workflow userà il comando cat per concatenare più saluti maiuscoli in un singolo file.
Eseguiamo il comando da solo nel terminale per verificare che funzioni come previsto, proprio come abbiamo fatto in precedenza.
Eseguite il seguente nel vostro terminale:
echo 'Hello' | tr '[a-z]' '[A-Z]' > UPPER-Hello-output.txt
echo 'Bonjour' | tr '[a-z]' '[A-Z]' > UPPER-Bonjour-output.txt
echo 'Holà' | tr '[a-z]' '[A-Z]' > UPPER-Holà-output.txt
cat UPPER-Hello-output.txt UPPER-Bonjour-output.txt UPPER-Holà-output.txt > COLLECTED-output.txt
L'output è un file di testo chiamato COLLECTED-output.txt che contiene le versioni maiuscole dei saluti originali.
Questo è il risultato che vogliamo ottenere con il nostro workflow.
2.2. Creare un nuovo processo per fare il passaggio di raccolta¶
Creiamo un nuovo processo e chiamiamolo collectGreetings().
Possiamo iniziare a scriverlo basandoci su ciò che abbiamo visto prima.
2.2.1. Scrivere le parti 'ovvie' del processo¶
Aggiungete la seguente definizione di processo allo script del workflow:
| hello-workflow.nf | |
|---|---|
Questo è ciò che possiamo scrivere con fiducia basandoci su ciò che avete imparato finora. Ma questo non è funzionale! Omette la/le definizione/i di input e la prima metà del comando script perché dobbiamo capire come scriverlo.
2.2.2. Definire gli input di collectGreetings()¶
Dobbiamo raccogliere i saluti da tutte le chiamate al processo convertToUpper().
Cosa sappiamo di poter ottenere dal passaggio precedente nel workflow?
Il channel emesso da convertToUpper() conterrà i percorsi ai singoli file contenenti i saluti maiuscoli.
Questo equivale a uno slot di input; chiamiamolo input_files per semplicità.
Nel blocco process, effettuate la seguente modifica al codice:
Nota che usiamo il prefisso path anche se ci aspettiamo che questo contenga più file.
2.2.3. Comporre il comando di concatenazione¶
Qui le cose potrebbero diventare un po' complicate, perché dobbiamo essere in grado di gestire un numero arbitrario di file di input. Nello specifico, non possiamo scrivere il comando in anticipo, quindi dobbiamo dire a Nextflow come comporlo a runtime basandosi su quali input fluiscono nel processo.
In altre parole, se abbiamo un channel di input contenente l'elemento [file1.txt, file2.txt, file3.txt], abbiamo bisogno che Nextflow lo trasformi in cat file1.txt file2.txt file3.txt.
Fortunatamente, Nextflow è perfettamente in grado di farlo per noi se scriviamo semplicemente cat ${input_files} nel comando script.
Nel blocco process, effettuate la seguente modifica al codice:
In teoria questo dovrebbe gestire qualsiasi numero arbitrario di file di input.
Suggerimento
Alcuni strumenti da riga di comando richiedono di fornire un argomento (come -input) per ogni file di input.
In quel caso, dovremmo fare un po' di lavoro extra per comporre il comando.
Potete vedere un esempio di questo nel corso di formazione Nextflow for Genomics.
2.3. Aggiungere il passaggio di raccolta al workflow¶
Ora dovremmo solo aver bisogno di chiamare il processo di raccolta sull'output del passaggio di conversione in maiuscolo.
2.3.1. Connettere le chiamate dei processi¶
Nel blocco workflow, effettuate la seguente modifica al codice:
Questo connette l'output di convertToUpper() all'input di collectGreetings().
2.3.2. Eseguire il workflow con -resume¶
Proviamo.
Output del comando
Viene eseguito con successo, incluso il terzo passaggio.
Tuttavia, guardi il numero di chiamate per collectGreetings() nell'ultima riga.
Ce ne aspettavamo solo una, ma ce ne sono tre.
Ora dia un'occhiata ai contenuti del file di output finale.
Oh no. Il passaggio di raccolta è stato eseguito individualmente su ogni saluto, il che NON è quello che volevamo.
Dobbiamo fare qualcosa per dire esplicitamente a Nextflow che vogliamo che quel terzo passaggio venga eseguito su tutti gli elementi nel channel emesso da convertToUpper().
2.4. Usare un operatore per raccogliere i saluti in un singolo input¶
Sì, ancora una volta la risposta al nostro problema è un operatore.
Nello specifico, useremo l'operatore collect() dal nome appropriato.
2.4.1. Aggiungere l'operatore collect()¶
Questa volta apparirà un po' diverso perché non stiamo aggiungendo l'operatore nel contesto di una channel factory; lo stiamo aggiungendo a un channel di output.
Prendiamo il convertToUpper.out e aggiungiamo l'operatore collect(), che ci dà convertToUpper.out.collect().
Possiamo collegarlo direttamente alla chiamata del processo collectGreetings().
Nel blocco workflow, effettuate la seguente modifica al codice:
2.4.2. Aggiungere alcune istruzioni view()¶
Includiamo anche un paio di istruzioni view() per visualizzare gli stati prima e dopo dei contenuti del channel.
Le istruzioni view() possono andare dove volete; le abbiamo messe subito dopo la chiamata per leggibilità.
2.4.3. Eseguire di nuovo il workflow con -resume¶
Proviamo:
Output del comando
N E X T F L O W ~ version 25.10.2
Launching `hello-workflow.nf` [soggy_franklin] DSL2 - revision: bc8e1b2726
[d6/cdf466] sayHello (1) | 3 of 3, cached: 3 ✔
[99/79394f] convertToUpper (2) | 3 of 3, cached: 3 ✔
[1e/83586c] collectGreetings | 1 of 1 ✔
Before collect: /workspaces/training/hello-nextflow/work/b3/d52708edba8b864024589285cb3445/UPPER-Bonjour-output.txt
Before collect: /workspaces/training/hello-nextflow/work/99/79394f549e3040dfc2440f69ede1fc/UPPER-Hello-output.txt
Before collect: /workspaces/training/hello-nextflow/work/aa/56bfe7cf00239dc5badc1d04b60ac4/UPPER-Holà-output.txt
After collect: [/workspaces/training/hello-nextflow/work/b3/d52708edba8b864024589285cb3445/UPPER-Bonjour-output.txt, /workspaces/training/hello-nextflow/work/99/79394f549e3040dfc2440f69ede1fc/UPPER-Hello-output.txt, /workspaces/training/hello-nextflow/work/aa/56bfe7cf00239dc5badc1d04b60ac4/UPPER-Holà-output.txt]
Viene eseguito con successo, anche se l'output del log potrebbe apparire un po' più disordinato di questo (l'abbiamo ripulito per leggibilità).
Questa volta il terzo passaggio è stato chiamato solo una volta!
Guardando l'output delle istruzioni view(), vedrete il seguente:
- Tre istruzioni
Before collect:, una per ogni saluto: a quel punto i percorsi dei file sono elementi individuali nel channel. - Una singola istruzione
After collect:: i tre percorsi dei file sono ora impacchettati in un singolo elemento.
Date un'occhiata ai contenuti del file di output finale.
Questa volta abbiamo tutti e tre i saluti nel file di output finale. Successo!
Nota
Se eseguite questo più volte senza -resume, vedrete che l'ordine dei saluti cambia da un'esecuzione all'altra.
Questo vi mostra che l'ordine in cui gli elementi fluiscono attraverso le chiamate dei processi non è garantito essere consistente.
2.4.4. Rimuovere le istruzioni view() per leggibilità¶
Prima di passare alla prossima sezione, raccomandiamo di cancellare le istruzioni view() per evitare di ingombrare l'output della console.
Questa è fondamentalmente l'operazione inversa dal punto 2.4.2.
Takeaway¶
Sapete come raccogliere gli output da un batch di chiamate di processo e alimentarli in un passaggio di analisi o somma congiunta.
Cosa c'è dopo?¶
Imparare come passare più di un input a un processo.
3. Passare più di un input a un processo¶
Vogliamo essere in grado di nominare il file di output finale con qualcosa di specifico per poter elaborare batch successivi di saluti senza sovrascrivere i risultati finali.
A tal fine, apporteremo i seguenti perfezionamenti al workflow:
- Modificare il processo di raccolta per accettare un nome definito dall'utente per il file di output
- Aggiungere un parametro da riga di comando al workflow e passarlo al processo di raccolta
3.1. Modificare il processo di raccolta¶
Dovremo dichiarare l'input aggiuntivo e integrarlo nel nome del file di output.
3.1.1. Dichiarare l'input aggiuntivo¶
Buone notizie: possiamo dichiarare quante variabili di input vogliamo nella definizione del processo.
Chiamiamo questa batch_name.
Nel blocco process, effettuate la seguente modifica al codice:
Potete configurare i vostri processi per aspettarsi quanti input volete. Al momento, questi sono tutti configurati come input obbligatori; dovete fornire un valore affinché il workflow funzioni.
Imparerete come gestire input obbligatori vs. opzionali più avanti nel vostro percorso con Nextflow.
3.1.2. Usare la variabile batch_name nel nome del file di output¶
Possiamo inserire la variabile nel nome del file di output nello stesso modo in cui abbiamo composto nomi di file dinamici prima.
Nel blocco process, effettuate la seguente modifica al codice:
Questo configura il processo per usare il valore batch_name per generare un nome di file specifico per l'output finale del workflow.
3.2. Aggiungere un parametro da riga di comando batch¶
Ora abbiamo bisogno di un modo per fornire il valore per batch_name e alimentarlo nella chiamata del processo.
3.2.1. Usare params per configurare il parametro¶
Sa già come usare il sistema params per dichiarare parametri CLI.
Usiamolo per dichiarare un parametro batch (con un valore predefinito perché siamo pigri).
Nella sezione dei parametri della pipeline, effettuate le seguenti modifiche al codice:
Proprio come abbiamo dimostrato per --input, potete sovrascrivere quel valore predefinito specificando un valore con --batch sulla riga di comando.
3.2.2. Passare il parametro batch al processo¶
Per fornire il valore del parametro al processo, dobbiamo aggiungerlo nella chiamata del processo.
Nel blocco workflow, effettuate la seguente modifica al codice:
Vedete che per fornire input multipli a un processo, li si elenca semplicemente nelle parentesi della chiamata, separati da virgole.
Avviso
DEVE fornire gli input al processo ESATTAMENTE NELLO STESSO ORDINE in cui sono elencati nel blocco di definizione degli input del processo.
3.3. Eseguire il workflow¶
Proviamo a eseguirlo con un nome di batch sulla riga di comando.
Output del comando
Viene eseguito con successo e produce l'output desiderato:
Ora, purché specifichiamo il parametro appropriatamente, le esecuzioni successive su altri batch di input non sovrascriveranno i risultati precedenti.
Takeaway¶
Sapete come passare più di un input a un processo.
Cosa c'è dopo?¶
Imparare come emettere output multipli e gestirli comodamente.
4. Aggiungere un output al passaggio di raccolta¶
Finora abbiamo usato processi che producevano solo un output ciascuno.
Abbiamo potuto accedere ai rispettivi output molto comodamente usando la sintassi <process>.out, che abbiamo usato sia nel contesto di passare un output al processo successivo (es. convertToUpper(sayHello.out)) che nel contesto della sezione publish: (es. first_output = sayHello.out).
Cosa succede quando un processo produce più di uno? Come gestiamo gli output multipli? Possiamo selezionare e usare un output specifico?
Tutte ottime domande, e la risposta breve è sì, possiamo!
Output multipli saranno impacchettati in channel separati. Possiamo scegliere di dare nomi a quei channel di output, il che rende facile riferirsi a loro individualmente in seguito, oppure possiamo riferirci a loro per indice.
Approfondiamo con un esempio.
A scopo dimostrativo, diciamo che vogliamo contare il numero di saluti che vengono raccolti per un dato batch di input e riportarlo in un file.
4.1. Modificare il processo per contare e produrre il numero di saluti¶
Questo richiederà due modifiche chiave alla definizione del processo: abbiamo bisogno di un modo per contare i saluti e scrivere un file di report, poi dobbiamo aggiungere quel file di report al blocco output del processo.
4.1.1. Contare il numero di saluti raccolti¶
Convenientemente, Nextflow ci permette di aggiungere codice arbitrario nel blocco script: della definizione del processo, il che è davvero utile per fare cose come questa.
Questo significa che possiamo usare la funzione incorporata size() di Nextflow per ottenere il numero di file nell'array input_files, e scrivere il risultato su file con un comando echo.
Nel blocco process collectGreetings, effettui le seguenti modifiche al codice:
La variabile count_greetings sarà calcolata a runtime.
4.1.2. Emettere il file di report e nominare gli output¶
In principio tutto ciò che dobbiamo fare è aggiungere il file di report al blocco output:.
Tuttavia, già che ci siamo, aggiungeremo anche alcuni tag emit: alle nostre dichiarazioni di output. Questi ci permetteranno di selezionare gli output per nome invece di dover usare indici posizionali.
Nel blocco process, effettuate la seguente modifica al codice:
I tag emit: sono opzionali, e avremmo potuto aggiungere un tag solo a uno degli output.
Ma come dice il detto, perché non entrambi?
Suggerimento
Se non nominate gli output di un processo usando emit:, potete comunque accedervi individualmente usando il rispettivo indice (a base zero).
Per esempio, userebbe <process>.out[0] per ottenere il primo output, <process>.out[1] per ottenere il secondo output, e così via.
Preferiamo nominare gli output perché altrimenti, è troppo facile prendere l'indice sbagliato per errore, specialmente quando il processo produce molti output.
4.2. Aggiornare gli output del workflow¶
Ora che abbiamo due output provenienti dal processo collectGreetings, l'output collectGreetings.out contiene due channel:
collectGreetings.out.outfilecontiene il file di output finalecollectGreetings.out.reportcontiene il file di report
Dobbiamo aggiornare gli output del workflow di conseguenza.
4.2.1. Aggiornare la sezione publish:¶
Nel blocco workflow, effettuate la seguente modifica al codice:
Come potete vedere, riferirsi a output specifici dei processi è ora banale.
Quando aggiungeremo un altro passaggio alla nostra pipeline nella Parte 5 (Containers), saremo in grado di riferirci facilmente a collectGreetings.out.outfile e passarlo al nuovo processo (spoiler: il nuovo processo si chiama cowpy).
Ma per ora, finiamo di aggiornare gli output a livello di workflow.
4.2.2. Aggiornare il blocco output¶
Nel blocco output, effettuate la seguente modifica al codice:
Non abbiamo bisogno di aggiornare la definizione dell'output collected dato che quel nome non è cambiato.
Dobbiamo solo aggiungere il nuovo output.
4.3. Eseguire il workflow¶
Proviamo a eseguirlo con l'attuale batch di saluti.
Output del comando
Se guardate nella directory results/hello_workflow/, troverete il nuovo file di report, trio-report.txt.
Apritelo per verificare che il workflow abbia correttamente riportato il conteggio dei saluti che sono stati elaborati.
Sentitevi liberi di aggiungere più saluti al CSV e testare cosa succede.
Takeaway¶
Sapete come far emettere a un processo output multipli nominati e come gestirli appropriatamente a livello di workflow.
Più in generale, capite i principi chiave coinvolti nel connettere processi insieme in modi comuni.
Cosa c'è dopo?¶
Prendetevi una pausa extra lunga, ve la siete guadagnata.
Quando siete pronti, passate alla Parte 4: Hello Modules per imparare come modularizzare il vostro codice per una migliore manutenibilità ed efficienza del codice.
Quiz¶
Come si accede all'output di un processo nel blocco workflow?
Cosa determina l'ordine di esecuzione dei processi in Nextflow?
Quando si dovrebbe usare l'operatore collect()?
Come si accede a un output nominato da un processo?
Qual è la sintassi corretta per nominare un output in un processo?
Quando si forniscono input multipli a un processo, cosa deve essere vero?