Skip to content

Release v2.0.0

Latest

Choose a tag to compare

@github-actions github-actions released this 12 Oct 14:29
372e442

immunum v2.0.0

What's changed

Highlights

  • Multiple domains per sequence: number_domains and segment_domains in every interface, and
    --all-domains on the CLI, number and segment each variable domain, such as both domains of an scFv.
  • CLI: a new immunum segment command, and the CLI now ships with the Python package, so
    uvx immunum or pip install immunum gets it without Rust.
  • Machine-readable errors: every failed result carries an error_kind, and setup mistakes raise
    immunum.Error (Python) or an Error with a kind (JavaScript).
  • Polars: results have the same fields as Python's, and every expression accepts a prebuilt
    Annotator.
  • Python: wheels for Windows x64 and Intel macOS, support for Python 3.14 and 3.15, an
    immunum[polars] extra, and an Annotator that can be shared across threads.
  • Docs: the demo page numbers every domain in a sequence, and a new "Upgrading to 2.0" guide covers
    the breaking changes.

Fixed

  • Polars: number, segment, numbering_method and segmentation_method paired the numbering with the
    sequence from its first residue instead of from query_start, so every residue after a leader or other
    leading flank was shifted by the length of that flank (#53).
  • segment dropped the residues the aligner leaves out before and after the domain instead of putting
    them in prefix and postfix, in Rust, Python, Polars and JavaScript. The regions now always join
    back into the input sequence, as documented (#58).
  • Python: an Annotator used from a thread other than the one that created it raised a PanicException
    ("unsendable, but sent to another thread"). It can now be shared across threads.
  • The documentation's web tool upper-cased the sequence before numbering it, so it showed residues the
    user hadn't entered; it now numbers the sequence as given, like every other interface. It still drops
    whitespace from the text box, so a wrapped or spaced sequence can be pasted.
  • CLI: an argument that wasn't an existing file was read as a sequence, so a mistyped file name gave one
    invalid-sequence record and exit status 0. An argument is now read as a sequence only when it is all
    letters; otherwise a missing file is an error (exit status 1).
  • CLI: the --min-confidence help said sequences below it are skipped; they get an error record.
  • Python: unpickling a corrupt Annotator raised PanicException; it raises ValueError.
  • CLI: when the reader of its output stopped early, as in immunum number seqs.fasta | head, the
    command printed error: write error: Broken pipe and exited 1. It now stops quietly with status 0.
  • Python wheels: rand 0.9.5 and xxhash-rust 0.8.19, which Polars compiles in, fix two
    low-severity advisories. CI tests against Polars 2.0, which immunum[polars] installs; the lockfile
    had the yanked Polars 1.39.2.
  • cargo install immunum also installed the developer tools benchmark and debug-validation,
    which only work in a checkout of the repository. They now need the new dev-tools feature
    (cargo run --features dev-tools --bin benchmark), so only immunum is installed, and the cli
    feature no longer pulls in chrono and toml. The README says how to install the CLI.
  • The comparison benchmark (task benchmark, task benchmark-accuracy) read the Polars positions
    and residues fields this release replaces, so it failed; it reads numbering.
  • Documentation: corrected statements that no longer held, such as the Rust install line ("1.3"),
    the npm package working in browsers (it is built for Node.js), every scheme placing CDRs
    differently on heavy and light chains (only Kabat, Chothia and Martin do), and what makes a
    sequence invalid_sequence. The Python API reference renders return values as one entry, no
    longer lists private members, and describes every result field.
  • Documentation site: a missing page showed the storage bucket's raw error instead of the site's 404
    page; the license badge and the contents links to the JavaScript and Rust sections went nowhere;
    and wasm-pack's copy of the README was published as a duplicate "Index" page. task docs-build,
    run by task ci, now fails on broken links.
  • The docs are published by their own workflow, which the release runs and which can be run by hand
    for a release tag, so a docs fix no longer needs a new version. The release publishes them after
    the packages.

Added

  • Rust: Annotator::domains(sequence) finds every variable domain in a sequence, ordered by position.
    It keeps the best alignment's domain, then searches the residues before and after it the same way, each
    on its own, until a part is shorter than MIN_SEQUENCE_LENGTH or aligns with too little confidence.
    Each domain numbers like Annotator::number on the residues it was found in, so a domain missing its
    N-terminal residues after another domain is found as it would be at the start of a sequence. A domain
    shorter than MIN_SEQUENCE_LENGTH is not reported, but the residues around it are still searched.
  • Rust: Domain::number(scheme) numbers a found domain under any scheme from the alignment that found it,
    without aligning again. The first domain of a single-domain sequence numbers exactly like
    Annotator::number, including AHo's light-chain position 149. A Domain's span, chain and confidence
    are read through methods (query_start(), chain(), ...), so they can't drift from its alignment.
  • Every interface numbers and segments every variable domain in a sequence, such as both domains of an
    scFv: number_domains and segment_domains in Rust (Annotator), Python (Annotator) and Polars,
    numberDomains and segmentDomains in JavaScript, and --all-domains on the CLI's number and
    segment. Each domain's result is what number or segment returns for that domain, in sequence
    order. The list is never empty: a sequence without a domain or an invalid one gives a single result
    with error set, as number and segment do, and the CLI writes one error record for it.
  • segment_domains puts every residue in exactly one domain's regions: a domain's prefix holds the
    residues since the previous domain (or the start of the sequence), and only the last domain has the
    residues after it as its postfix, so all domains' regions in order rebuild the sequence.
  • CLI: immunum segment splits sequences into FR/CDR regions, one record per sequence (or per domain
    with --all-domains) with a column or field per region. It takes the same options as number. With
    --all-domains, both commands add a 0-based domain column (TSV) or field (JSON).
  • Polars: every expression (number, number_domains, segment, segment_domains) takes either
    chains, scheme and min_confidence, or a prebuilt Annotator as annotator=.
  • Rust: NumberedRecord and the new SegmentedRecord have a domain field and in_domain, and
    OutputFormat::write_header and write_record take an all_domains switch, with
    write_segment_header and write_segment_record for segments.
  • Rust: NumberingResult::residues(sequence) pairs each numbered position with its residue, and
    NumberingResult::segment(sequence) splits a numbering into FR/CDR regions, flanks included, without
    numbering again. sequence is the whole sequence that was numbered or searched; one too short for the
    numbering is an error. Python, JavaScript, Polars and the CLI now all use these.
  • Rust: numbering::SEGMENT_NAMES lists the segments in sequence order by the names every interface
    uses, built from Region::name() and the new numbering::PREFIX and numbering::POSTFIX, and
    SegmentResult::regions() pairs each with its residues. Python, JavaScript, Polars and the
    documentation's web tool take their region names and order from these instead of their own lists.
  • CLI: JSON and JSONL records carry query_start and query_end, as Python results do (queryStart and
    queryEnd in JavaScript).
  • Python and JavaScript expose the same scheme lookups: scheme_supports_chain(scheme, chain)
    (schemeSupportsChain in JavaScript) tells whether a scheme numbers a chain, by the rule the
    Annotator constructor applies, and JavaScript gains regionsFor(scheme, chain), which Python has as
    regions_for. Both take names and report unknown ones as every interface does; they are
    immunum::scheme_supports_chain and numbering::region_spans in Rust, with Scheme::supports(chain)
    and Region::name(). The documentation's web tool uses schemeSupportsChain to enable its chain
    checkboxes instead of keeping its own copy of the rule.
  • Rust: per_domain turns what number_domains and segment_domains return into one result per
    domain, or the error as the single result. Python, JavaScript, Polars and the CLI all use it, so they
    report an invalid sequence the same way.
  • Every result that failed carries error_kind next to error (errorKind in JavaScript), so failed
    rows can be filtered without matching messages: invalid_sequence, low_confidence or, from the
    *_domains methods only, domain_too_short (the best alignment is confident but shorter than a
    domain must be, so there is no domain to report). It is a field of Python's NumberingResult and
    SegmentationResult, of the Polars structs and of CLI JSON records, and a column of CLI TSV output.
  • Python: immunum.Error, a ValueError subclass raised for every setup mistake, with a kind:
    invalid_chain, invalid_scheme, unsupported_chain or invalid_min_confidence. JavaScript throws
    Error objects with the same kind (ImmunumError in the TypeScript types).
  • CLI: when any record has an error, a one-line summary goes to stderr.
  • The Python package installs the immunum command, so uvx immunum number seqs.fasta runs the CLI
    without Rust or a separate install, and pip install immunum puts it on the path. It is the same
    CLI as cargo install immunum, now in the library as immunum::cli::run; Ctrl-C stops it as it
    stops the native binary.
  • CLI: immunum --version prints the version.
  • Python: the polars extra, pip install "immunum[polars]", installs Polars for immunum.polars.
  • Python: wheels for Windows x64 and Intel macOS, besides Linux and Apple Silicon, so pip install
    and uvx immunum there no longer compile from source. Python 3.14 and 3.15 are supported: pip
    refused them, as the package declared requires-python below 3.14. CI tests the oldest and the
    newest supported Python, macOS and Windows.
  • Documentation: an "Upgrading to 2.0" guide with what can break in each interface, and the
    changelog as a page. The benchmarks page says which immunum versions its plots come from.
  • Python: immunum.__version__, the installed version, which was only reachable as
    immunum._internal.__version__. JavaScript: version() returns the version of immunum the module
    was built from, as immunum.__version__ does in Python.
  • The documentation's demo page numbers and segments every domain in a sequence, with an scFv
    example. It colours the whole sequence by region, with flanks and linkers dimmed, and shows each
    domain's chain, query range, confidence, FR/CDR segments and numbering. It shows the immunum
    version it runs, read from the loaded WebAssembly module.

Changed

  • The crate declares rust-version = "1.89", the oldest Rust that builds every feature, so an older
    toolchain is told what it needs instead of failing in a dependency.
  • Breaking: error messages drop their category prefix, which error_kind and kind now carry, and
    read the same in every interface: Invalid sequence: sequence length 4 is below minimum 30 is now
    sequence length 4 is below minimum 30, Low confidence: 0.0416 < threshold 0.5000 is now
    alignment confidence 0.0416 is below min_confidence 0.5000, and Invalid chain type: unknown chain 'IGX' … is now unknown chain 'IGX' …. A scheme asked for a chain it doesn't number names the chain.
  • Breaking: JavaScript throws Error objects with message and kind instead of plain strings.
  • Breaking: CLI TSV output has an error_kind column after error, and JSON records an
    error_kind field.
  • Breaking: Rust errors are split by how they're reported. immunum::Error is a setup mistake,
    raised by every interface: InvalidChain, InvalidScheme, the new UnsupportedChain (a scheme asked
    for a chain it has no rules for, which was InvalidScheme), InvalidMinConfidence, InvalidPosition
    and the new WrongSequence (a numbering paired with another sequence, which was InvalidSequence).
    immunum::SequenceError is one sequence that couldn't be numbered, returned by every interface:
    InvalidSequence, LowConfidence and the new DomainTooShort. Annotator::number, segment,
    domains, number_domains and segment_domains return Result<_, SequenceError>, and domains is
    an error instead of an empty list when there is no domain. Both enums are #[non_exhaustive] and have
    kind(). immunum::Result takes the error type as a defaulted second parameter. AlignmentError,
    ConsensusParseError, PositionMappingError and Io are gone: none could happen through the
    library. ScoringMatrix::load returns the matrix itself, and the validation functions return a boxed
    ValidationError.
  • Breaking: Rust NumberedRecord and SegmentedRecord hold result: Result<_, SequenceError> and
    are built with new, instead of success/failure and separate result/error options.
  • Breaking: Python's SegmenationResult is renamed SegmentationResult, fixing its spelling.
    Annotator.segment and segment_domains return it as before; only code naming the class changes.
  • Breaking: Python's SegmentationResult declares its regions in sequence order, prefix first
    and postfix last, as every other interface does, so as_dict() joined in order rebuilds the
    sequence. prefix and postfix came after fr4.
  • Breaking: JavaScript uses camelCase throughout, as JavaScript and TypeScript code expects: number
    results have queryStart and queryEnd instead of query_start and query_end, and the
    constructor's third parameter is declared as minConfidence. Every other field name is the same as in
    Python, Polars and the CLI.
  • Breaking: JavaScript segment and segmentDomains results that failed set every region to null,
    as Python, Polars and the CLI do, instead of leaving the region fields out.
  • Breaking: Polars number and numbering_method return the same struct, with the fields Python's
    Annotator.number returns: chain, scheme, confidence, numbering, query_start,
    query_end, error and error_kind. numbering is a list of {position, residue} structs;
    explode and unnest it for one row per residue. number returned positions and residues lists
    instead of numbering, and had no confidence (#38); neither had query_start or query_end.
  • Chain and scheme names are parsed once, in Rust, for every interface. Python drops its own alias
    tables, so Python, JavaScript, Polars and the CLI accept the same names and report the same error
    message for an unknown one. The chain groups ig, tcr and all, which only the CLI accepted, now
    work everywhere; a chain named twice (e.g. ["ig", "H"]) is used once. An unknown chain or scheme
    name gets one message everywhere, listing every name the parser accepts (taken from the parser
    itself, the name results report first), plus the chain groups where groups are accepted.
  • min_confidence outside [0, 1] is rejected by every interface. Only Python checked it; JavaScript,
    Polars and the CLI accepted any value.
  • Polars builds the annotator when the expression is built, from chains, scheme and
    min_confidence or from annotator=, so an unknown name raises immunum.Error (a ValueError) right
    there. Both forms run through the same plugin function.
  • Rust: Chain and Scheme parse errors are immunum::Error naming the accepted values, instead of
    strum::ParseError. New: Chain::parse_names and Annotator::from_names.
  • Rust: Annotator is Send + Sync: its alignment buffer is per thread, so one annotator can serve many
    threads. A thread keeps at most about 650 KB of alignment buffer between calls, what a 1,000-residue
    sequence needs; a longer sequence's buffer is freed when its call returns. Annotator::number and
    Annotator::segment are built on the pieces above; apart from the flanks fixed above, their results
    are unchanged.

Deprecated

  • Polars numbering_method and segmentation_method. Use number(expr, annotator=annotator) and
    segment(expr, annotator=annotator), which return the same.

Removed

  • Rust: Chain::parse_chain_spec. Use Chain::parse_names(spec.split(',')).

Install

  • Python — pip install immunum==2.0.0, or pip install "immunum[polars]==2.0.0" for the Polars plugin
  • CLI — uvx immunum@2.0.0 --help, or cargo install immunum@2.0.0
  • JavaScript / WASM — npm install immunum@2.0.0
  • Rust — cargo add immunum@2.0.0

Build info

  • Python wheels: Python 3.10+ (abi3) — Linux x86_64 and aarch64 (manylinux), macOS Apple Silicon and Intel, Windows x64
  • WASM package: Node.js (CommonJS)
  • Rust crate: published to crates.io