Skip to content

Contribute

Bug reports, questions, and documentation fixes are welcome at github.com/nirmallab/scimappro/issues.

Read the license before you fork

The Scimappro Academic License does not permit distributing a modified copy — that includes publishing a public fork. You may patch your own copy for internal use, and you are encouraged to send those patches upstream instead of carrying them. Contributions are accepted under the project's license.

Development setup

git clone https://github.com/nirmallab/scimappro.git
cd scimappro
uv sync --all-extras
uv run pytest

On a cloud-synced folder, build the environment outside the tree:

UV_PROJECT_ENVIRONMENT=~/.venvs/scimappro uv sync --all-extras

Test against the other supported interpreters with uv sync --all-extras -p 3.13 and -p 3.14.

API conventions

These are enforced by tests in tests/test_ported_api_surface.py and tests/test_spatialdata_support.py, not just by review.

  • Public names are camelCase. spatialDistance, not spatial_distance; imageId, not imageid. The exception is where a third-party API already fixes the spelling.
  • The first argument is data. Never adata, and never accept adata= as an alias in Python. It accepts an AnnData, an .h5ad path, a SpatialData, or a .zarr store.
  • Never name a local variable data — it shadows the parameter. Use plotData, matrix, coordsDf, subsetAdata. There is a test for this.
  • Route every input through scimappro/_data.py. No inline isinstance(data, str) branches:
from scimappro._data import finalizeData, openStream, resolveData

resolved = resolveData(data, sdataTable=sdataTable, streamData=streamData,
                       verbose=verbose, imageId=imageId,
                       xCoordinate=xCoordinate, yCoordinate=yCoordinate)
if resolved.streaming:
    with openStream(resolved, edit=True) as capAdata:
        ...
    return None
adata = resolved.adata
...
return finalizeData(resolved, adata, outputDir, verbose)

Read-only consumers (plots, exporters, graph builders) call loadTable instead, or scimappro.pl._utils.load_data inside scimappro.pl. - sdataTable is the last named parameter, immediately before **kwargs, so functions with a required second positional argument keep working. - Import spatialdata lazily, inside functions, never at module level. - Standard parameters: outputDir=None, streamData=False, maxWorkers=None (defaulting to max(1, cpu_count() - 1)), verbose, label. - CLIs expose --data with --adata as an argparse alias (dest="data"), plus --sdataTable.

Docstring style

The API reference on this site is generated straight from docstrings by mkdocstrings, so the docstring is the documentation. Google style, with a module-level abstract:

"""
!!! abstract "Short Description"
    `sp.tl.spatialDistance` computes the average shortest distance between
    every pair of cell types, per image.

## Function
"""

and per function:

def spatialDistance(data, phenotype="phenotype", ..., sdataTable=None):
    """
    Compute the average distance between every pair of cell phenotypes.

    Parameters:
        data (AnnData | SpatialData | str, required):
            Cell table: an `AnnData`, a path to an `.h5ad` file, a
            `SpatialData`, or a path to a `.zarr` store.

        phenotype (str, optional):
            Column in `obs` holding the cell type labels.

        outputDir (str, optional):
            Directory to write the updated object to. With `None` the object is
            returned instead.

    Returns:
        adata (anndata.AnnData):
            The input with `uns[label]` set to the distance matrix. `None` in
            streaming mode, where the file on disk is updated.

    Example:
        ```python
        adata = sp.tl.spatialDistance(adata, phenotype="phenotype")
        ```
    """

Rules that keep the rendered pages consistent:

  • Document every parameter, including the universal ones (data, outputDir, streamData, sdataTable, maxWorkers, verbose, label).
  • Refer to functions as sp.tl.…, matching import scimappro as sp.
  • Blank line between parameters — mkdocstrings needs it to keep them separate.
  • Every public function gets an Example: block with runnable code.

Adding a function

  1. Write it following the conventions above.
  2. Add it to the subpackage's __init__.py.
  3. Add a four-line stub page under docs/api/<subpackage>/<name>.md:
---
hide:
  - toc
---

::: scimappro.tl.myFunction
  1. Add it to nav: in mkdocs.yml and to the table in docs/api/index.md.
  2. Add tests: the non-streaming path, the streaming path if it has one, stream-vs-memory equality of whatever slot it writes, SpatialData parity, and the error paths for missing layers/columns and invalid arguments.

Building the docs

uv sync --group docs
uv run mkdocs serve            # live reload at http://127.0.0.1:8000
uv run mkdocs build --strict   # what CI runs; fails on broken links

--strict is what gates a pull request, so a dead cross-reference or an orphaned nav entry fails the build rather than shipping.

The tutorial notebooks

mkdocs-jupyter renders the notebooks in docs/tutorials/nbs/ as committedexecute: false, so CI never runs them. That keeps the docs build fast and deterministic, and it means the outputs on the site are whatever is in git.

So execute them yourself before committing, against the demo data in example_data/:

uv run jupyter nbconvert --to notebook --execute --inplace \
    docs/tutorials/nbs/<name>.ipynb

Each notebook resolves the data directory in its first cell, and each one recomputes what it needs from adata_scimap.h5ad rather than depending on an earlier notebook having run.

Never hand-edit a notebook's outputs. If a cell cannot run headlessly — pl.image_viewer needs napari and a window, helpers.addROI_omero needs an OMERO export — put the code in a markdown fence with an admonition saying it is not executed, rather than pasting in output from somewhere else.

Documentation deploys automatically to https://pro.scimap.xyz from .github/workflows/docs.yml on every push to main.

Porting a function from scimap

The repo carries a skill file at skills/scimap-to-scimappro/SKILL.md with the full mapping tables and the streaming/efficiency patterns. In brief: keep the old numerical behaviour unless the maintainer explicitly accepts a new standard, snapshot new expectations under a distinct fixture name, and add a stream-vs-memory equality test for the field the function writes.