Parte 3: Hello Workflow¶
La maggior parte dei workflow nel mondo reale coinvolge più di un passaggio. In questo modulo di formazione, imparerai come connettere i processi insieme in un workflow a più fasi.
Questo ti insegnerà il modo Nextflow per ottenere i seguenti obiettivi:
- Far fluire i dati da un processo al successivo
- Raccogliere gli output da più chiamate di processo in una singola chiamata
- Passare più di un input a un processo
- Gestire più output provenienti da un processo
Per dimostrare ciò, continueremo a costruire sull'esempio del dominio indipendente Hello World delle Parti 1 e 2. Questa volta, apporteremo le seguenti modifiche al nostro workflow per riflettere meglio su come le persone costruiscono flussi di lavoro 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 unico 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 produca anche una statistica semplice su ciò che è stato elaborato.
0. Warmup: Run hello-workflow.nf
¶
Useremo lo script del workflow hello-workflow.nf
come punto di partenza.
Esso è equivalente allo script prodotto lavorando attraverso la Parte 2 di questo corso di formazione.
Per essere sicuri che tutto funzioni, esegui lo script una volta prima di apportare qualsiasi modifica
N E X T F L O W ~ version 24.10.0
Launching `hello-workflow.nf` [stupefied_sammet] DSL2 - revision: b9e466930b
executor > local (3)
[2a/324ce6] sayHello (3) | 3 of 3 ✔
Come in precedenza, troverai i file di output nella directory results
(specificata dalla direttiva publishDir
).
Note
Potrebbe esserci anche un file chiamato output.txt
se hai lavorato attraverso la Parte 2 nello stesso ambiente.
Se tutto ha funzionato, sei pronto per imparare come assemblare un workflow a più fasi.
1. Aggiungi un secondo step al workflow¶
Aggiungeremo un passaggio per convertire il saluto in maiuscolo. A tal fine, dobbiamo fare tre cose:
- Definire il comando che useremo per eseguire la conversione in maiuscolo.
- Scrivere un nuovo processo che racchiuda il comando per la conversione in maiuscolo.
- Chiamare il nuovo processo nel blocco del workflow e configurarlo per prendere l'output del processo
sayHello()
come input.
1.1 Definire il comando per la conversione in maiuscolo e testarlo nel terminale¶
Per eseguire la conversione dei saluti in maiuscolo, useremo uno strumento UNIX classico chiamato tr
per 'text replacement' (sostituzione del testo), con la seguente sintassi:
Questa è una sostituzione di testo molto semplice che non tiene conto delle lettere accentate, quindi ad esempio 'Holà' diventerà 'HOLà', ma andrà bene lo stesso per dimostrare i concetti di Nextflow, e questo è ciò che conta.
Per testarlo, possiamo eseguire il comando echo 'Hello World'
e passare il suo output al comando tr
:
Il risultato è un file di testo chiamato UPPER-output.txt
che contiene la versione in maiuscolo della stringa Hello World
:
Questo è ciò che proveremo a fare con il nostro workflow.
1.1 Scrivere il passaggio di conversione in maiuscolo come un processo Nextflow¶
Possiamo modellare il nostro nuovo processo sul primo, poiché vogliamo usare gli stessi componenti.
Aggiungi la seguente definizione del processo allo script del workflow:
hello-workflow.nf | |
---|---|
Qui, componiamo il secondo nome del file di output in base al nome del file di input, in modo simile a quanto abbiamo fatto inizialmente per l'output del primo processo.
Note
Nextflow determinerà l'ordine delle operazioni in base alla concatenazione degli input e degli output, quindi l'ordine delle definizioni dei processi nello script del flusso di lavoro non è importante. Tuttavia, ti consigliamo di essere gentile con i tuoi collaboratori e con il futuro te stesso, e cercare di scriverle in un ordine logico per motivi di leggibilità."
1.2 Aggiungi una chiamata al nuovo processo nel blocco del workflow¶
Ora dobbiamo dire a Nextflow di chiamare effettivamente il processo che abbiamo appena definito.
Nel blocco del workflow, apporta la seguente modifica al codice:
Questo non è ancora funzionante perché non abbiamo specificato cosa deve essere l'input per il processo convertToUpper()
.
1.3 Passare l'output del primo processo al secondo processo¶
Ora dobbiamo fare in modo che l'output del processo sayHello()
fluisca nel processo convertToUpper()
.
Comodamente, Nextflow impacchetta automaticamente l'output di un processo in un canale chiamato <process>.out
.
Quindi, l'output del processo sayHello
è un canale chiamato sayHello.out
, che possiamo collegare direttamente alla chiamata a convertToUpper()
.
Nel blocco del workflow, apporta la seguente modifica al codice:
Per un caso semplice come questo (un output a un input), è tutto ciò che dobbiamo fare per connettere due processi!
1.4 Esegui di nuovo il flusso di lavoro con -resume
¶
Eseguiamo di nuovo il flusso di lavoro utilizzando il flag -resume
, poiché abbiamo già eseguito con successo il primo passaggio del workflow.
Dovresti vedere il seguete output:
Output | |
---|---|
Ora c'è una riga extra nell'output della console (riga 7), che corrisponde al nuovo processo che abbiamo appena aggiunto.
Diamo un'occhiata all'interno della directory di lavoro di una delle chiamate al secondo processo.
work/b3/d52708edba8b864024589285cb3445/
├── Bonjour-output.txt -> /workspaces/training/hello-nextflow/work/79/33b2f0af8438486258d200045bd9e8/Bonjour-output.txt
└── UPPER-Bonjour-output.txt
Troviamo due file di output: l'output del primo processo e l'output del secondo.
L'output del primo processo è lì perché Nextflow lo ha messo in quella directory per avere tutto il necessario per l'esecuzione all'interno della stessa sottodirectory. Tuttavia, in realtà si tratta di un collegamento simbolico che punta al file originale nella sottodirectory della prima chiamata al processo. Per impostazione predefinita, quando si esegue su una singola macchina, come stiamo facendo qui, Nextflow utilizza collegamenti simbolici anziché copie per mettere in scena i file di input e i file intermedi.
Troverai anche i file di output finali nella directory results
, poiché abbiamo usato la direttiva publishDir
anche nel secondo processo.
results
├── Bonjour-output.txt
├── Hello-output.txt
├── Holà-output.txt
├── UPPER-Bonjour-output.txt
├── UPPER-Hello-output.txt
└── UPPER-Holà-output.txt
Pensiamo a come tutto ciò che abbiamo fatto è stato connettere l'output di sayHello
all'input di convertToUpper
e i due processi potrebbero essere eseguiti in serie.
Nextflow ha fatto il lavoro difficile di gestire i singoli file di input e output e passarli tra i due comandi per noi.
Questa è una delle ragioni per cui i canali di Nextflow sono così potenti: si occupano del lavoro noioso coinvolto nel connettere i passaggi del workflow.
Conclusioni¶
Ora sai come aggiungere un secondo passaggio che prende l'output del primo passaggio come input.
Cosa c'è dopo?¶
Impara come raccogliere gli output da chiamate di processo in batch e passarli a un singolo processo.
2. Aggiungi un terzo passo per raccogliere tutti i saluti¶
Quando utilizziamo un processo per applicare una trasformazione a ciascuno degli elementi di un canale, come stiamo facendo qui con i saluti multipli, a volte vogliamo raccogliere gli elementi dal canale di output di quel processo e passarli a un altro processo che esegue una sorta di analisi o somma.
Nel prossimo passo scriveremo semplicemente tutti gli elementi di un canale in un singolo file, utilizzando il comando UNIX cat
.
2.1. Definisci il comando di raccolta e testalo nel terminale¶
Il passo di raccolta che vogliamo aggiungere al nostro flusso di lavoro utilizzerà il comando cat
per concatenare i saluti in maiuscolo in un unico file.
Eseguiamo il comando da solo nel terminale per verificare che funzioni come previsto, proprio come abbiamo fatto in precedenza.
Esegui il seguente comando nel tuo 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 in maiuscolo dei saluti originali.
Questo è il risultato che vogliamo ottenere con il nostro workflow.
2.2. Crea un nuovo processo per eseguire il passo di raccolta¶
Creiamo un nuovo processo e chiamamolo collectGreetings()
.
Possiamo iniziare a scriverlo basandoci su quello precedente.
2.2.1. Scrivi le parti 'ovvie' del processo¶
Aggiungi la seguente definizione del processo allo script del workflow:
hello-workflow.nf | |
---|---|
Questo è ciò che possiamo scrivere con sicurezza in base a quanto appreso finora. Ma non è funzionale! Manca la definizione dell'input e la prima metà del comando dello script perché dobbiamo capire come scrivere quella parte.
2.2.2. Definire gli input per collectGreetings()
¶
Dobbiamo raccogliere i saluti da tutte le chiamate al processo convertToUpper()
.
Cosa sappiamo che possiamo ottenere dal passo precedente nel workflow?
Il canale di output di convertToUpper()
conterrà i percorsi dei singoli file che contengono i saluti in maiuscolo.
Questo corrisponde a uno slot di input; chiamiamolo input_files
per semplicità.
Nel blocco del processo, apporta la seguente modifica al codice:
Nota che usiamo il prefisso path
anche se ci aspettiamo che questo contenga più file.
Nextflow non ha problemi con questo, quindi non importa.
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. In particolare, non possiamo scrivere il comando in anticipo, quindi dobbiamo dire a Nextflow come comporlo in fase di esecuzione in base agli input che fluiscono nel processo.
In altre parole, se abbiamo un canale di input che contiene l'elemento [file1.txt, file2.txt, file3.txt]
, dobbiamo fare in modo che Nextflow lo trasformi in cat file1.txt file2.txt file3.txt
.
Fortunatamente, Nextflow è abbastanza felice di farlo per noi se scriviamo semplicemente cat ${input_files}
nel comando dello script.
Nel blocco del processo, apporta la seguente modifica al codice:
In teoria, questo dovrebbe gestire qualsiasi numero arbitrario di file di input.
Tip
Alcuni strumenti da riga di comando richiedono di fornire un argomento (come -input
) per ogni file di input.
In tal caso, dovremmo fare un po' di lavoro extra per comporre il comando.
Puoi vedere un esempio di questo nel corso di formazione Nextflow for Genomics.
2.3. Aggiungi il passo di raccolta al workflow¶
Ora dovremmo semplicemente chiamare il processo di raccolta sull'output del passo di trasformazione in maiuscolo.
2.3.1. Collega le chiamate ai processi¶
Nel blocco del workflow, apporta la seguente modifica al codice:
Questo collega l'output di convertToUpper()
all'input di collectGreetings()
.
2.3.2. Esegui il workflow con -resume
¶
Proviamolo.
Viene eseguito con successo, compreso il terzo passo:
Output | |
---|---|
Tuttavia, guarda il numero di chiamate per collectGreetings() alla riga 8. Ce ne aspettavamo solo una, ma ce ne sono tre.
E dai un'occhiata anche ai contenuti del file di output finale:
Oh no. Il passo di raccolta è stato eseguito singolarmente su ogni saluto, il che NON è quello che volevamo.
Dobbiamo fare qualcosa per dire esplicitamente a Nextflow che vogliamo che quel terzo passo venga eseguito su tutti gli elementi nel canale di output di convertToUpper()
.
2.4. Usa un operatore per raccogliere i saluti in un unico input¶
Sì, ancora una volta la risposta al nostro problema è un operatore.
In particolare, utilizzeremo l'operatore opportunamente chiamato [collect()
] (https://www.nextflow.io/docs/latest/reference/operator.html#collect).
2.4.1. Aggiungi l'operatore collect()
¶
Questa volta sarà un po' diverso perché non stiamo aggiungendo l'operatore nel contesto di una fabbrica di canali, ma a un canale di output.
Prendiamo convertToUpper.out
e aggiungiamo l'operatore collect()
, che diventa convertToUpper.out.collect()
.
Possiamo collegarlo direttamente alla chiamata del processo collectGreetings()
.
Nel blocco del workflow, apporta la seguente modifica al codice:
2.4.2. Aggiungi alcune dichiarazioni view()
¶
Includiamo anche un paio di dichiarazioni view()
per visualizzare lo stato prima e dopo dei contenuti del canale.
hello-workflow.nf | |
---|---|
Le dichiarazioni view()
possono essere posizionate dove vuoi; noi le abbiamo messe dopo la chiamata per migliorare la leggibilità.
2.4.3. Esegui di nuovo il workflow con -resume
¶
Proviamolo:
Viene eseguito con successo, anche se l'output del log potrebbe sembrare un po' più disordinato di così (l'abbiamo ripulito per migliorare la leggibilità).
Questa volta il terzo passo è stato chiamato solo una volta!
Guardando l'output delle dichiarazioni view()
, vediamo quanto segue:
- Tre dichiarazioni
Before collect:
, una per ciascun saluto: a quel punto i percorsi dei file sono elementi individuali nel canale. - Una sola dichiarazione
After collect:
: i tre percorsi dei file sono ora raggruppati in un singolo elemento.
Dai un'occhiata anche ai contenuti del file di output finale:
Questa volta abbiamo tutti e tre i saluti nel file di output finale. Successo! Rimuovi le chiamate view
opzionali per rendere gli output successivi meno verbosi.
Note
Se esegui questo processo più volte senza -resume
, vedrai che l'ordine dei saluti cambia da un'esecuzione all'altra.
Questo ti mostra che l'ordine in cui gli elementi fluiscono attraverso le chiamate ai processi non è garantito essere consistente.
Conclusione¶
Ora sai come raccogliere gli output da un gruppo di chiamate ai processi e passarli a un passo di analisi o somma congiunta.
Cosa c'è dopo?¶
Impara come passare più di un input a un processo.
3. Passare più di un input a un processo per nominare in modo univoco il file di output finale¶
Vogliamo essere in grado di dare al file di output finale un nome specifico, in modo da poter elaborare successivi lotti di saluti senza sovrascrivere i risultati finali.
A tal fine, apporteremo le seguenti modifiche 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 passarne il valore al processo di raccolta
3.1. Modificare il processo di raccolta per accettare un nome definito dall'utente per il file di output¶
Dobbiamo dichiarare l'input aggiuntivo e integrarlo nel nome del file di output.
3.1.1. Dichiarare l'input aggiuntivo nella definizione del processo¶
Buone notizie: possiamo dichiarare tutte le variabili di input che vogliamo.
Chiamiamo questa batch_name
.
Nel blocco del processo, apportiamo la seguente modifica al codice:
Puoi configurare i tuoi processi per aspettarsi quanti input desideri. Più avanti, imparerai come gestire gli input obbligatori e opzionali.
3.1.2. Utilizzare la variabile batch_name
nel nome del file di output¶
Nel blocco del processo, apporta la seguente modifica al codice:
Questo configura il processo per utilizzare il valore di batch_name
per generare un nome file specifico per il file di output finale del workflow.
3.2. Aggiungere un parametro batch
da riga di comando¶
Ora abbiamo bisogno di un modo per fornire il valore di batch_name
e passarne il valore alla chiamata del processo.
3.2.1. Utilizzare params
per configurare il parametro¶
Sai già come utilizzare il sistema params
per dichiarare i parametri CLI.
Usiamo questo sistema per dichiarare un parametro batch
(con un valore predefinito, perché siamo pigri).
Nella sezione dei parametri della pipeline, apporta le seguenti modifiche al codice:
Ricorda che puoi sovrascrivere il 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 del workflow, apporta la seguente modifica al codice:
Warning
Devi fornire gli input a un processo NELLO STESSO ORDINE ESATTO in cui sono elencati nel blocco di definizione degli input del processo.
3.3. Eseguire il workflow¶
Proviamo a eseguire questo con un nome di batch sulla riga di comando.
Viene eseguito con successo:
Output | |
---|---|
E produce l'output desiderato:
Ora, le esecuzioni successive su altri lotti di input non sovrascriveranno i risultati precedenti (purché specifichiamo correttamente il parametro).
Cosa devi ricordare¶
Ora sai come passare più di un input a un processo.
Cosa succede dopo?¶
Impara come emettere più output e gestirli comodamente.
4. Aggiungere un output al passo di raccolta¶
Quando un processo produce un solo output, è facile accedervi (nel blocco del workflow) utilizzando la sintassi <process>.out
.
Quando ci sono due o più output, il metodo predefinito per selezionare un output specifico è usare l'indice corrispondente (basato su zero); per esempio, useresti <process>.out[0]
per ottenere il primo output.
Questo però non è particolarmente comodo, perché è facile selezionare l'indice sbagliato.
Vediamo come possiamo selezionare e utilizzare un output specifico di un processo quando ce ne sono più di uno.
Per scopi dimostrativi, supponiamo che vogliamo contare e segnalare il numero di saluti che vengono raccolti per un dato lotto di input.
A tal fine, apporteremo le seguenti modifiche al workflow:
- Modificare il processo per contare e produrre il numero di saluti
- Una volta che il processo è stato eseguito, selezionare il conteggio e riportarlo utilizzando
view
(nel blocco del workflow)
4.1. Modificare il processo per contare e produrre il numero di saluti¶
Questo richiederà due modifiche principali alla definizione del processo: dobbiamo trovare un modo per contare i saluti, poi dobbiamo aggiungere quel conteggio al blocco output
del processo.
4.1.1. Contare il numero di saluti raccolti¶
Fortunatamente, Nextflow ci consente di aggiungere codice arbitrario nel blocco script:
della definizione del processo, che risulta molto utile per fare cose come questa.
Ciò significa che possiamo utilizzare la funzione incorporata size()
per ottenere il numero di file nell'array input_files
.
Nel blocco del processo collectGreetings
, apporta la seguente modifica al codice:
La variabile count_greetings
verrà calcolata durante l'esecuzione.
4.1.2. Emissione del conteggio come output con nome¶
In linea di principio, tutto ciò che dobbiamo fare è aggiungere la variabile count_greetings
al blocco output:
.
Tuttavia, mentre ci siamo, aggiungeremo anche alcune etichette emit:
alle nostre dichiarazioni di output. Queste ci permetteranno di selezionare gli output per nome, invece di dover usare indici posizionali.
Nel blocco del processo, apporta la seguente modifica al codice:
Le etichette emit:
sono opzionali, e avremmo potuto aggiungere un'etichetta solo a uno degli output.
Ma, come si suol dire, perché non entrambi?
4.2. Segnalare l'output alla fine del workflow¶
Ora che abbiamo due output provenienti dal processo collectGreetings
, l'output collectGreetings.out
contiene due canali:
collectGreetings.out.outfile
contiene il file di output finalecollectGreetings.out.count
contiene il conteggio dei saluti
Potremmo inviare uno o entrambi questi output a un altro processo per ulteriori elaborazioni. Tuttavia, per concludere il lavoro, useremo semplicemente view()
per dimostrare che possiamo accedere e segnalare il conteggio dei saluti.
Nel blocco del workflow, apporta la seguente modifica al codice:
Note
Esistono altri modi per ottenere un risultato simile, inclusi alcuni più eleganti, come l'operatore count()
, ma questo ci permette di mostrare come gestire più output, che è ciò che ci interessa.
4.3. Eseguire il workflow¶
Proviamo a eseguire questo con l'attuale lotto di saluti.
Viene eseguito con successo:
Output | |
---|---|
L'ultima riga (riga 8) mostra che abbiamo correttamente recuperato il conteggio dei saluti elaborati. Sentiti libero di aggiungere più saluti al CSV e vedere cosa succede.
Cosa devi ricordare¶
Ora sai come far emettere a un processo un output con nome e come accedervi dal blocco del workflow.
Più in generale, comprendi i principi chiave per connettere i processi in modi comuni.
Cosa succede dopo?¶
Fai una lunga pausa, te la sei guadagnata. Quando sei pronto, passa alla Parte 4 per imparare a modularizzare il tuo codice per una migliore manutenibilità e efficienza del codice.