diff --git a/examples/full_parset.sh b/examples/full_parset.sh new file mode 100755 index 0000000000000000000000000000000000000000..cb0db7fabf415da087893b4d08d56679d0c9c293 --- /dev/null +++ b/examples/full_parset.sh @@ -0,0 +1,7 @@ +#!/bin/sh -eu + +# Grep for keys, optional (commented) keys, and sections; +# uncomment and convert ini to json +egrep '^((#[[:blank:]]*)?[[:alnum:]_]* =|\[[^]]*\])' rapthor.parset \ + | sed 's,^# *,,' \ + > rapthor_full.parset diff --git a/examples/ini2json.sh b/examples/ini2json.sh new file mode 100755 index 0000000000000000000000000000000000000000..90cdd749e6d9532b9e66c50eeec0cc11a937dbdf --- /dev/null +++ b/examples/ini2json.sh @@ -0,0 +1,36 @@ +#!/bin/sh -e +# +# Convert an INI-file to a JSON-file. + +if [ $# -lt 1 -o $# -gt 2 ] +then + echo "Usage: ${0} <ini-file> [<json-file>]" + exit 1 +fi + +INPUT="${1}" +OUTPUT="${2:-${INPUT}.json}" + +# Check if input file exists. Bail out if not. +if ! [ -f "${INPUT}" ] +then + echo "${INPUT}: No such file or directory" + exit 1 +fi + +# Check if `jc`, a tool for converting INI to JSON, is available. +# If not, install it in a virtual environment. +if ! $(which jc > /dev/null) +then + # Are we inside a virtual environment? If not, create one and activate it. + if [ -z "${VIRTUAL_ENV}" ] + then + ROOT="$(git rev-parse --show-toplevel)" + python3 -m venv "${ROOT}/venv" + . "${ROOT}/venv/bin/activate" + fi + pip install --disable-pip-version-check --quiet --require-virtualenv jc +fi + +# Do the actual conversion from INI to JSON +cat "${INPUT}" | jc --ini -mp > "${OUTPUT}" diff --git a/rapthor/pipeline/execution/examples/hba_nl/hba_nl.json b/rapthor/pipeline/execution/examples/hba_nl/hba_nl.json index 3fc73779d69c8db39674686c45b6ffa227fbe053..5ed0f8337d8e27b9b3e6f5d5ace66cbc5ab3ea6c 100644 --- a/rapthor/pipeline/execution/examples/hba_nl/hba_nl.json +++ b/rapthor/pipeline/execution/examples/hba_nl/hba_nl.json @@ -1,10 +1,33 @@ { "surls": [ "gsiftp://gridftp.grid.sara.nl/pnfs/grid.sara.nl/data/lofar/user/disk/rapthor/testing/L667526_121MHz_uv_pre-cal.ms.tar", - "gsiftp://gridftp.grid.sara.nl/pnfs/grid.sara.nl/data/lofar/user/disk/rapthor/testing/L667526_123MHz_uv_pre-cal.ms.tar" + "gsiftp://gridftp.grid.sara.nl/pnfs/grid.sara.nl/data/lofar/user/disk/rapthor/testing/L667526_123MHz_uv_pre-cal.ms.tar", + "gsiftp://gridftp.grid.sara.nl/pnfs/grid.sara.nl/data/lofar/user/disk/rapthor/testing/L667526_125MHz_uv_pre-cal.ms.tar", + "gsiftp://gridftp.grid.sara.nl/pnfs/grid.sara.nl/data/lofar/user/disk/rapthor/testing/L667526_127MHz_uv_pre-cal.ms.tar" ], "settings": { - "class": "File", - "path": "L667526.config.json" + "global": { + "regroup_input_skymodel": "True", + "selfcal_data_fraction": "0.01", + "strategy": "/project/rapthor/Data/rapthor/L667526/custom_calibration_strategy.py" + }, + "calibration": { + }, + "imaging": { + "dde_method": "facets" + }, + "cluster": { + "use_container": "True", + "container_type": "singularity" + } + }, + "virtualenv": { + "python": { + "version": "3.9" + }, + "rapthor": { + "version": "atdb_integration_test", + "requirements": [] + } } } diff --git a/rapthor/pipeline/execution/hba_nl.cwl b/rapthor/pipeline/execution/hba_nl.cwl index 18034fbb3afb9fbc2670d6785f4ede1769c25671..5105f3f14f283219108e6fdafcf96709db5337b4 100644 --- a/rapthor/pipeline/execution/hba_nl.cwl +++ b/rapthor/pipeline/execution/hba_nl.cwl @@ -6,6 +6,7 @@ doc: | This is the top-level workflow for the Rapthor HBA NL pipeline. requirements: +- class: MultipleInputFeatureRequirement - class: ScatterFeatureRequirement - class: SubworkflowFeatureRequirement @@ -14,51 +15,30 @@ inputs: doc: List of SURLs to the input data type: string[] - id: settings - doc: File containing the settings for the Rapthor pipeline in JSON format - type: File -- id: skymodel - type: File? - doc: Optional input sky model -- id: apparent_skymodel - type: File? - doc: Optional apparent sky model -- id: strategy + doc: Pipeline settings, used to generate a parset file type: - - File? - - string? - doc: | - Optional strategy; either a name (e.g., "selfcal"), or a path to a python - strategy file (e.g., "/path/to/my_fancy_strategy.py") + type: record + fields: + - name: global + type: Any + - name: calibration + type: Any + - name: imaging + type: Any + - name: cluster + type: Any +- id: virtualenv + type: Any outputs: -- id: images - type: Directory[] - outputSource: - - run_rapthor/images -- id: logs - type: Directory[] - outputSource: - - run_rapthor/logs - id: parset type: File outputSource: - - run_rapthor/parset -- id: plots - type: Directory[] - outputSource: - - run_rapthor/plots -- id: regions - type: Directory[] - outputSource: - - run_rapthor/regions -- id: skymodels - type: Directory[] - outputSource: - - run_rapthor/skymodels -- id: solutions - type: Directory[] + run_rapthor/parset +- id: tarballs + type: File[] outputSource: - - run_rapthor/solutions + tar_directory/tarball steps: # fetch MS files @@ -94,16 +74,12 @@ steps: Run the Rapthor pipeline in a virtual environment that is created on the fly in: + - id: msin + source: concat_ms/msout - id: settings source: settings - - id: ms - source: concat_ms/msout - - id: skymodel - source: skymodel - - id: apparent_sky - source: apparent_skymodel - - id: strategy - source: strategy + - id: virtualenv + source: virtualenv out: - id: images - id: logs @@ -113,3 +89,22 @@ steps: - id: skymodels - id: solutions run: run_rapthor.cwl + +- id: tar_directory + label: Tar the pipeline results + doc: | + Create tar-balls for each of the output directories produced by Rapthor. + in: + - id: directory + linkMerge: merge_flattened + source: + - run_rapthor/images + - run_rapthor/logs + - run_rapthor/plots + - run_rapthor/regions + - run_rapthor/skymodels + - run_rapthor/solutions + out: + - id: tarball + run: tar_directory.cwl + scatter: directory diff --git a/rapthor/pipeline/execution/run_rapthor.cwl b/rapthor/pipeline/execution/run_rapthor.cwl index d538ee7934c6ca7eebf2d6c7d1df367b55813f07..6658bb9d20d313d84b4311361e545aa9f07d51fb 100644 --- a/rapthor/pipeline/execution/run_rapthor.cwl +++ b/rapthor/pipeline/execution/run_rapthor.cwl @@ -12,32 +12,30 @@ doc: | are put in the parset file need to be the same as the paths used when Rapthor is actually being run. -# Input should be a JSON file with the correct pipeline settings. The contents of -# this file will be converted to a valid Rapthor parset file. inputs: -- id: settings - type: File - doc: File containing the settings for the Rapthor pipeline in JSON format -- id: ms +- id: msin type: Directory doc: | Input Measurement Set In principle, Rapthor can handle multiple input Measurement Sets with data from different epochs containing the same frequency bands (e.g., from multiple nights of observations). This CWL step does _not_ support this. -- id: skymodel - type: File? - doc: Optional input sky model -- id: apparent_sky - type: File? - doc: Optional apparent sky model -- id: strategy +- id: settings + doc: Pipeline settings, used to generate a parset file type: - - File? - - string? - doc: | - Optional strategy; either a name (e.g., "selfcal"), or a path to a python - strategy file (e.g., "/path/to/my_fancy_strategy.py") + type: record + fields: + - name: global + type: Any + - name: calibration + type: Any + - name: imaging + type: Any + - name: cluster + type: Any +- id: virtualenv + doc: Description of the virtual environment used to run Rapthor. + type: Any # Anything that the Rapthor pipeline will produce as outputs. outputs: @@ -76,59 +74,36 @@ baseCommand: requirements: - class: InlineJavascriptRequirement + expressionLib: + - { $include: utils.js} - class: InitialWorkDirRequirement listing: - - entryname: json2ini.jq + - entryname: rapthor.parset + writable: True entry: | - # Convert JSON to INI using `jq` - # See: https://stackoverflow.com/a/50902853 - def kv: to_entries[] | "\(.key)=\(.value)"; - if type == "array" then .[] else . end - | to_entries[] - | "[\(.key)]", (.value|kv) + ${ + var settings = inputs.settings; + settings.global.dir_working = runtime.outdir; + settings.global.input_ms = inputs.msin.path; + var result = ""; + ["global", "calibration", "imaging", "cluster"].forEach(element => { + result += objectToParsetString(settings[element], element) + "\n\n" + }); + return result; + } - entryname: runner.sh entry: | #!/bin/bash set -ex - # Compose a filter to update the relevant items in the input JSON file. - # NOTE: the JavaScript expressions are *always* evaluated. Hence, the - # extra check in the `then`-clause. Also note that `.global.strategy` - # can either be a string or a File, hence the ternary expression. - filter='.config - | .global.dir_working |= "$(runtime.outdir)" - | .global.input_ms |= "$(inputs.ms.path)" - | .global.input_skymodel |= - if $(inputs.skymodel != null) - then "$(inputs.skymodel && inputs.skymodel.path)" - else empty - end - | .global.apparent_skymodel |= - if $(inputs.apparent_sky != null) - then "$(inputs.apparent_sky && inputs.apparent_sky.path)" - else empty - end - | .global.strategy |= - if $(inputs.strategy != null) - then "$(inputs.strategy && inputs.strategy.path - ? inputs.strategy.path - : inputs.strategy)" - else empty - end - ' - - # Read input JSON file, update paths, and write to INI (parset) file - jq -c "\${filter}" "$(inputs.settings.path)" | \ - jq -rf json2ini.jq > rapthor.parset - # Create virtual environment and activate it. - python_version=`jq -r .virtualenv.python.version "$(inputs.settings.path)"` + python_version="$(inputs.virtualenv.python.version)" virtualenv --python="python\${python_version}" venv . venv/bin/activate # Install rapthor - rapthor_version=`jq -r .virtualenv.rapthor.version "$(inputs.settings.path)"` + rapthor_version="$(inputs.virtualenv.rapthor.version)" pip install --no-cache-dir \ git+https://git.astron.nl/RD/rapthor.git@\${rapthor_version} diff --git a/rapthor/pipeline/execution/tar_directory.cwl b/rapthor/pipeline/execution/tar_directory.cwl new file mode 100644 index 0000000000000000000000000000000000000000..267ef83862a32b2e142e62577fab551cc5ad26ae --- /dev/null +++ b/rapthor/pipeline/execution/tar_directory.cwl @@ -0,0 +1,30 @@ +id: tar_directory +label: Tar directory contents +class: CommandLineTool +cwlVersion: v1.2 + +doc: | + Create a tar-ball of the contents of the input directory. + +inputs: +- id: directory + type: Directory + +outputs: +- id: tarball + type: File + outputBinding: + glob: $(inputs.directory.basename).tar.gz + +baseCommand: + - tar + +arguments: + - --create + - --dereference + - --gzip + - prefix: --directory + valueFrom: $(inputs.directory.dirname) + - prefix: --file + valueFrom: $(inputs.directory.basename).tar.gz + - valueFrom: $(inputs.directory.basename) diff --git a/rapthor/pipeline/execution/utils.js b/rapthor/pipeline/execution/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..f9fe6ffd898f87b1e08afad5932dd6d8cfae4cc2 --- /dev/null +++ b/rapthor/pipeline/execution/utils.js @@ -0,0 +1,26 @@ +function objectToParsetString(obj, section) { + var result = "[" + section + "]" + for(var item in obj) { + var value = obj[item] + var vType = typeof value + if(value === null) continue + try{ + if (vType === 'object' && !Array.isArray(value) && value.hasOwnProperty('path')) { + value = value.path + } else if(vType === 'object' && Array.isArray(value) ) { + value = "[" + value.join(',') + "]" + } + else if(vType === 'object' && !value.hasOwnProperty('path')) { + throw "Unsupported type" + } else { + value = String(value) + } + + result += "\n" + item + "=" + value + } catch(e) { + console.error(item, value, e) + throw e + } + } + return result +}