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.
-
Anchor-point / distance-zone analysis of a latent dimension (
interscale.tl.anchors, plotted byinterscale.pl.anchor_plots):find_anchor_cellslocalises the foci a dimension is reading (per-sample tail quantile, then a spatial-coherence filter),anchor_signed_distancegives every cell a signed distance to the anchor border,anchor_zonescuts that intoanchor core/near/farat the local component's reach (local_reach_um=num_layersxradius), andprofile_by_distance/anchor_enrichmentprofile expression and composition against it. This is the evidence a global dimension carries a long-range interaction rather than a neighbourhood effect: every distance plot marks the local reach, past which no cell's neighbourhood extends. -
Gene-wise (per-entry) masking for the node-level reconstruction task, alongside the existing whole-cell masking, selected by
dataset.mask_strategy("node"|"gene"). Under"gene"a Bernoulli subset of(cell, gene)entries is blanked in every cell and the loss and every regression metric are restricted to those entries. Registered as thenode_reg_genemasktask forlegnini23andmelton25, withconfig_files/sweeps/mask_granularity_legnini.yamlrunning the granularity/rate ablation.
tl.masking.MASK_VALUEis now -1, was 0. Masked positions must be distinguishable from real measurements: ~62% of legnini23'slog1p_normentries are already exactly 0 and 648 of its cells are all-zero, so a zero fill made the corruption invisible under gene masking. Measured at gene rate 0.25 over 3 seeds, fill 0 scoredval_concordance_corr0.0696 ±0.0113 against -1's 0.0799 ±0.0046 — better on every seed, with 2.5x less run-to-run spread. A configurable fill (zero/sentinel/learned, including a trainable per-gene[MASK]token) was trialled and removed: the learnable token tied with the fixed -1 to three decimals, so the option carried no information. This also changes the cell-masking path, whose published runs used 0.dataset.pct_mask_nodesanddataset.pct_mask_genesare replaced by a singledataset.mask_percentage(andGraphAnnDataModule/BaseModuletake onemask_percentageargument).mask_strategyalready says whether a masked unit is a cell or an entry, so a second rate was always the inert half of a pair — and setting the wrong one silently produced a run with no masking.GraphAnnDataModule.mask_rateandBaseModule.mask_rateare gone with it. Every config usingpct_mask_nodesmust be renamed, including saved wandb sweeps.tl.masking.apply_masktakesmask_strategyexplicitly instead of inferring it from whether the batch carries agene_mask, andGlobalModule._process_batch_for_metricsgates on the strategy the same way. A stalegene_maskcan no longer hijack a cell-masking run, soGraphAnnDataModule._clear_gene_maskis removed.tl.masking.apply_maskandBaseModule._common_step_maskingnow return a third value, the entry mask; every_common_stepreturns a sixth value carrying it through to the training plan.GlobalModule._process_batch_for_metricsreturns it as a third value.DualDecoderCombinedModule.compute_separate_lossestakes it as an optional fifth argument.LocalModule._common_stepreturned a 4-tuple where the training plan unpacked 5; it now matches the 6-tuple contract of the other modules.requires-pythonis now>=3.12, was>=3.11, and the test matrix's lower rung moves with it. anndata, scanpy, squidpy and scvi-tools all require 3.12 already, so the 3.11 claim only told the resolver to back-solve to years-old releases of them on that interpreter — the "old and broken versions" of #59.- Dependency bounds were loosened (#59).
scanpyno longer carries a<1.13upper bound: the break was squidpy's, not scanpy's — squidpy <= 1.8.3 imports the privatescanpy.plotting._tools, which the scanpy 1.13 plotting rewrite moved underscanpy.plotting.legacy, soimport squidpy(and through itimport interscale) raisedModuleNotFoundErroron scanpy 1.13.0a2. squidpy's main branch already imports from either location, so[tool.uv.sources]takes squidpy from git until that fix is released, with the commit pinned inuv.lock.numpystays uncapped and resolves to 2.x; its>=1.19floor and scipy's>=1.7are gone, sincerequires-pythonalready excludes everything below their first cp312 wheels. torch-cluster,torch-scatterandtorch-sparseare no longer part of the resolution. They come fromgeome, which never imports them, and torch-geometric >= 2.3 covers what they provide; PyPI only carries their 2023 source distributions, which must be compiled against the installed torch.[[tool.uv.dependency-metadata]]restates geome's requirements without them, so the[tool.uv] extra-build-dependenciesblock and CI's torch-version-pinnedUV_FIND_LINKSPyG wheel index are both gone.pip install interscalestill pulls them in through geome —docs/installation.mdnow derives the PyG wheel index from the installed torch instead of naming a version.
initial release