Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
- Documentation: a Getting started page (install for every interface, supported Python versions and
wheel platforms, and which interface to use), a Concepts and FAQ page (chains and their aliases and
groups, how the schemes' regions differ, what confidence measures and how to pick
min_confidence, prefix and postfix, the domain search and input rules), guides for the command line (input formats, every TSV column and JSON field, how--all-domainsshifts the columns, exit statuses), JavaScript and Rust, one Errors page for every interface, the TypeScript declarations as the JavaScript API reference, the per-scheme accuracy fromBENCHMARKS.tomlon the benchmarks page, and Citation and Contributing pages. CITATION.cff, so GitHub offers "Cite this repository", andCONTRIBUTING.mdwith the development setup and project layout that were in the README.- The documentation's demo accepts pasted FASTA, re-numbers when a setting changes, runs on Ctrl/Cmd+Enter, copies and downloads results as TSV or JSON, makes a shareable link, compares the schemes' regions per domain, explains how to read the result, fits a phone screen and shows keyboard focus on its chain and scheme buttons.
Changed
- The README is a quickstart for each interface; the details moved to the documentation site, where each topic now lives on one page. The site has a description for link previews, "edit this page" links, and no previous/next links on the Demo, Benchmarks and Contributing tabs, where they led to other tabs' pages.
- The crate's
documentationlink points to docs.rs.
Fixed
- Documentation: the Polars page's examples ran nothing but the error example; it now has a
runnable example with its real output. The Polars advice to find successes by a null
errorcounted null sequences as successes. The benchmarks page names the versions of the tools it compares and says how each plot's error bars are computed.
[2.0.0] - 2026-10-12
Highlights
- Multiple domains per sequence:
number_domainsandsegment_domainsin every interface, and--all-domainson the CLI, number and segment each variable domain, such as both domains of an scFv. - CLI: a new
immunum segmentcommand, and the CLI now ships with the Python package, souvx immunumorpip install immunumgets it without Rust. - Machine-readable errors: every failed result carries an
error_kind, and setup mistakes raiseimmunum.Error(Python) or anErrorwith akind(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 anAnnotatorthat 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_methodandsegmentation_methodpaired the numbering with the sequence from its first residue instead of fromquery_start, so every residue after a leader or other leading flank was shifted by the length of that flank (#53). segmentdropped the residues the aligner leaves out before and after the domain instead of putting them inprefixandpostfix, in Rust, Python, Polars and JavaScript. The regions now always join back into the input sequence, as documented (#58).- Python: an
Annotatorused from a thread other than the one that created it raised aPanicException("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-confidencehelp said sequences below it are skipped; they get an error record. - Python: unpickling a corrupt
AnnotatorraisedPanicException; it raisesValueError. - CLI: when the reader of its output stopped early, as in
immunum number seqs.fasta | head, the command printederror: write error: Broken pipeand exited 1. It now stops quietly with status 0. - Python wheels:
rand0.9.5 andxxhash-rust0.8.19, which Polars compiles in, fix two low-severity advisories. CI tests against Polars 2.0, whichimmunum[polars]installs; the lockfile had the yanked Polars 1.39.2. cargo install immunumalso installed the developer toolsbenchmarkanddebug-validation, which only work in a checkout of the repository. They now need the newdev-toolsfeature (cargo run --features dev-tools --bin benchmark), so onlyimmunumis installed, and theclifeature no longer pulls inchronoandtoml. The README says how to install the CLI.- The comparison benchmark (
task benchmark,task benchmark-accuracy) read the Polarspositionsandresiduesfields this release replaces, so it failed; it readsnumbering. - 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 sequenceinvalid_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 bytask 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 thanMIN_SEQUENCE_LENGTHor aligns with too little confidence. Each domain numbers likeAnnotator::numberon 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 thanMIN_SEQUENCE_LENGTHis 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 likeAnnotator::number, including AHo's light-chain position 149. ADomain'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_domainsandsegment_domainsin Rust (Annotator), Python (Annotator) and Polars,numberDomainsandsegmentDomainsin JavaScript, and--all-domainson the CLI'snumberandsegment. Each domain's result is whatnumberorsegmentreturns for that domain, in sequence order. The list is never empty: a sequence without a domain or an invalid one gives a single result witherrorset, asnumberandsegmentdo, and the CLI writes one error record for it. segment_domainsputs every residue in exactly one domain's regions: a domain'sprefixholds the residues since the previous domain (or the start of the sequence), and only the last domain has the residues after it as itspostfix, so all domains' regions in order rebuild the sequence.- CLI:
immunum segmentsplits 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 asnumber. With--all-domains, both commands add a 0-baseddomaincolumn (TSV) or field (JSON). - Polars: every expression (
number,number_domains,segment,segment_domains) takes eitherchains,schemeandmin_confidence, or a prebuiltAnnotatorasannotator=. - Rust:
NumberedRecordand the newSegmentedRecordhave adomainfield andin_domain, andOutputFormat::write_headerandwrite_recordtake anall_domainsswitch, withwrite_segment_headerandwrite_segment_recordfor segments. - Rust:
NumberingResult::residues(sequence)pairs each numbered position with its residue, andNumberingResult::segment(sequence)splits a numbering into FR/CDR regions, flanks included, without numbering again.sequenceis 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_NAMESlists the segments in sequence order by the names every interface uses, built fromRegion::name()and the newnumbering::PREFIXandnumbering::POSTFIX, andSegmentResult::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_startandquery_end, as Python results do (queryStartandqueryEndin JavaScript). - Python and JavaScript expose the same scheme lookups:
scheme_supports_chain(scheme, chain)(schemeSupportsChainin JavaScript) tells whether a scheme numbers a chain, by the rule theAnnotatorconstructor applies, and JavaScript gainsregionsFor(scheme, chain), which Python has asregions_for. Both take names and report unknown ones as every interface does; they areimmunum::scheme_supports_chainandnumbering::region_spansin Rust, withScheme::supports(chain)andRegion::name(). The documentation's web tool usesschemeSupportsChainto enable its chain checkboxes instead of keeping its own copy of the rule. - Rust:
per_domainturns whatnumber_domainsandsegment_domainsreturn 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_kindnext toerror(errorKindin JavaScript), so failed rows can be filtered without matching messages:invalid_sequence,low_confidenceor, from the*_domainsmethods 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'sNumberingResultandSegmentationResult, of the Polars structs and of CLI JSON records, and a column of CLI TSV output. - Python:
immunum.Error, aValueErrorsubclass raised for every setup mistake, with akind:invalid_chain,invalid_scheme,unsupported_chainorinvalid_min_confidence. JavaScript throwsErrorobjects with the samekind(ImmunumErrorin the TypeScript types). - CLI: when any record has an error, a one-line summary goes to stderr.
- The Python package installs the
immunumcommand, souvx immunum number seqs.fastaruns the CLI without Rust or a separate install, andpip install immunumputs it on the path. It is the same CLI ascargo install immunum, now in the library asimmunum::cli::run; Ctrl-C stops it as it stops the native binary. - CLI:
immunum --versionprints the version. - Python: the
polarsextra,pip install "immunum[polars]", installs Polars forimmunum.polars. - Python: wheels for Windows x64 and Intel macOS, besides Linux and Apple Silicon, so
pip installanduvx immunumthere no longer compile from source. Python 3.14 and 3.15 are supported:piprefused them, as the package declaredrequires-pythonbelow 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 asimmunum._internal.__version__. JavaScript:version()returns the version of immunum the module was built from, asimmunum.__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_kindandkindnow carry, and read the same in every interface:Invalid sequence: sequence length 4 is below minimum 30is nowsequence length 4 is below minimum 30,Low confidence: 0.0416 < threshold 0.5000is nowalignment confidence 0.0416 is below min_confidence 0.5000, andInvalid chain type: unknown chain 'IGX' …is nowunknown chain 'IGX' …. A scheme asked for a chain it doesn't number names the chain. - Breaking: JavaScript throws
Errorobjects withmessageandkindinstead of plain strings. - Breaking: CLI TSV output has an
error_kindcolumn aftererror, and JSON records anerror_kindfield. - Breaking: Rust errors are split by how they're reported.
immunum::Erroris a setup mistake, raised by every interface:InvalidChain,InvalidScheme, the newUnsupportedChain(a scheme asked for a chain it has no rules for, which wasInvalidScheme),InvalidMinConfidence,InvalidPositionand the newWrongSequence(a numbering paired with another sequence, which wasInvalidSequence).immunum::SequenceErroris one sequence that couldn't be numbered, returned by every interface:InvalidSequence,LowConfidenceand the newDomainTooShort.Annotator::number,segment,domains,number_domainsandsegment_domainsreturnResult<_, SequenceError>, anddomainsis an error instead of an empty list when there is no domain. Both enums are#[non_exhaustive]and havekind().immunum::Resulttakes the error type as a defaulted second parameter.AlignmentError,ConsensusParseError,PositionMappingErrorandIoare gone: none could happen through the library.ScoringMatrix::loadreturns the matrix itself, and the validation functions return a boxedValidationError. - Breaking: Rust
NumberedRecordandSegmentedRecordholdresult: Result<_, SequenceError>and are built withnew, instead ofsuccess/failureand separateresult/erroroptions. - Breaking: Python's
SegmenationResultis renamedSegmentationResult, fixing its spelling.Annotator.segmentandsegment_domainsreturn it as before; only code naming the class changes. - Breaking: Python's
SegmentationResultdeclares its regions in sequence order,prefixfirst andpostfixlast, as every other interface does, soas_dict()joined in order rebuilds the sequence.prefixandpostfixcame afterfr4. - Breaking: JavaScript uses camelCase throughout, as JavaScript and TypeScript code expects:
numberresults havequeryStartandqueryEndinstead ofquery_startandquery_end, and the constructor's third parameter is declared asminConfidence. Every other field name is the same as in Python, Polars and the CLI. - Breaking: JavaScript
segmentandsegmentDomainsresults that failed set every region tonull, as Python, Polars and the CLI do, instead of leaving the region fields out. - Breaking: Polars
numberandnumbering_methodreturn the same struct, with the fields Python'sAnnotator.numberreturns:chain,scheme,confidence,numbering,query_start,query_end,erroranderror_kind.numberingis a list of{position, residue}structs; explode and unnest it for one row per residue.numberreturnedpositionsandresidueslists instead ofnumbering, and had noconfidence(#38); neither hadquery_startorquery_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,tcrandall, 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_confidenceoutside[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,schemeandmin_confidenceor fromannotator=, so an unknown name raisesimmunum.Error(aValueError) right there. Both forms run through the same plugin function. - Rust:
ChainandSchemeparse errors areimmunum::Errornaming the accepted values, instead ofstrum::ParseError. New:Chain::parse_namesandAnnotator::from_names. - Rust:
AnnotatorisSend + 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::numberandAnnotator::segmentare built on the pieces above; apart from the flanks fixed above, their results are unchanged.
Deprecated
- Polars
numbering_methodandsegmentation_method. Usenumber(expr, annotator=annotator)andsegment(expr, annotator=annotator), which return the same.
Removed
- Rust:
Chain::parse_chain_spec. UseChain::parse_names(spec.split(',')).
[1.3.3] - 2026-10-01
Fixed
- A sequence ending exactly one residue past a domain's last framework position no longer gets that
residue numbered as part of the domain. A heavy chain + one residue numbered it as an insertion after
the last position (IMGT
128A, Kabat113A), and a kappa or lambda chain + one residue numbered it at IMGT 128, a position no light chain has. In both cases it also extendedquery_endand FR4. Two or more trailing residues were already left out. Numbering now matches ANARCI, including AHo's position 149 after a light chain. - The documentation release build failed because
task docs-build -- --strictforwarded--strictthrough thebuild-wasm-webdependency intocargo build. The wasm-pack task no longer takesCLI_ARGS.
Changed
- The TRB validation fixture no longer expects a residue at IMGT 128. The residue after a TRB J-region is the first residue of the constant region; TRB germline J genes and ANARCI end at 127.
[1.3.2] - 2026-09-29
Added
- The documentation footer now shows the version it was built from, linking to the matching GitHub release.
Changed
- The release workflow builds the documentation site and publishes it to S3
[1.3.1] - 2026-09-02
Added
regions_for(scheme, chain)in Python, returning a scheme's FR/CDR boundaries as{region: (start, end)}with both ends inclusive and keyed by the lowercase region namessegment()uses. These are the same tables numbering assigns residues to, now readable without numbering a sequence. RaisesValueErrorfor an unknown scheme or chain, and for a pair immunum cannot number (Kabat, Chothia, Martin and AHo on TCR chains).- Rust:
RegionDefinition::spans()reads the stored region ends back as inclusive(start, end)pairs, andScheme::validate_chain()is the scheme/chain checkAnnotator::newpreviously did inline.
[1.3.0] - 2026-08-28
Added
- Chothia, Martin (extended Chothia) and AHo numbering schemes for antibody chains (IGH, IGK, IGL),
each derived from the internal IMGT numbering like Kabat. Select them as
chothia/c,martin/m,aho/afrom the CLI (-s), Python (Annotator(..., scheme=...), including the Polars plugin), WASM and Rust (Scheme::Chothia,Scheme::Martin,Scheme::Aho). Per-residue agreement against ANARCI-numbered reference sets is 99.98–100% for all three (seeBENCHMARKS.toml); TCR chains remain IMGT-only. - Validation fixtures for the new schemes:
fixtures/validation/ab_{H,K,L}_{chothia,martin,aho}.csv.
Changed
- Segmentation breaking change for Kabat: FR/CDR boundaries are now scheme and chain specific
instead of one shared table, so
segment()returns different region strings under Kabat. Heavy chains use CDR-H1 31–35 (plus 35A/35B), CDR-H2 50–65, CDR-H3 95–102; light chains use CDR-L1 24–34, CDR-L2 50–56, CDR-L3 89–97 with FR4 ending at 107. The previous table applied CDR1 26–35, CDR2 51–57, CDR3 93–100 to both chains. Position numbers are unchanged; only the region a residue is assigned to changes. IMGT segmentation is unaffected. - The CDR-center insertion penalty dropped from -3.0 to -2.0. Ultralong CDR3 domains (e.g. bovine
VHH-like heavy chains absorbing ~48 residues at IMGT 111/112) are no longer suffix-clipped before
FR4, so
query_endand the numbering now cover the full domain. Raised AHo heavy per-residue accuracy from 99.87% to 100% and IMGT TRB from 99.79% to 99.80%. - AHo light chains now emit the trailing position 149 when a residue follows AHo 148, matching
ANARCI's
number_ahotail rule. Adds one residue to the numbered range for affected sequences. - Rust API breaking change:
numbering::segmenttakes an extrachain: Chainargument, andSchemegained three variants, so exhaustivematcharms onSchemein downstream code need updating.
[1.2.0] - 2026-08-04
Fixed
- Kabat numbering no longer panics when a CDR aligns to a single residue. Heavy CDR1, heavy CDR3 and light CDR2 each had a
deletion_ordershorter thanbase_len - 1, so deleting every base position but one indexed past the end of the slice. The orders now follow ANARCI, which leaves position 23 for heavy CDR1 and 94 for heavy CDR3. Affected roughly 1 in 20 000 truncated but productive VHH reads, all of which numbered correctly under IMGT.
[1.1.2] - 2026-05-21
Added
py.typedmarker so downstream type checkers (mypy, pyright) pick up inline annotations and the bundled_internal.pyistub per PEP 561.
[1.1.1] - 2026-05-18
Changed
- Increased maximum allowed input sequence length to 10000.
Fixed
- Updated packages to address security vulnerabilities.
[1.1.0] - 2026-04-09
Added
- Interactive WASM numbering tool and demo page with UI for confidence settings.
- Sequence validation with length constraints and character checks.
- Error handling in numbering and segmentation processes.
- Logo in README and docs.
Changed
- WASM breaking change: the
Numberingoutput type is nowMap<string, string>instead ofRecord<string, string>. Access residues withnumbering.get("112A")instead ofnumbering["112A"], and iterate withfor (const [pos, aa] of numbering)ornumbering.entries()instead ofObject.entries(numbering). This preserves insertion-code ordering (e.g."111A"/"112A"stay between"111"and"112"). number()result now exposesquery_start/query_endas inclusive 0-indexed integers marking the aligned region of the input sequence (bothnullon error).
Fixed
- Clearer error message on numbering failure.
- macOS CI build.
[1.0.0] - Prior release
See the GitHub releases page for history prior to 1.1.0.