Parte 3: Hello Workflow¶
Tradução assistida por IA - saiba mais e sugira melhorias
Veja a playlist completa no canal do Nextflow no YouTube.
A transcrição do vídeo está disponível aqui.
A maioria dos fluxos de trabalho do mundo real envolve mais de uma etapa. Neste módulo de treinamento, você aprenderá como conectar processos em um fluxo de trabalho com múltiplas etapas.
Isso ensinará a você a maneira Nextflow de realizar o seguinte:
- Fazer os dados fluírem de um processo para o próximo
- Coletar saídas de múltiplas chamadas de processo em uma única chamada de processo
- Passar parâmetros adicionais para um processo
- Lidar com múltiplas saídas provenientes de um processo
Para demonstrar, continuaremos construindo sobre o exemplo Hello World agnóstico de domínio das Partes 1 e 2. Desta vez, faremos as seguintes alterações em nosso fluxo de trabalho para refletir melhor como as pessoas constroem fluxos de trabalho reais:
- Adicionar uma segunda etapa que converte a saudação para maiúsculas.
- Adicionar uma terceira etapa que coleta todas as saudações transformadas e as escreve em um único arquivo.
- Adicionar um parâmetro para nomear o arquivo de saída final e passá-lo como uma entrada secundária para a etapa de coleta.
- Fazer a etapa de coleta também reportar uma estatística simples sobre o que foi processado.
Como começar a partir desta seção
Esta seção do curso assume que você completou as Partes 1-2 do curso Hello Nextflow, mas se você está confortável com os conceitos básicos cobertos nessas seções, pode começar daqui sem fazer nada especial.
0. Aquecimento: Execute hello-workflow.nf¶
Vamos usar o script de fluxo de trabalho hello-workflow.nf como ponto de partida.
Ele é equivalente ao script produzido ao trabalhar na Parte 2 deste curso de treinamento, exceto que removemos as instruções view() e alteramos o destino de saída:
Este diagrama resume a operação atual do fluxo de trabalho. Deve parecer familiar, exceto que agora estamos mostrando explicitamente que as saídas do processo são empacotadas em um canal, assim como as entradas foram. Vamos colocar esse canal de saída em bom uso em um minuto.
Apenas para garantir que tudo está funcionando, execute o script uma vez antes de fazer qualquer alteração:
Saída do comando
Como anteriormente, você encontrará os arquivos de saída no local especificado no bloco output.
Para este capítulo, está em results/hello_workflow/.
Conteúdo do diretório
Se isso funcionou para você, você está pronto para aprender como montar um fluxo de trabalho com múltiplas etapas.
1. Adicione uma segunda etapa ao fluxo de trabalho¶
Vamos adicionar uma etapa para converter cada saudação para maiúsculas.
Para isso, precisamos fazer três coisas:
- Definir o comando que vamos usar para fazer a conversão para maiúsculas.
- Escrever um novo processo que envolve o comando de conversão para maiúsculas.
- Chamar o novo processo no bloco workflow e configurá-lo para receber a saída do processo
sayHello()como entrada.
1.1. Defina o comando de conversão para maiúsculas e teste-o no terminal¶
Para fazer a conversão das saudações para maiúsculas, vamos usar uma ferramenta UNIX clássica chamada tr para 'substituição de texto', com a seguinte sintaxe:
Esta é uma substituição de texto muito ingênua que não leva em conta letras acentuadas, então por exemplo 'Holà' se tornará 'HOLà', mas fará um trabalho bom o suficiente para demonstrar os conceitos do Nextflow e isso é o que importa.
Para testá-lo, podemos executar o comando echo 'Hello World' e direcionar sua saída para o comando tr:
A saída é um arquivo de texto chamado UPPER-output.txt que contém a versão em maiúsculas da string Hello World.
Isso é basicamente o que vamos tentar fazer com nosso fluxo de trabalho.
1.2. Escreva a etapa de conversão para maiúsculas como um processo Nextflow¶
Podemos modelar nosso novo processo no primeiro, já que queremos usar todos os mesmos componentes.
Adicione a seguinte definição de processo ao script de fluxo de trabalho, logo abaixo do primeiro:
| hello-workflow.nf | |
|---|---|
Neste, componhamos o segundo nome de arquivo de saída com base no nome do arquivo de entrada, de forma similar ao que fizemos originalmente para a saída do primeiro processo.
1.3. Adicione uma chamada ao novo processo no bloco workflow¶
Agora precisamos dizer ao Nextflow para realmente chamar o processo que acabamos de definir.
No bloco workflow, faça a seguinte alteração de código:
| hello-workflow.nf | |
|---|---|
Isso ainda não está funcional porque não especificamos o que deve ser entrada para o processo convertToUpper().
1.4. Passe a saída do primeiro processo para o segundo processo¶
Agora precisamos fazer a saída do processo sayHello() fluir para o processo convertToUpper().
Convenientemente, o Nextflow empacota automaticamente a saída de um processo em um canal, como mostrado no diagrama na seção de aquecimento.
Podemos nos referir ao canal de saída de um processo como <processo>.out.
Então a saída do processo sayHello é um canal chamado sayHello.out, que podemos conectar diretamente na chamada para convertToUpper().
No bloco workflow, faça a seguinte alteração de código:
Para um caso simples como este (uma saída para uma entrada), isso é tudo que precisamos fazer para conectar dois processos!
1.5. Configure a publicação de saída do fluxo de trabalho¶
Finalmente, vamos atualizar as saídas do fluxo de trabalho para publicar os resultados do segundo processo também.
1.5.1. Atualize a seção publish: do bloco workflow¶
No bloco workflow, faça a seguinte alteração de código:
A lógica é a mesma de antes.
1.5.2. Atualize o bloco output¶
No bloco output, faça a seguinte alteração de código:
Mais uma vez, a lógica é a mesma de antes.
Isso mostra que você pode controlar as configurações de saída em um nível muito granular, para cada saída individual. Sinta-se à vontade para tentar alterar os caminhos ou o modo de publicação para um dos processos para ver o que acontece.
Claro, isso significa que estamos repetindo algumas informações aqui, o que pode se tornar inconveniente se quisermos atualizar o local para todas as saídas da mesma forma. Mais tarde no curso, você aprenderá como configurar essas definições para múltiplas saídas de forma estruturada.
1.6. Execute o fluxo de trabalho com -resume¶
Vamos testar isso usando a flag -resume, já que já executamos a primeira etapa do fluxo de trabalho com sucesso.
Saída do comando
Agora há uma linha extra na saída do console que corresponde ao novo processo que acabamos de adicionar.
Você encontrará as saídas no diretório results/hello_workflow conforme definido no bloco output.
Conteúdo do diretório
Isso é conveniente! Mas ainda vale a pena dar uma olhada dentro do diretório de trabalho de uma das chamadas para o segundo processo.
Conteúdo do diretório
Observe que há dois arquivos *-output: a saída do primeiro processo assim como a saída do segundo.
A saída do primeiro processo está lá porque o Nextflow a preparou lá para ter tudo o que é necessário para a execução dentro do mesmo subdiretório.
No entanto, é na verdade um link simbólico apontando para o arquivo original no subdiretório da primeira chamada de processo. Por padrão, ao executar em uma única máquina como estamos fazendo aqui, o Nextflow usa links simbólicos em vez de cópias para preparar arquivos de entrada e intermediários.
Agora, antes de seguir em frente, pense em como tudo o que fizemos foi conectar a saída de sayHello à entrada de convertToUpper e os dois processos puderam ser executados em série.
O Nextflow fez o trabalho pesado de lidar com arquivos individuais de entrada e saída e passá-los entre os dois comandos para nós.
Esta é uma das razões pelas quais os canais do Nextflow são tão poderosos: eles cuidam do trabalho braçal envolvido em conectar etapas de fluxo de trabalho.
Conclusão¶
Você sabe como encadear processos conectando a saída de uma etapa como entrada para a próxima etapa.
O que vem a seguir?¶
Aprenda como coletar saídas de chamadas de processo em lote e alimentá-las em um único processo.
2. Adicione uma terceira etapa para coletar todas as saudações¶
Quando usamos um processo para aplicar uma transformação a cada um dos elementos em um canal, como estamos fazendo aqui com as múltiplas saudações, às vezes queremos coletar elementos do canal de saída desse processo e alimentá-los em outro processo que realiza algum tipo de análise ou sumarização.
Para demonstrar, adicionaremos uma nova etapa ao nosso pipeline que coleta todas as saudações em maiúsculas produzidas pelo processo convertToUpper e as escreve em um único arquivo.
Sem estragar a surpresa, mas isso vai envolver um operador muito útil.
2.1. Defina o comando de coleta e teste-o no terminal¶
A etapa de coleta que queremos adicionar ao nosso fluxo de trabalho usará o comando cat para concatenar múltiplas saudações em maiúsculas em um único arquivo.
Vamos executar o comando sozinho no terminal para verificar que funciona como esperado, assim como fizemos anteriormente.
Execute o seguinte no seu terminal:
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
A saída é um arquivo de texto chamado COLLECTED-output.txt que contém as versões em maiúsculas das saudações originais.
Esse é o resultado que queremos alcançar com nosso fluxo de trabalho.
2.2. Crie um novo processo para fazer a etapa de coleta¶
Vamos criar um novo processo e chamá-lo de collectGreetings().
Podemos começar a escrevê-lo com base no que vimos antes.
2.2.1. Escreva as partes 'óbvias' do processo¶
Adicione a seguinte definição de processo ao script de fluxo de trabalho:
| hello-workflow.nf | |
|---|---|
Isso é o que podemos escrever com confiança com base no que você aprendeu até agora. Mas isso não é funcional! Deixa de fora a(s) definição(ões) de entrada e a primeira metade do comando script porque precisamos descobrir como escrever isso.
2.2.2. Defina as entradas para collectGreetings()¶
Precisamos coletar as saudações de todas as chamadas para o processo convertToUpper().
O que sabemos que podemos obter da etapa anterior no fluxo de trabalho?
O canal de saída de convertToUpper() conterá os caminhos para os arquivos individuais contendo as saudações em maiúsculas.
Isso equivale a um slot de entrada; vamos chamá-lo de input_files por simplicidade.
No bloco de processo, faça a seguinte alteração de código:
Observe que usamos o prefixo path mesmo que esperemos que isso contenha múltiplos arquivos.
2.2.3. Componha o comando de concatenação¶
É aqui que as coisas podem ficar um pouco complicadas, porque precisamos ser capazes de lidar com um número arbitrário de arquivos de entrada. Especificamente, não podemos escrever o comando antecipadamente, então precisamos dizer ao Nextflow como compô-lo em tempo de execução com base nas entradas que fluem para o processo.
Em outras palavras, se tivermos um canal de entrada contendo o elemento [file1.txt, file2.txt, file3.txt], precisamos que o Nextflow transforme isso em cat file1.txt file2.txt file3.txt.
Felizmente, o Nextflow está muito feliz em fazer isso por nós se simplesmente escrevermos cat ${input_files} no comando script.
No bloco de processo, faça a seguinte alteração de código:
Em teoria, isso deve lidar com qualquer número arbitrário de arquivos de entrada.
Dica
Algumas ferramentas de linha de comando exigem fornecer um argumento (como -input) para cada arquivo de entrada.
Nesse caso, teríamos que fazer um pouco de trabalho extra para compor o comando.
Você pode ver um exemplo disso no curso de treinamento Nextflow for Genomics.
2.3. Adicione a etapa de coleta ao fluxo de trabalho¶
Agora devemos apenas precisar chamar o processo de coleta na saída da etapa de conversão para maiúsculas.
Isso também é um canal, chamado convertToUpper.out.
2.3.1. Conecte as chamadas de processo¶
No bloco workflow, faça a seguinte alteração de código:
Isso conecta a saída de convertToUpper() à entrada de collectGreetings().
2.3.2. Execute o fluxo de trabalho com -resume¶
Vamos tentar.
Saída do comando
Ele executa com sucesso, incluindo a terceira etapa.
No entanto, observe o número de chamadas para collectGreetings() na última linha.
Estávamos esperando apenas uma, mas há três.
Agora dê uma olhada no conteúdo do arquivo de saída final.
Oh não. A etapa de coleta foi executada individualmente em cada saudação, o que NÃO é o que queríamos.
Precisamos fazer algo para dizer ao Nextflow explicitamente que queremos que essa terceira etapa seja executada em todos os elementos no canal de saída de convertToUpper().
2.4. Use um operador para coletar as saudações em uma única entrada¶
Sim, mais uma vez a resposta para nosso problema é um operador.
Especificamente, vamos usar o operador apropriadamente chamado collect().
2.4.1. Adicione o operador collect()¶
Desta vez vai parecer um pouco diferente porque não estamos adicionando o operador no contexto de uma fábrica de canais; estamos adicionando-o a um canal de saída.
Pegamos o convertToUpper.out e anexamos o operador collect(), o que nos dá convertToUpper.out.collect().
Podemos conectar isso diretamente na chamada do processo collectGreetings().
No bloco workflow, faça a seguinte alteração de código:
2.4.2. Adicione algumas instruções view()¶
Vamos também incluir algumas instruções view() para visualizar os estados antes e depois do conteúdo do canal.
| hello-workflow.nf | |
|---|---|
As instruções view() podem ir onde você quiser; nós as colocamos logo após a chamada para legibilidade.
2.4.3. Execute o fluxo de trabalho novamente com -resume¶
Vamos tentar:
Saída do 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]
Ele executa com sucesso, embora a saída do log possa parecer um pouco mais bagunçada do que isso (nós a limpamos para legibilidade).
Desta vez a terceira etapa foi chamada apenas uma vez!
Olhando para a saída das instruções view(), vemos o seguinte:
- Três instruções
Antes do collect:, uma para cada saudação: nesse ponto os caminhos de arquivo são itens individuais no canal. - Uma única instrução
Depois do collect:: os três caminhos de arquivo agora estão empacotados em um único elemento.
Podemos resumir isso com o seguinte diagrama:
Finalmente, você pode dar uma olhada no conteúdo do arquivo de saída para se satisfazer de que tudo funcionou corretamente.
Desta vez temos todas as três saudações no arquivo de saída final. Sucesso!
Nota
Se você executar isso várias vezes sem -resume, verá que a ordem das saudações muda de uma execução para a próxima.
Isso mostra que a ordem na qual os elementos fluem através das chamadas de processo não é garantida ser consistente.
2.4.4. Remova as instruções view() para legibilidade¶
Antes de passar para a próxima seção, recomendamos que você exclua as instruções view() para evitar poluir a saída do console.
Esta é basicamente a operação reversa do ponto 2.4.2.
Conclusão¶
Você sabe como coletar saídas de um lote de chamadas de processo e alimentá-las em uma etapa de análise ou sumarização conjunta.
Para recapitular, isso é o que você construiu até agora:
O que vem a seguir?¶
Aprenda como passar mais de uma entrada para um processo.
3. Passe parâmetros adicionais para um processo¶
Queremos ser capazes de nomear o arquivo de saída final algo específico para processar lotes subsequentes de saudações sem sobrescrever os resultados finais.
Para isso, vamos fazer os seguintes refinamentos no fluxo de trabalho:
- Modificar o processo coletor para aceitar um nome definido pelo usuário para o arquivo de saída (
batch_name) - Adicionar um parâmetro de linha de comando ao fluxo de trabalho (
--batch) e passá-lo para o processo coletor
3.1. Modifique o processo coletor¶
Vamos precisar declarar a entrada adicional e integrá-la ao nome do arquivo de saída.
3.1.1. Declare a entrada adicional¶
Boas notícias: podemos declarar quantas variáveis de entrada quisermos na definição do processo.
Vamos chamar esta de batch_name.
No bloco de processo, faça a seguinte alteração de código:
Você pode configurar seus processos para esperar quantas entradas quiser. Agora, todas estão configuradas para serem entradas obrigatórias; você deve fornecer um valor para o fluxo de trabalho funcionar.
Você aprenderá como gerenciar entradas obrigatórias vs. opcionais mais tarde em sua jornada com Nextflow.
3.1.2. Use a variável batch_name no nome do arquivo de saída¶
Podemos inserir a variável no nome do arquivo de saída da mesma forma que compusemos nomes de arquivo dinâmicos antes.
No bloco de processo, faça a seguinte alteração de código:
Isso configura o processo para usar o valor de batch_name para gerar um nome de arquivo específico para a saída final do fluxo de trabalho.
3.2. Adicione um parâmetro de linha de comando batch¶
Agora precisamos de uma maneira de fornecer o valor para batch_name e alimentá-lo para a chamada do processo.
3.2.1. Use params para configurar o parâmetro¶
Você já sabe como usar o sistema params para declarar parâmetros CLI.
Vamos usar isso para declarar um parâmetro batch (com um valor padrão porque somos preguiçosos).
Na seção de parâmetros do pipeline, faça as seguintes alterações de código:
Assim como demonstramos para --input, você pode sobrescrever esse valor padrão especificando um valor com --batch na linha de comando.
3.2.2. Passe o parâmetro batch para o processo¶
Para fornecer o valor do parâmetro ao processo, precisamos adicioná-lo na chamada do processo.
No bloco workflow, faça a seguinte alteração de código:
Você vê que para fornecer múltiplas entradas a um processo, você simplesmente as lista nos parênteses da chamada, separadas por vírgulas.
Aviso
Você DEVE fornecer as entradas ao processo na EXATA MESMA ORDEM em que estão listadas no bloco de definição de entrada do processo.
3.3. Execute o fluxo de trabalho¶
Vamos tentar executar isso com um nome de lote na linha de comando.
Saída do comando
Ele executa com sucesso e produz a saída desejada:
Agora, desde que especifiquemos o parâmetro apropriadamente, execuções subsequentes em outros lotes de entradas não sobrescreverão resultados anteriores.
Conclusão¶
Você sabe como passar mais de uma entrada para um processo.
O que vem a seguir?¶
Aprenda como emitir múltiplas saídas e lidar com elas convenientemente.
4. Adicione uma saída à etapa coletora¶
Até agora estivemos usando processos que produziam apenas uma saída cada.
Conseguimos acessar suas respectivas saídas muito convenientemente usando a sintaxe <processo>.out, que usamos tanto no contexto de passar uma saída para o próximo processo (por exemplo, convertToUpper(sayHello.out)) quanto no contexto da seção publish: (por exemplo, first_output = sayHello.out).
O que acontece quando um processo produz mais de uma? Como lidamos com as múltiplas saídas? Podemos selecionar e usar uma saída específica?
Todas excelentes perguntas, e a resposta curta é sim, podemos!
Múltiplas saídas serão empacotadas em canais separados. Podemos escolher dar nomes a esses canais de saída, o que facilita referenciá-los individualmente mais tarde, ou podemos referenciá-los por índice.
Para fins de demonstração, digamos que queremos contar o número de saudações que estão sendo coletadas para um determinado lote de entradas e reportá-lo em um arquivo.
4.1. Modifique o processo para contar e emitir o número de saudações¶
Isso exigirá duas mudanças principais na definição do processo: precisamos de uma maneira de contar as saudações e escrever um arquivo de relatório, então precisamos adicionar esse arquivo de relatório ao bloco output do processo.
4.1.1. Conte o número de saudações coletadas¶
Convenientemente, o Nextflow nos permite adicionar código arbitrário no bloco script: da definição do processo, o que é muito útil para fazer coisas como esta.
Isso significa que podemos usar a função integrada size() do Nextflow para obter o número de arquivos no array input_files, e escrever o resultado em um arquivo com um comando echo.
No bloco de processo collectGreetings, faça as seguintes alterações de código:
A variável count_greetings será computada em tempo de execução.
4.1.2. Emita o arquivo de relatório e nomeie as saídas¶
Em princípio, tudo o que precisamos fazer é adicionar o arquivo de relatório ao bloco output:.
No entanto, enquanto estamos nisso, também vamos adicionar algumas tags emit: às nossas declarações de saída. Estas nos permitirão selecionar as saídas por nome em vez de ter que usar índices posicionais.
No bloco de processo, faça a seguinte alteração de código:
As tags emit: são opcionais, e poderíamos ter adicionado uma tag a apenas uma das saídas.
Mas como diz o ditado, por que não ambas?
Dica
Se você não nomear as saídas de um processo usando emit:, ainda pode acessá-las individualmente usando seu respectivo índice (baseado em zero).
Por exemplo, você usaria <processo>.out[0] para obter a primeira saída, <processo>.out[1] para obter a segunda saída, e assim por diante.
Preferimos nomear saídas porque caso contrário, é muito fácil pegar o índice errado por erro, especialmente quando o processo produz muitas saídas.
4.2. Atualize as saídas do fluxo de trabalho¶
Agora que temos duas saídas saindo do processo collectGreetings, a saída collectGreetings.out contém dois canais:
collectGreetings.out.outfilecontém o arquivo de saída finalcollectGreetings.out.reportcontém o arquivo de relatório
Precisamos atualizar as saídas do fluxo de trabalho de acordo.
4.2.1. Atualize a seção publish:¶
No bloco workflow, faça a seguinte alteração de código:
Como você pode ver, referir-se a saídas específicas de processo agora é trivial.
Quando formos adicionar mais uma etapa ao nosso pipeline na Parte 5 (Contêineres), seremos capazes de facilmente referir-nos a collectGreetings.out.outfile e passá-lo para o novo processo (spoiler: o novo processo se chama cowpy).
Mas por enquanto, vamos terminar de atualizar as saídas no nível do fluxo de trabalho.
4.2.2. Atualize o bloco output¶
No bloco output, faça a seguinte alteração de código:
Não precisamos atualizar a definição de saída collected já que esse nome não mudou.
Só precisamos adicionar a nova saída.
4.3. Execute o fluxo de trabalho¶
Vamos tentar executar isso com o lote atual de saudações.
Saída do comando
Se você olhar no diretório results/hello_workflow/, encontrará o novo arquivo de relatório, trio-report.txt.
Abra-o para verificar que o fluxo de trabalho reportou corretamente a contagem de saudações que foram processadas.
Sinta-se à vontade para adicionar mais saudações ao CSV e testar o que acontece.
Conclusão¶
Você sabe como fazer um processo emitir múltiplas saídas nomeadas e como lidar com elas apropriadamente no nível do fluxo de trabalho.
De forma mais geral, você entende os princípios-chave envolvidos em conectar processos de maneiras comuns.
O que vem a seguir?¶
Faça uma pausa extra longa, você mereceu.
Quando estiver pronto, passe para Parte 4: Hello Modules para aprender como modularizar seu código para melhor manutenibilidade e eficiência de código.
Quiz¶
Como você acessa a saída de um processo no bloco workflow?
O que determina a ordem de execução dos processos no Nextflow?
Quando você deve usar o operador collect()?
Como você acessa uma saída nomeada de um processo?
Qual é a sintaxe correta para nomear uma saída em um processo?
Ao fornecer múltiplas entradas para um processo, o que deve ser verdadeiro?