Skip to content

Interoperability

Interoperability is about how your software talks to the rest of the world: which languages it is written in, which data formats it reads and writes, what platforms it runs on, and how reproducibly others can recreate your computational environment. The aim is machine-actionable, reusable information - both for humans coming to your project, and for tools (registries, CI systems, package managers) that need to do something useful with it.

Programming languages

List the languages actually used in the project, with version constraints where they matter. This includes more than the primary language: list any language used for non-trivial logic within the project.

A reasonable inventory covers:

  • The primary language of the package, library, or executable.
  • Workflow languages if any logic lives there: Snakemake, Nextflow, CWL, WDL.
  • Build / glue languages with non-trivial logic: Make, CMake, Bash scripts that orchestrate the pipeline.
  • Frontend / backend split, if applicable.
  • Compiled cores with language bindings - for example, a Rust core exposed through Python via PyO3, or a C++ core wrapped in R.

For each entry, capture:

  • Language name as commonly known (e.g., "Python", "Rust", "R").
  • Version constraint - "3.14.2", ">=3.11", ">=1.78,<1.85". Constraints belong in your project manifest (pyproject.toml, Cargo.toml, DESCRIPTION).
  • Role in the project - "primary library + CLI", "workflow definitions in workflows/", "glue scripts only, no scientific logic".

A real-world example:

Python >=3.11,<3.13 - primary package and CLI
Snakemake >=8 - workflow definitions
Bash - small helper scripts only, no scientific logic
Rust >=1.78 - performance-critical parser, exposed through Python bindings

In the Software Management Plan (SMP): the Programming languages list captures language, version constraint, and role.

Data formats

Listing formats is the easy part. The harder part is describing what your tool expects within those formats, because the format itself often does not capture all necessary structure or assumptions. CSV, JSON, XML, HDF5, and Parquet are containers, what makes data interoperable is the schema inside.

For each format the software reads or writes, capture:

  • Format name. Prefer controlled EDAM Format terms where available (the SMP integrates with EDAM - start typing and pick from the dropdown). Common entries: BAM, FASTQ, VCF, DICOM, NIfTI, BIDS, TSV, JSON, Parquet, NetCDF. Alternatively, you can choose a machine-readable IANA media type (MIME type, e.g., text/csv, application/json).
  • Format version / profile if it matters: VCF >=4.2, DICOM SR, BIDS 1.8.
  • Openness. Whether the format is an open, documented standard, or something closer to bespoke or proprietary - see below.
  • Schema or expected columns/fields, especially for generic containers. For a CSV, "columns: subject_id, age_at_scan, group" is the actual contract. Link to a schema file if one exists (JSON Schema, XSD, BIDS specification).
  • Constraints that matter for interoperability - e.g., "only ASCII filenames", "no compressed inputs", "must be sorted by chromosome".
  • An example file URL, ideally checked into the repository under examples/ or tests/data/. This is a format exemplar: a small file someone can compare their own data against. The fixture that reproduces a known-good test run is a different thing, recorded under Sample data and parameters.

Never commit real patient data, even as a test fixture. Use synthetic or thoroughly de-identified data, and have it reviewed before committing if in any doubt.

EDAM

EDAM is excellent for common bioinformatics formats and operations and increasingly used in clinical-data contexts. If your input is something exotic (a custom binary log from a piece of lab equipment, or a project-internal TSV layout), there will be no EDAM term. In that case, use a clear descriptive name and provide the schema. Do not force-fit an EDAM term that does not really apply.

Open, documented, bespoke, proprietary

A format is open when its specification is public and implementable without asking anyone's permission: VCF, NIfTI, Parquet, or CSV accompanied by a schema. It is proprietary when reading it requires closed-source tooling or a licence - most instrument vendor formats, and anything whose specification sits behind an NDA.

Between the two sits the bespoke format: yours, undocumented, and readable only by your own code. Bespoke is not the same as proprietary - nothing stops someone reverse-engineering it - but in practice it fails for the same reason. No other tool can consume it, and before long neither can you, once the person who designed it has moved on.

Where a format sits determines how much interoperability work you have already done for a downstream user:

Format What it costs someone else
Open standard Nothing. Their existing tools already read it.
Bespoke, documented A parser - which they can at least write.
Bespoke, undocumented Reverse engineering, or asking you.
Proprietary A licence, or a conversion step through vendor software.

At the boundaries of your software - what it accepts from the world and hands back to it - prefer open formats. Inside a pipeline a bespoke intermediate is perfectly fine and often faster, because it never leaves the pipeline. Where a proprietary format is unavoidable, and for instrument output it usually is, document the conversion path: which tool converts it, into what open format, and whether anything is lost on the way.

In the SMP: each format in the Data formats list carries an Open format question - Yes, Partially, or Proprietary / undocumented. "Partially" covers the middle cases: a bespoke format you have documented, or an open standard used with vendor extensions.

In the SMP: the Data formats list captures, for each format, its name (EDAM where possible), version constraint, whether it is an open standard, schema or constraints, and an example-file URL. The list does not itself say which formats are read and which are written - that is recorded per operation in the Functions list, where each function names its own inputs and outputs from this list. Skip the section entirely if the software does not consume or emit external data.

Functions / operations

For tools that expose multiple operations (a CLI with several subcommands, an API with several endpoints, a library with distinct entry points), the SMP can capture each operation with:

  • the input format it consumes;
  • the operation it performs (preferably an EDAM Operation term, e.g., Variant calling, Sequence alignment, Image segmentation);
  • the command or command-line fragment that triggers it;
  • the output format it produces;
  • a note clarifying anything unusual about the operation.

This is the same metadata that tools like bio.tools and Galaxy use to describe what software does.

In the SMP: the Functions list is only needed when the software exposes one or multiple distinct operations. It is also where input and output are distinguished: each function selects its inputs and outputs from the Data formats list above. If you skip this list, the plan records which formats the software handles but not which way they flow.

APIs and interfaces

If your software exposes interfaces beyond a single CLI, document them. Common interface types:

  • Command-line interface. Usable subcommands; flags; standard streams.
  • Language API - Python, R, JavaScript, Rust. Public functions and their stability guarantees.
  • REST or GraphQL API, with OpenAPI or GraphQL schema where possible.
  • Plugin system - extension points, plugin lifecycle, plugin discovery.
  • Web interface - for clinicians or end-users.
  • Database schema - if your software reads or writes a structured database, the schema is part of the interface.
  • File-based workflow interface - e.g., a Snakemake module that consumes and produces specific file layouts.

For each interface, state whether it is stable, experimental, or internal. Users who depend on an "experimental" API do so with that caveat; users who depend on an "internal" API have no guarantee of stability. These labels form the contract.

For REST APIs in particular, provide an OpenAPI specification, most modern frameworks (FastAPI, Spring Boot, Express + swagger-jsdoc) generate one from the code. The spec then drives client generation, documentation tools like Redoc and Swagger UI, and contract testing.

Cross-platform compatibility

State which operating systems and architectures the software officially supports. There is a real distinction worth respecting:

  • Supported - CI-tested, advertised in the README, bugs accepted and triaged.
  • Expected to work - used by maintainers in practice, but no CI; bugs may not be prioritized.
  • Untested - silence is the right answer; do not claim what you do not verify.

Pure-runtime languages (Python, R, Java, JavaScript without native dependencies) usually work on Linux, macOS, and Windows alike. Anything compiled, anything that calls system libraries, anything that depends on a specific filesystem semantics, anything that uses GPU acceleration, those have real platform constraints worth being explicit about.

For Level B/C, run CI on at least Linux, plus macOS. Windows is genuinely harder for many scientific stacks; "supported under WSL" is a common compromise.

In the SMP: the Cross-platform compatibility question is a multi-select. Tick only the platforms you actually support. "Linux" is the safe default; pick more deliberately.

Resource requirements

Describe typical and worst-case hardware resources required to run the software. Users planning compute allocations or deploying to clinical infrastructure need this.

A useful structure:

Memory     : ~1 GB typical, up to 16 GB for whole-genome inputs
Storage    : ~200 MB code; ~5 GB for a representative input dataset
Compute    : 1–8 CPU cores typical; scales linearly to 32 in batch mode
GPU        : NVIDIA, ≥ 12 GB VRAM, CUDA ≥ 11.8 (segmentation step only)
Wall clock : ~5 min/subject; ~3 h for a 100-subject cohort on 8 cores

If resource use depends strongly on input characteristics, name the driving factor (number of subjects, file size, image dimensions, model parameter count) rather than giving a single point estimate.

In the SMP: the Resource requirements field is free-text. Even a few lines following the structure above is useful.

Containerization and environment specification

Beyond your language-level dependency manifest (pyproject.toml, requirements.txt, environment.yml, Cargo.lock, renv.lock, etc., which live in the repository), three patterns of environment specification offer increasing levels of reproducibility:

  1. Lock-file plus README narrative. The manifest pins dependency versions; the README explains the supported platform, language version, and installation command. Acceptable for Level A and for smaller Level B projects.
  2. Container image - a Dockerfile or an Apptainer / Singularity recipe. The container is executable documentation: it captures the OS, system libraries, language runtime, and all dependencies in a form that anyone can run. This is the practical gold standard.
  3. Whole-environment specification. Bit-for-bit reproducibility, including system-library versions. Worth the cost for software that must produce the same numbers across machines, or for clinical-grade reproducibility.

Useful options:

  • Docker and Podman - general containers. Podman is daemonless and rootless by default, which matters in some institutional contexts.
  • Apptainer (formerly Singularity) - for HPC environments where Docker is not available.
  • Nix / Guix - rigorous whole-environment reproducibility, including system libraries.
  • Pixi and conda-lock - multi-platform environment locks, lightweight compared to full containers.

A container by itself is not automatically reproducible. Practical hygiene:

  • Pin base images and dependency versions. FROM python:3.11 will drift over time as the upstream Python image is rebuilt. FROM python:3.11.7-slim-bookworm is much closer to reproducible.
  • Document build commands in the README or developer documentation.
  • Publish tagged images rather than relying on latest.
  • Build for the right runtime. HPC clusters almost always need Apptainer images - you can build an Apptainer image from a Docker image with apptainer build my.sif docker://....

The Turing Way - Containers covers the trade-offs in more depth.

In the SMP: the Containerization question offers four states - Full environment spec, Dockerfile or equivalent, Lock-file only, No. Pick the strongest pattern that is genuinely in place.