Writing your first CWL Workflow document
Goal
Building on the CommandLineTool
created on the previous step, the first Workflow
will include two processing steps:
- a fan-out processing pattern to use
gdal_translate
to clip the bands B04, B03 and B02 - a fan-in processing pattern using OTB's image stacking CLI to create an RGB composite
At this stage, there's a translate.cwl
CWL document and a file params.yml
.
CWL Workflow skeleton
Start creating the CWL workflow document with it's initial structure:
class:
label:
doc:
id:
requirements:
inputs:
outputs:
steps:
node_:
run:
in:
out:
cwlVersion: v1.0
Set the CWL class to Workflow
:
class: Workflow
label:
doc:
id:
requirements:
inputs:
outputs:
steps:
node_:
run:
in:
out:
cwlVersion: v1.0
Workflow information
Set the Workflow information (label
, doc
) and id
:
class: Workflow
label: Sentinel-2 RGB creation
doc: This workflow creates an RGB composite
id: main
requirements:
inputs:
outputs:
steps:
node_:
run:
in:
out:
cwlVersion: v1.0
Workflow requirements
Set the Workflow requirements to support the fan-out pattern. Setting the ScatterFeatureRequirement
requirement tells the CWL runner to spawn several processes (these may run in parallel).
class: Workflow
label: Sentinel-2 RGB creation
doc: This workflow creates an RGB composite
id: main
requirements:
- class: ScatterFeatureRequirement
inputs:
outputs:
steps:
node_:
run:
in:
out:
cwlVersion: v1.0
Workflow inputs
Define the inputs: - the list of geotifs - the area of interest - the EPSG code used to express the area of interest coordinates
The geotiffs
are a list of files, this workflow parameter type is now File[]
:
class: Workflow
label: Sentinel-2 RGB creation
doc: This workflow creates an RGB composite
id: main
requirements:
- class: ScatterFeatureRequirement
inputs:
geotiffs:
doc: list of geotifs
type: File[]
aoi:
doc: area of interest as a bounding box
type: string
epsg:
doc: EPSG code
type: string
default: "EPSG:4326"
outputs:
steps:
node_:
run:
in:
out:
cwlVersion: v1.0
Workflow processing step
Update the first processing step to set the sub-workflow translate.cwl
to be run:
class: Workflow
label: Sentinel-2 RGB creation
doc: This workflow creates an RGB composite
id: main
requirements:
- class: ScatterFeatureRequirement
inputs:
geotiffs:
doc: list of geotifs
type: File[]
aoi:
doc: area of interest as a bounding box
type: string
epsg:
doc: EPSG code
type: string
default: "EPSG:4326"
outputs:
steps:
node_:
run: translate.cwl
in:
out:
cwlVersion: v1.0
Workflow input mapping
Now, set the step inputs mapping where:
translate.cwl
inputgeotiff
is mapped to worklow inputgeotiffs
translate.cwl
inputaoi
is mapped to worklow inputaoi
translate.cwl
inputepsg
is mapped to worklow inputepsg
class: Workflow
label: Sentinel-2 RGB creation
doc: This workflow creates an RGB composite
id: main
requirements:
- class: ScatterFeatureRequirement
inputs:
geotiffs:
doc: list of geotifs
type: File[]
aoi:
doc: area of interest as a bounding box
type: string
epsg:
doc: EPSG code
type: string
default: "EPSG:4326"
outputs:
steps:
node_:
run: translate.cwl
in:
geotiff: geotiffs
aoi: aoi
epsg: epsg
out:
cwlVersion: v1.0
Workflow step output
Set the node_translate
output, i.e. the clipped_tif
generated by translate.cwl
:
class: Workflow
label: Sentinel-2 RGB creation
doc: This workflow creates an RGB composite
id: main
requirements:
- class: ScatterFeatureRequirement
inputs:
geotiffs:
doc: list of geotifs
type: File[]
aoi:
doc: area of interest as a bounding box
type: string
epsg:
doc: EPSG code
type: string
default: "EPSG:4326"
outputs:
steps:
node_translate:
run: translate.cwl
in:
geotiff: geotiffs
aoi: aoi
epsg: epsg
out:
- clipped_tif
cwlVersion: v1.0
Scatter (fan-out)
Now set the fan-out configuration by setting the scatter
method and input (the geotiffs):
class: Workflow
label: Sentinel-2 RGB creation
doc: This workflow creates an RGB composite
id: main
requirements:
- class: ScatterFeatureRequirement
inputs:
geotiffs:
doc: list of geotifs
type: File[]
aoi:
doc: area of interest as a bounding box
type: string
epsg:
doc: EPSG code
type: string
default: "EPSG:4326"
outputs:
steps:
node_translate:
run: translate.cwl
in:
geotiff: geotiffs
aoi: aoi
epsg: epsg
out:
- clipped_tif
scatter: geotiffs
scatterMethod: dotproduct
cwlVersion: v1.0
Adding another step
The next step requires an additional CWL CommandLineTool. It's a file called concatenate.cwl
and it contains:
class: CommandLineTool
requirements:
InlineJavascriptRequirement: {}
DockerRequirement:
dockerPull: terradue/otb-7.2.0
baseCommand: otbcli_ConcatenateImages
arguments:
- -out
- composite.tif
inputs:
tifs:
type: File[]
inputBinding:
position: 5
prefix: -il
separate: true
outputs:
composite:
outputBinding:
glob: "composite.tif"
type: File
stderr: stderr
stdout: stdout
cwlVersion: v1.0
Add the additional step to concatenate the clipped geotiffs.
class: Workflow
label: Sentinel-2 RGB creation
doc: This workflow creates an RGB composite
id: main
requirements:
- class: ScatterFeatureRequirement
inputs:
geotiffs:
doc: list of geotifs
type: File[]
aoi:
doc: area of interest as a bounding box
type: string
epsg:
doc: EPSG code
type: string
default: "EPSG:4326"
outputs:
steps:
node_translate:
run: translate.cwl
in:
geotiff: geotiffs
aoi: aoi
epsg: epsg
out:
- clipped_tif
scatter: geotiffs
scatterMethod: dotproduct
node_concatenate:
run: concatenate.cwl
in:
tifs:
source: node_translate/clipped_tif
out:
- composite
cwlVersion: v1.0
Mapping the inputs from the previous step
The inputs come from the previous step using the mapping:
class: Workflow
label: Sentinel-2 RGB creation
doc: This workflow creates an RGB composite
id: main
requirements:
- class: ScatterFeatureRequirement
inputs:
geotiffs:
doc: list of geotifs
type: File[]
aoi:
doc: area of interest as a bounding box
type: string
epsg:
doc: EPSG code
type: string
default: "EPSG:4326"
outputs:
steps:
node_translate:
run: translate.cwl
in:
geotiff: geotiffs
aoi: aoi
epsg: epsg
out:
- clipped_tif
scatter: geotiffs
scatterMethod: dotproduct
node_concatenate:
run: concatenate.cwl
in:
tifs:
source: node_translate/clipped_tif
out:
- composite
cwlVersion: v1.0
The workflow only misses the Workflow
output
block, a mapping to the node_concatenate
outputs:
class: Workflow
label: Sentinel-2 RGB creation
doc: This workflow creates an RGB composite
id: main
requirements:
- class: ScatterFeatureRequirement
inputs:
geotiffs:
doc: list of geotifs
type: File[]
aoi:
doc: area of interest as a bounding box
type: string
epsg:
doc: EPSG code
type: string
default: "EPSG:4326"
outputs:
rgb:
outputSource:
- node_concatenate/composite
type: File
steps:
node_translate:
run: translate.cwl
in:
geotiff: geotiffs
aoi: aoi
epsg: epsg
out:
- clipped_tif
scatter: geotiffs
scatterMethod: dotproduct
node_concatenate:
run: concatenate.cwl
in:
tifs:
source: node_translate/clipped_tif
out:
- composite
cwlVersion: v1.0
Test the CWL workflow
Download the three geotiff with:
curl -o B04.tif https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/53/H/PA/2021/7/S2B_53HPA_20210723_0_L2A/B04.tif
curl -o B03.tif https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/53/H/PA/2021/7/S2B_53HPA_20210723_0_L2A/B03.tif
curl -o B02.tif https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/53/H/PA/2021/7/S2B_53HPA_20210723_0_L2A/B02.tif
Create a file called composite-params.yml
with:
bbox: "136.659,-35.96,136.923,-35.791"
geotiffs:
- { "class": "File", "path": "B04.tif" }
- { "class": "File", "path": "B03.tif" }
- { "class": "File", "path": "B02.tif" }
epsg: "EPSG:4326"
Run the workflow with:
cwltool --parallel composite.cwl composite-params.yml
This creates a file called composite.tif
.