alhena 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. alhena-0.1.0/.github/workflows/docs.yml +63 -0
  2. alhena-0.1.0/.github/workflows/release.yml +95 -0
  3. alhena-0.1.0/.github/workflows/tests.yml +143 -0
  4. alhena-0.1.0/.gitignore +17 -0
  5. alhena-0.1.0/CITATION.cff +48 -0
  6. alhena-0.1.0/CONTRIBUTING.md +41 -0
  7. alhena-0.1.0/LICENSE +29 -0
  8. alhena-0.1.0/PKG-INFO +200 -0
  9. alhena-0.1.0/README.md +149 -0
  10. alhena-0.1.0/ci/constraints-min-extras.txt +6 -0
  11. alhena-0.1.0/ci/constraints-min.txt +16 -0
  12. alhena-0.1.0/docs/api/annotate.md +26 -0
  13. alhena-0.1.0/docs/api/cells.md +50 -0
  14. alhena-0.1.0/docs/api/celltyper.md +38 -0
  15. alhena-0.1.0/docs/api/constants.md +78 -0
  16. alhena-0.1.0/docs/api/encoder.md +60 -0
  17. alhena-0.1.0/docs/api/errors.md +38 -0
  18. alhena-0.1.0/docs/api/index.md +38 -0
  19. alhena-0.1.0/docs/api/io.md +192 -0
  20. alhena-0.1.0/docs/api/metrics.md +32 -0
  21. alhena-0.1.0/docs/api/preprocess.md +42 -0
  22. alhena-0.1.0/docs/api/refbuild.md +47 -0
  23. alhena-0.1.0/docs/api/reference.md +32 -0
  24. alhena-0.1.0/docs/api/spatial.md +57 -0
  25. alhena-0.1.0/docs/api/types.md +59 -0
  26. alhena-0.1.0/docs/api/viz.md +45 -0
  27. alhena-0.1.0/docs/assets/alhena_banner.svg +4 -0
  28. alhena-0.1.0/docs/assets/alhena_lockup.svg +1 -0
  29. alhena-0.1.0/docs/assets/alhena_lockup_1600.png +0 -0
  30. alhena-0.1.0/docs/assets/alhena_lockup_reverse.svg +1 -0
  31. alhena-0.1.0/docs/assets/alhena_mark_512.png +0 -0
  32. alhena-0.1.0/docs/assets/figures/multiscale_overview.webp +0 -0
  33. alhena-0.1.0/docs/assets/figures/multiscale_preview.webp +0 -0
  34. alhena-0.1.0/docs/assets/figures/tutorial4_cell_space.webp +0 -0
  35. alhena-0.1.0/docs/assets/figures/tutorial4_cell_states.webp +0 -0
  36. alhena-0.1.0/docs/assets/figures/tutorial4_co_occurrence.webp +0 -0
  37. alhena-0.1.0/docs/assets/figures/tutorial4_cross_scale.webp +0 -0
  38. alhena-0.1.0/docs/assets/figures/tutorial4_physical_space.webp +0 -0
  39. alhena-0.1.0/docs/assets/figures/tutorial4_spatialdata.webp +0 -0
  40. alhena-0.1.0/docs/assets/figures/tutorial4_tile_gallery.webp +0 -0
  41. alhena-0.1.0/docs/assets/figures/tutorial4_tile_states.webp +0 -0
  42. alhena-0.1.0/docs/assets/figures/usecase_field.webp +0 -0
  43. alhena-0.1.0/docs/assets/figures/usecase_hero.webp +0 -0
  44. alhena-0.1.0/docs/assets/figures/usecase_neighborhood.webp +0 -0
  45. alhena-0.1.0/docs/assets/figures/usecase_tissue_contexts.webp +0 -0
  46. alhena-0.1.0/docs/assets/figures/usecase_triptych.webp +0 -0
  47. alhena-0.1.0/docs/cite.md +58 -0
  48. alhena-0.1.0/docs/cli.md +815 -0
  49. alhena-0.1.0/docs/concepts/cell-pooling.md +222 -0
  50. alhena-0.1.0/docs/concepts/cell-typing.md +376 -0
  51. alhena-0.1.0/docs/concepts/geometry.md +521 -0
  52. alhena-0.1.0/docs/concepts/how-it-works.md +214 -0
  53. alhena-0.1.0/docs/concepts/references.md +620 -0
  54. alhena-0.1.0/docs/faq.md +615 -0
  55. alhena-0.1.0/docs/index.md +119 -0
  56. alhena-0.1.0/docs/inputs/coordinates.md +300 -0
  57. alhena-0.1.0/docs/inputs/images.md +432 -0
  58. alhena-0.1.0/docs/inputs/index.md +113 -0
  59. alhena-0.1.0/docs/inputs/nuclei.md +400 -0
  60. alhena-0.1.0/docs/inputs/references.md +145 -0
  61. alhena-0.1.0/docs/installation.md +220 -0
  62. alhena-0.1.0/docs/outputs/cell-annotations.md +180 -0
  63. alhena-0.1.0/docs/outputs/cell-embeddings.md +188 -0
  64. alhena-0.1.0/docs/outputs/downstream-analysis.md +356 -0
  65. alhena-0.1.0/docs/outputs/index.md +99 -0
  66. alhena-0.1.0/docs/outputs/persistence.md +216 -0
  67. alhena-0.1.0/docs/outputs/tile-embeddings.md +218 -0
  68. alhena-0.1.0/docs/start/choose-your-task.md +168 -0
  69. alhena-0.1.0/docs/start/demo.ipynb +622 -0
  70. alhena-0.1.0/docs/start/getting-started.md +83 -0
  71. alhena-0.1.0/docs/stylesheets/extra.css +61 -0
  72. alhena-0.1.0/docs/tutorials/01_tile_embeddings.ipynb +573 -0
  73. alhena-0.1.0/docs/tutorials/02_cell_embeddings.ipynb +406 -0
  74. alhena-0.1.0/docs/tutorials/03_cell_annotation.ipynb +720 -0
  75. alhena-0.1.0/docs/tutorials/04_multiscale_analysis.ipynb +1135 -0
  76. alhena-0.1.0/docs/tutorials/05_custom_reference.ipynb +612 -0
  77. alhena-0.1.0/docs/tutorials/06_annotation_evaluation.ipynb +375 -0
  78. alhena-0.1.0/docs/tutorials/index.md +43 -0
  79. alhena-0.1.0/docs/use-case/crc_multiscale.ipynb +2001 -0
  80. alhena-0.1.0/mkdocs.yml +151 -0
  81. alhena-0.1.0/pyproject.toml +95 -0
  82. alhena-0.1.0/scripts/alhena_atlas_embed.py +1848 -0
  83. alhena-0.1.0/scripts/requirements-atlas.txt +11 -0
  84. alhena-0.1.0/src/alhena/__init__.py +148 -0
  85. alhena-0.1.0/src/alhena/_bundle.py +121 -0
  86. alhena-0.1.0/src/alhena/_commands.py +89 -0
  87. alhena-0.1.0/src/alhena/_constants.py +247 -0
  88. alhena-0.1.0/src/alhena/_coverage.py +77 -0
  89. alhena-0.1.0/src/alhena/_defaults.py +35 -0
  90. alhena-0.1.0/src/alhena/_errors.py +75 -0
  91. alhena-0.1.0/src/alhena/_frames.py +90 -0
  92. alhena-0.1.0/src/alhena/_geometry.py +483 -0
  93. alhena-0.1.0/src/alhena/_hub.py +109 -0
  94. alhena-0.1.0/src/alhena/_layout.py +532 -0
  95. alhena-0.1.0/src/alhena/_plotting.py +241 -0
  96. alhena-0.1.0/src/alhena/_progress.py +166 -0
  97. alhena-0.1.0/src/alhena/_rings.py +65 -0
  98. alhena-0.1.0/src/alhena/_shards.py +155 -0
  99. alhena-0.1.0/src/alhena/_truth.py +126 -0
  100. alhena-0.1.0/src/alhena/_version.py +3 -0
  101. alhena-0.1.0/src/alhena/_wording.py +120 -0
  102. alhena-0.1.0/src/alhena/annotate.py +666 -0
  103. alhena-0.1.0/src/alhena/api.py +512 -0
  104. alhena-0.1.0/src/alhena/cells/__init__.py +35 -0
  105. alhena-0.1.0/src/alhena/cells/_canvas.py +298 -0
  106. alhena-0.1.0/src/alhena/cells/_centroids.py +261 -0
  107. alhena-0.1.0/src/alhena/cells/_checks.py +415 -0
  108. alhena-0.1.0/src/alhena/cells/_drawing.py +253 -0
  109. alhena-0.1.0/src/alhena/cells/_inputs.py +465 -0
  110. alhena-0.1.0/src/alhena/cells/_labels.py +255 -0
  111. alhena-0.1.0/src/alhena/cells/_path.py +413 -0
  112. alhena-0.1.0/src/alhena/cells/_polygons.py +400 -0
  113. alhena-0.1.0/src/alhena/cells/_pooling.py +368 -0
  114. alhena-0.1.0/src/alhena/cells/_table.py +321 -0
  115. alhena-0.1.0/src/alhena/cli/__init__.py +32 -0
  116. alhena-0.1.0/src/alhena/cli/_annotate.py +310 -0
  117. alhena-0.1.0/src/alhena/cli/_demo.py +27 -0
  118. alhena-0.1.0/src/alhena/cli/_download.py +66 -0
  119. alhena-0.1.0/src/alhena/cli/_encode.py +209 -0
  120. alhena-0.1.0/src/alhena/cli/_info.py +74 -0
  121. alhena-0.1.0/src/alhena/cli/_options.py +239 -0
  122. alhena-0.1.0/src/alhena/cli/_parser.py +330 -0
  123. alhena-0.1.0/src/alhena/cli/_reading.py +339 -0
  124. alhena-0.1.0/src/alhena/cli/_reference.py +139 -0
  125. alhena-0.1.0/src/alhena/cli/_shared.py +281 -0
  126. alhena-0.1.0/src/alhena/demo/__init__.py +87 -0
  127. alhena-0.1.0/src/alhena/demo/_fetch.py +340 -0
  128. alhena-0.1.0/src/alhena/demo/_main.py +164 -0
  129. alhena-0.1.0/src/alhena/demo/_pannuke.py +355 -0
  130. alhena-0.1.0/src/alhena/demo/_report.py +604 -0
  131. alhena-0.1.0/src/alhena/demo/_run.py +190 -0
  132. alhena-0.1.0/src/alhena/encoder/__init__.py +34 -0
  133. alhena-0.1.0/src/alhena/encoder/_adapters.py +234 -0
  134. alhena-0.1.0/src/alhena/encoder/_inference.py +597 -0
  135. alhena-0.1.0/src/alhena/encoder/_loading.py +368 -0
  136. alhena-0.1.0/src/alhena/hub.py +512 -0
  137. alhena-0.1.0/src/alhena/io/__init__.py +159 -0
  138. alhena-0.1.0/src/alhena/io/_carry.py +158 -0
  139. alhena-0.1.0/src/alhena/io/_centroids.py +421 -0
  140. alhena-0.1.0/src/alhena/io/_files.py +307 -0
  141. alhena-0.1.0/src/alhena/io/_formats.py +134 -0
  142. alhena-0.1.0/src/alhena/io/_grid.py +327 -0
  143. alhena-0.1.0/src/alhena/io/_hdf5.py +328 -0
  144. alhena-0.1.0/src/alhena/io/_images.py +272 -0
  145. alhena-0.1.0/src/alhena/io/_labels.py +523 -0
  146. alhena-0.1.0/src/alhena/io/_nuclei.py +104 -0
  147. alhena-0.1.0/src/alhena/io/_polygons.py +645 -0
  148. alhena-0.1.0/src/alhena/io/_resolution.py +278 -0
  149. alhena-0.1.0/src/alhena/io/_saved.py +447 -0
  150. alhena-0.1.0/src/alhena/io/_slides.py +467 -0
  151. alhena-0.1.0/src/alhena/io/_sources.py +442 -0
  152. alhena-0.1.0/src/alhena/io/_tiles.py +222 -0
  153. alhena-0.1.0/src/alhena/metrics.py +424 -0
  154. alhena-0.1.0/src/alhena/preprocess.py +149 -0
  155. alhena-0.1.0/src/alhena/py.typed +0 -0
  156. alhena-0.1.0/src/alhena/refbuild.py +737 -0
  157. alhena-0.1.0/src/alhena/reference.py +648 -0
  158. alhena-0.1.0/src/alhena/spatial/__init__.py +35 -0
  159. alhena-0.1.0/src/alhena/spatial/_cells.py +221 -0
  160. alhena-0.1.0/src/alhena/spatial/_elements.py +233 -0
  161. alhena-0.1.0/src/alhena/spatial/_nuclei.py +207 -0
  162. alhena-0.1.0/src/alhena/spatial/_sopa.py +200 -0
  163. alhena-0.1.0/src/alhena/spatial/_tiles.py +368 -0
  164. alhena-0.1.0/src/alhena/spatial/_windows.py +180 -0
  165. alhena-0.1.0/src/alhena/types.py +874 -0
  166. alhena-0.1.0/src/alhena/viz.py +780 -0
  167. alhena-0.1.0/tests/conftest.py +15 -0
  168. alhena-0.1.0/tests/support.py +227 -0
  169. alhena-0.1.0/tests/test_annotate.py +261 -0
  170. alhena-0.1.0/tests/test_api.py +127 -0
  171. alhena-0.1.0/tests/test_atlas_script.py +180 -0
  172. alhena-0.1.0/tests/test_cells.py +381 -0
  173. alhena-0.1.0/tests/test_cli.py +383 -0
  174. alhena-0.1.0/tests/test_demo.py +236 -0
  175. alhena-0.1.0/tests/test_encode.py +381 -0
  176. alhena-0.1.0/tests/test_encoder.py +315 -0
  177. alhena-0.1.0/tests/test_hub.py +264 -0
  178. alhena-0.1.0/tests/test_io.py +796 -0
  179. alhena-0.1.0/tests/test_metrics.py +164 -0
  180. alhena-0.1.0/tests/test_reference.py +231 -0
  181. alhena-0.1.0/tests/test_results.py +319 -0
  182. alhena-0.1.0/tests/test_spatial.py +329 -0
  183. alhena-0.1.0/tests/test_viz.py +257 -0
@@ -0,0 +1,63 @@
1
+ name: docs
2
+
3
+ # A push to main builds and deploys the site; a pull request only builds it.
4
+ on:
5
+ push:
6
+ branches: [main]
7
+ pull_request:
8
+
9
+ # Read only; the deploy job alone gets the two Pages permissions.
10
+ permissions:
11
+ contents: read
12
+
13
+ # A running deployment in `pages` is never canceled; a pull request builds in a group of its own.
14
+ concurrency:
15
+ group: ${{ github.event_name == 'push' && 'pages' || format('docs-{0}', github.ref) }}
16
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
17
+
18
+ jobs:
19
+ build:
20
+ runs-on: ubuntu-latest
21
+
22
+ steps:
23
+ - uses: actions/checkout@v7
24
+
25
+ - uses: actions/setup-python@v7
26
+ with:
27
+ python-version: "3.12"
28
+
29
+ # CPU builds of torch and torchvision: the build never uses CUDA.
30
+ # mkdocstrings parses `src` and the notebooks are not executed: nothing here runs alhena.
31
+ - name: Install
32
+ run: |
33
+ python -m pip install --upgrade pip
34
+ python -m pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
35
+ python -m pip install -e ".[docs]"
36
+
37
+ # --strict fails on any warning: a moved page, a nav entry with no file, an unresolved path.
38
+ - name: Build
39
+ run: mkdocs build --strict
40
+
41
+ - if: github.event_name == 'push'
42
+ uses: actions/upload-pages-artifact@v5
43
+ with:
44
+ path: site
45
+
46
+ # A job of its own, so a failed build never enters the `github-pages` environment.
47
+ deploy:
48
+ if: github.event_name == 'push'
49
+ needs: build
50
+ runs-on: ubuntu-latest
51
+ permissions:
52
+ pages: write
53
+ id-token: write
54
+ environment:
55
+ name: github-pages
56
+ url: ${{ steps.deployment.outputs.page_url }}
57
+
58
+ steps:
59
+ # Fails here, naming the cause, where the Pages source is not set to GitHub Actions.
60
+ - uses: actions/configure-pages@v6
61
+
62
+ - id: deployment
63
+ uses: actions/deploy-pages@v5
@@ -0,0 +1,95 @@
1
+ name: release
2
+
3
+ # A push to main builds the package; Run workflow also uploads it to TestPyPI, a release to PyPI.
4
+ on:
5
+ push:
6
+ branches: [main]
7
+ workflow_dispatch:
8
+ release:
9
+ types: [published]
10
+
11
+ # Read only; the two upload jobs alone get id-token: write, for Trusted Publishing.
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ build:
17
+ runs-on: ubuntu-latest
18
+
19
+ steps:
20
+ # The build needs no git access after the checkout, so the token is not kept.
21
+ - uses: actions/checkout@v7
22
+ with:
23
+ persist-credentials: false
24
+
25
+ - uses: actions/setup-python@v7
26
+ with:
27
+ python-version: "3.12"
28
+
29
+ - name: Install
30
+ run: python -m pip install build twine
31
+
32
+ # The source archive and the wheel, in dist/.
33
+ - name: Build
34
+ run: python -m build
35
+
36
+ # Checks that the README renders on PyPI; --strict fails on warnings too.
37
+ - name: Check the package
38
+ run: twine check --strict dist/*
39
+
40
+ # A release uploads only the version its tag names: v plus __version__.
41
+ - name: Check the tag
42
+ if: github.event_name == 'release'
43
+ env:
44
+ TAG: ${{ github.event.release.tag_name }}
45
+ run: |
46
+ version=$(python -c "import runpy; print(runpy.run_path('src/alhena/_version.py')['__version__'])")
47
+ if [ "$TAG" != "v$version" ]; then
48
+ echo "::error::Tag $TAG does not match version $version in src/alhena/_version.py: tag the release v$version, or change the version first."
49
+ exit 1
50
+ fi
51
+
52
+ - uses: actions/upload-artifact@v7
53
+ with:
54
+ name: python-package-distributions
55
+ path: dist/
56
+
57
+ # Run workflow, by hand: a trial upload to TestPyPI.
58
+ testpypi:
59
+ if: github.event_name == 'workflow_dispatch'
60
+ needs: build
61
+ runs-on: ubuntu-latest
62
+ permissions:
63
+ id-token: write
64
+ environment:
65
+ name: testpypi
66
+ url: https://test.pypi.org/p/alhena
67
+
68
+ steps:
69
+ - uses: actions/download-artifact@v8
70
+ with:
71
+ name: python-package-distributions
72
+ path: dist/
73
+
74
+ - uses: pypa/gh-action-pypi-publish@release/v1
75
+ with:
76
+ repository-url: https://test.pypi.org/legacy/
77
+
78
+ # A published release; waits for approval where the `pypi` environment requires a reviewer.
79
+ pypi:
80
+ if: github.event_name == 'release'
81
+ needs: build
82
+ runs-on: ubuntu-latest
83
+ permissions:
84
+ id-token: write
85
+ environment:
86
+ name: pypi
87
+ url: https://pypi.org/p/alhena
88
+
89
+ steps:
90
+ - uses: actions/download-artifact@v8
91
+ with:
92
+ name: python-package-distributions
93
+ path: dist/
94
+
95
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,143 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ # An older run on the same branch is canceled when a new one starts.
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ python-version: ["3.12", "3.13", "3.14"]
23
+
24
+ steps:
25
+ - uses: actions/checkout@v7
26
+
27
+ - uses: actions/setup-python@v7
28
+ with:
29
+ python-version: ${{ matrix.python-version }}
30
+
31
+ # CPU builds of torch and torchvision: the tests never use CUDA.
32
+ # viz for test_viz.py; the spatial and wsi tests run in the extras and floors jobs.
33
+ - name: Install
34
+ run: |
35
+ python -m pip install --upgrade pip
36
+ python -m pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
37
+ python -m pip install -e ".[dev,viz]"
38
+
39
+ # ruff keeps its format within a minor release: the floors' 0.16.0 and 0.16 here agree.
40
+ - name: Lint
41
+ run: |
42
+ ruff check src tests scripts
43
+ ruff format --check src tests scripts
44
+
45
+ # -rs lists each skipped test with its reason, so a lost dependency shows as a shorter run.
46
+ - name: Test
47
+ run: pytest -rs
48
+
49
+ # Every extra at its latest release, as a new install resolves it.
50
+ extras:
51
+ runs-on: ubuntu-latest
52
+
53
+ steps:
54
+ - uses: actions/checkout@v7
55
+
56
+ - uses: actions/setup-python@v7
57
+ with:
58
+ python-version: "3.12"
59
+
60
+ - name: Install the extras
61
+ run: |
62
+ python -m pip install --upgrade pip
63
+ python -m pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
64
+ python -m pip install -e ".[dev,viz,spatial,wsi]"
65
+
66
+ - name: Test
67
+ run: pytest -rs
68
+
69
+ # Every dependency at the lowest release pyproject.toml allows, pinned in ci/constraints-min.txt.
70
+ floors:
71
+ runs-on: ubuntu-latest
72
+
73
+ steps:
74
+ - uses: actions/checkout@v7
75
+
76
+ - uses: actions/setup-python@v7
77
+ with:
78
+ python-version: "3.12"
79
+
80
+ # torchvision 0.17.0 is the release paired with torch 2.2.0, from the CPU index.
81
+ - name: Install the floors
82
+ run: |
83
+ python -m pip install --upgrade pip
84
+ python -m pip install torch==2.2.0 torchvision==0.17.0 --index-url https://download.pytorch.org/whl/cpu
85
+ python -m pip install -c ci/constraints-min.txt -e ".[dev,viz]"
86
+
87
+ - name: Lint
88
+ run: |
89
+ ruff check src tests scripts
90
+ ruff format --check src tests scripts
91
+
92
+ - name: Test
93
+ run: pytest -rs
94
+
95
+ # Each extra at its floor, the core resolved freely: the spatial stack needs newer core releases.
96
+ floors-spatial:
97
+ runs-on: ubuntu-latest
98
+
99
+ steps:
100
+ - uses: actions/checkout@v7
101
+
102
+ - uses: actions/setup-python@v7
103
+ with:
104
+ python-version: "3.12"
105
+
106
+ - name: Install the spatial extra at its floors
107
+ run: |
108
+ python -m pip install --upgrade pip
109
+ python -m pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
110
+ python -m pip install -c ci/constraints-min-extras.txt -e ".[dev,viz,spatial]"
111
+
112
+ - name: Lint
113
+ run: |
114
+ ruff check src tests scripts
115
+ ruff format --check src tests scripts
116
+
117
+ - name: Test
118
+ run: pytest -rs
119
+
120
+ # A job of its own: tiffslide 2.5.1 needs zarr 2, and spatialdata 0.8.0 needs zarr 3.
121
+ floors-wsi:
122
+ runs-on: ubuntu-latest
123
+
124
+ steps:
125
+ - uses: actions/checkout@v7
126
+
127
+ - uses: actions/setup-python@v7
128
+ with:
129
+ python-version: "3.12"
130
+
131
+ - name: Install the wsi extra at its floor
132
+ run: |
133
+ python -m pip install --upgrade pip
134
+ python -m pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
135
+ python -m pip install -c ci/constraints-min-extras.txt -e ".[dev,viz,wsi]"
136
+
137
+ - name: Lint
138
+ run: |
139
+ ruff check src tests scripts
140
+ ruff format --check src tests scripts
141
+
142
+ - name: Test
143
+ run: pytest -rs
@@ -0,0 +1,17 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ build/
6
+ dist/
7
+ *.egg-info/
8
+ .venv/
9
+ venv/
10
+ # the environment of scripts/alhena_atlas_embed.py
11
+ alhena_atlas/
12
+ .ipynb_checkpoints/
13
+ .DS_Store
14
+
15
+ # documentation build
16
+ site/
17
+ .cache/
@@ -0,0 +1,48 @@
1
+ cff-version: 1.2.0
2
+ message: If you use this software, please cite it using the metadata in this file.
3
+ type: software
4
+ title: ALHENA
5
+ abstract: "ALHENA, an H&E encoder distilled from transcriptomic foundation models for multi-scale slide analysis."
6
+ version: 0.1.0
7
+ license: BSD-3-Clause
8
+ repository-code: https://github.com/prism-oncology/ALHENA
9
+ keywords:
10
+ - histopathology
11
+ - spatial-transcriptomics
12
+ - cell-typing
13
+ - foundation-model
14
+ authors:
15
+ - family-names: Giraud-Sauveur
16
+ given-names: Félicie
17
+ email: felicie.giraudsauveur@gmail.com
18
+ - family-names: Blampey
19
+ given-names: Quentin
20
+ - family-names: Marza
21
+ given-names: Pierre
22
+ - family-names: Benkirane
23
+ given-names: Hakim
24
+ - family-names: Vakalopoulou
25
+ given-names: Maria
26
+ - family-names: Cournède
27
+ given-names: Paul-Henry
28
+ - family-names: Christodoulidis
29
+ given-names: Stergios
30
+ preferred-citation:
31
+ type: unpublished
32
+ title: "ALHENA, an H&E encoder distilled from transcriptomic foundation models for multi-scale slide analysis"
33
+ notes: Manuscript under review.
34
+ authors:
35
+ - family-names: Giraud-Sauveur
36
+ given-names: Félicie
37
+ - family-names: Blampey
38
+ given-names: Quentin
39
+ - family-names: Marza
40
+ given-names: Pierre
41
+ - family-names: Benkirane
42
+ given-names: Hakim
43
+ - family-names: Vakalopoulou
44
+ given-names: Maria
45
+ - family-names: Cournède
46
+ given-names: Paul-Henry
47
+ - family-names: Christodoulidis
48
+ given-names: Stergios
@@ -0,0 +1,41 @@
1
+ # Contributing to ALHENA
2
+
3
+ Bug reports, questions and suggestions are welcome in the
4
+ [issues](https://github.com/prism-oncology/ALHENA/issues). Include the command or code you ran, the
5
+ message it printed in full, and what `alhena info` reports.
6
+
7
+ ## Development install
8
+
9
+ Clone the repository and install it in editable mode, with the development tools:
10
+
11
+ ```bash
12
+ git clone https://github.com/prism-oncology/ALHENA.git
13
+ cd ALHENA
14
+ pip install -e ".[dev]"
15
+ ```
16
+
17
+ Add the `viz`, `wsi` and `spatial` extras to run the tests that need them.
18
+
19
+ ## Tests and style
20
+
21
+ The tests use synthetic data only, so they need no model weights and no network:
22
+
23
+ ```bash
24
+ pytest
25
+ ```
26
+
27
+ The code is linted and formatted with ruff, as the continuous integration checks it:
28
+
29
+ ```bash
30
+ ruff check src tests scripts
31
+ ruff format --check src tests scripts
32
+ ```
33
+
34
+ ## Documentation
35
+
36
+ The documentation is built with MkDocs. Install the docs extra, then serve the site locally:
37
+
38
+ ```bash
39
+ pip install -e ".[docs]"
40
+ mkdocs serve
41
+ ```
alhena-0.1.0/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Félicie Giraud-Sauveur
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
alhena-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,200 @@
1
+ Metadata-Version: 2.5
2
+ Name: alhena
3
+ Version: 0.1.0
4
+ Summary: ALHENA, an H&E encoder distilled from transcriptomic foundation models for multi-scale slide analysis.
5
+ Project-URL: Homepage, https://github.com/prism-oncology/ALHENA
6
+ Project-URL: Documentation, https://prism-oncology.github.io/ALHENA/
7
+ Project-URL: Source, https://github.com/prism-oncology/ALHENA
8
+ Project-URL: Issues, https://github.com/prism-oncology/ALHENA/issues
9
+ Project-URL: Weights, https://huggingface.co/prism-oncology/ALHENA
10
+ Project-URL: References, https://huggingface.co/prism-oncology/ALHENA-references
11
+ Author-email: Félicie Giraud-Sauveur <felicie.giraudsauveur@gmail.com>
12
+ License-Expression: BSD-3-Clause
13
+ License-File: LICENSE
14
+ Keywords: cell-typing,foundation-model,histopathology,spatial-transcriptomics
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
23
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
24
+ Requires-Python: >=3.12
25
+ Requires-Dist: anndata>=0.12.7
26
+ Requires-Dist: h5py>=3.10
27
+ Requires-Dist: huggingface-hub>=0.34
28
+ Requires-Dist: numpy>=1.26
29
+ Requires-Dist: opencv-python-headless>=4.8
30
+ Requires-Dist: pandas>=2.1.1
31
+ Requires-Dist: pyarrow>=14
32
+ Requires-Dist: safetensors>=0.4
33
+ Requires-Dist: timm<2,>=1.0
34
+ Requires-Dist: torch>=2.2
35
+ Requires-Dist: tqdm>=4.65
36
+ Provides-Extra: dev
37
+ Requires-Dist: pytest>=8.0; extra == 'dev'
38
+ Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
39
+ Provides-Extra: docs
40
+ Requires-Dist: mkdocs-jupyter>=0.25; extra == 'docs'
41
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
42
+ Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
43
+ Provides-Extra: spatial
44
+ Requires-Dist: sopa>=2.2; extra == 'spatial'
45
+ Requires-Dist: spatialdata>=0.8; extra == 'spatial'
46
+ Provides-Extra: viz
47
+ Requires-Dist: matplotlib>=3.8; extra == 'viz'
48
+ Provides-Extra: wsi
49
+ Requires-Dist: tiffslide>=2.5.1; extra == 'wsi'
50
+ Description-Content-Type: text/markdown
51
+
52
+ <p align="center">
53
+ <img src="https://raw.githubusercontent.com/prism-oncology/ALHENA/main/docs/assets/alhena_banner.svg" alt="ALHENA" width="100%" height="168">
54
+ </p>
55
+
56
+ <p align="center">
57
+ <b>ALHENA, an H&amp;E encoder distilled from transcriptomic foundation models for multi-scale slide analysis.</b>
58
+ </p>
59
+
60
+ <p align="center">
61
+ <a href="https://prism-oncology.github.io/ALHENA/"><b>Documentation</b></a> |
62
+ <a href="https://github.com/prism-oncology/ALHENA-paper"><b>Paper code</b></a> |
63
+ <a href="https://huggingface.co/prism-oncology/ALHENA"><b>Weights on Hugging Face</b></a> |
64
+ <a href="https://huggingface.co/prism-oncology/ALHENA-references"><b>References on Hugging Face</b></a>
65
+ </p>
66
+
67
+ <p align="center">
68
+ <a href="https://pypi.org/project/alhena/"><img alt="PyPI" src="https://img.shields.io/pypi/v/alhena.svg"></a>
69
+ <a href="https://github.com/prism-oncology/ALHENA/actions/workflows/tests.yml"><img alt="Tests" src="https://github.com/prism-oncology/ALHENA/actions/workflows/tests.yml/badge.svg"></a>
70
+ <a href="https://github.com/prism-oncology/ALHENA/actions/workflows/docs.yml"><img alt="Docs" src="https://github.com/prism-oncology/ALHENA/actions/workflows/docs.yml/badge.svg"></a>
71
+ <img alt="License: BSD-3-Clause" src="https://img.shields.io/badge/license-BSD--3--Clause-blue.svg">
72
+ <img alt="Python 3.12+" src="https://img.shields.io/badge/python-3.12%2B-blue.svg">
73
+ </p>
74
+
75
+ ---
76
+
77
+ ALHENA reads an H&E section and returns two things: **embeddings**, per tile and per cell, and
78
+ **cell types** on an unlabeled section. No spatial transcriptomics is needed at inference, and
79
+ nothing is fitted at deployment.
80
+
81
+ Cell types come from retrieval against a labeled single-cell reference, so a different reference
82
+ gives a different taxonomy. ALHENA provides prebuilt references for fifteen human tissues, built
83
+ from fourteen public single-cell atlases, or you can build a custom reference.
84
+
85
+ When the inputs share a spatial frame, the embeddings and the cell types stay aligned with the
86
+ slide and can be analyzed together across scales.
87
+
88
+ ALHENA is gamma Geminorum, from the Arabic *al-han'ah*, a brand mark placed on something to make it
89
+ readable. To the eye, it appears as a single star, though it is in fact a binary system. The light
90
+ we see comes from a bright A-type subgiant. Its companion has never been imaged and reveals itself
91
+ only through the faint wobble it leaves in the motion of the visible star. The H&E section is that
92
+ visible star, inexpensive, familiar, and collected everywhere. The spatial transcriptome is the
93
+ unseen companion, physically present, shaping what we observe, yet never directly accessible at
94
+ inference.
95
+
96
+ ## Install
97
+
98
+ ALHENA needs Python 3.12 or later, and runs with or without a GPU.
99
+
100
+ ```bash
101
+ pip install alhena
102
+ ```
103
+
104
+ Optional extras add plotting (`viz`), whole-slide reading (`wsi`) and SpatialData support
105
+ (`spatial`): `pip install "alhena[viz,wsi,spatial]"`.
106
+
107
+ The UNI2-h model weights are distributed separately by their authors and are not included with
108
+ ALHENA. Their repository is gated: request access at
109
+ [MahmoodLab/UNI2-h](https://huggingface.co/MahmoodLab/UNI2-h) and accept the license. Then log
110
+ in and download the weights, 2.7 GB, into the Hugging Face cache, where ALHENA finds them:
111
+
112
+ ```bash
113
+ hf auth login
114
+ hf download MahmoodLab/UNI2-h pytorch_model.bin
115
+ ```
116
+
117
+ For weights stored elsewhere, set `ALHENA_BACKBONE` to the file. ALHENA adds its own low-rank
118
+ adapters and projection heads on top of the UNI2-h weights, and downloads them from Hugging Face
119
+ on first use, with the prebuilt reference of each tissue it annotates.
120
+
121
+ ## See it work
122
+
123
+ The demo needs no data of your own. It reads a few annotated breast H&E fields from the public
124
+ PanNuke dataset, annotates their nuclei with the prebuilt breast reference, and compares the
125
+ predicted cell types with the PanNuke labels. It needs the UNI2-h model weights.
126
+
127
+ ```bash
128
+ alhena demo Breast
129
+ ```
130
+
131
+ ## Use it on your own data
132
+
133
+ ```python
134
+ import alhena
135
+
136
+ source = alhena.io.read_slide("slide.svs") # an H&E slide, read tile by tile
137
+ nuclei = alhena.io.read_polygons("nuclei.geojson", tiles=source) # nucleus outlines
138
+
139
+ encoder = alhena.load_encoder()
140
+ tiles = encoder.encode_tiles(source) # one embedding per tile
141
+ tiles.write_h5ad("tiles.h5ad") # one row per tile, ready to analyze
142
+
143
+ cells = encoder.encode_cells(source, nuclei=nuclei) # one pass, each tile once
144
+ cells.write_h5ad("cells.h5ad")
145
+
146
+ typer = alhena.load_celltyper(tissue="Breast") # loads the reference alone
147
+ result = typer.annotate(cells) # retrieval, no encoder
148
+ result.to_frame()
149
+ ```
150
+
151
+ `read_slide` needs the `wsi` extra and reads the resolution the slide declares. `source` can also
152
+ be an array of H&E tiles, `(n_tiles, height, width, 3)` uint8 RGB; `mpp=` then declares the
153
+ resolution it is already at. The nuclei come from a segmenter of your choice: ALHENA does not
154
+ segment. The documentation shows how to
155
+ [segment a slide with sopa and StarDist](https://prism-oncology.github.io/ALHENA/inputs/nuclei/#segment-a-slide-with-sopa-and-stardist).
156
+ `read_polygons` expects the outlines in the slide's full-resolution pixels. Outlines written in
157
+ microns need `units="um"`, and outlines segmented on another image need `transform=`. See
158
+ [Coordinates](https://prism-oncology.github.io/ALHENA/inputs/coordinates/).
159
+ With SpatialData, `alhena.spatial` reads the image and the nucleus outlines from the object.
160
+
161
+ Cells are encoded once and can be annotated again, against another reference or on another
162
+ annotation axis, without rerunning the image encoder: `alhena.io.read_cells("cells.h5ad")` reads
163
+ them back with the provenance that pairs them with a reference.
164
+ `typer.annotate(source, nuclei=nuclei)` runs both steps in one call and keeps the cells on
165
+ `result.cells`.
166
+
167
+ ## Documentation
168
+
169
+ **[prism-oncology.github.io/ALHENA](https://prism-oncology.github.io/ALHENA/)**
170
+
171
+ Start with [Choose your task](https://prism-oncology.github.io/ALHENA/start/choose-your-task/):
172
+ tile embeddings, cell embeddings, cell-type annotation, or multi-scale slide analysis.
173
+ [Inputs](https://prism-oncology.github.io/ALHENA/inputs/) says how to provide your images and
174
+ nuclei, whatever form they take.
175
+
176
+ Six [tutorials](https://prism-oncology.github.io/ALHENA/tutorials/) cover these tasks, custom
177
+ references, and annotation evaluation. The [use case example](https://prism-oncology.github.io/ALHENA/use-case/crc_multiscale/) applies
178
+ ALHENA to two public colorectal cancer cohorts, from tissue contexts to patient-level analyses.
179
+
180
+ ## Contributing
181
+
182
+ Report a problem or ask a question in the [issues](https://github.com/prism-oncology/ALHENA/issues);
183
+ [CONTRIBUTING.md](https://github.com/prism-oncology/ALHENA/blob/main/CONTRIBUTING.md) explains how to
184
+ install ALHENA for development and run its tests.
185
+
186
+ ## License
187
+
188
+ The ALHENA software is released under the BSD 3-Clause License. The ALHENA model weights,
189
+ comprising the LoRA adapters and the projection heads used at inference, are released under the
190
+ Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0).
191
+
192
+ The precomputed single-cell reference representations are released under the CC BY-NC 4.0
193
+ license. They remain subject to the terms of their source atlases, and those derived from DISCO
194
+ are provided for academic research only, in accordance with the DISCO terms of use.
195
+
196
+ ## Citation
197
+
198
+ If you use ALHENA, please cite it as described on the
199
+ [Cite page](https://prism-oncology.github.io/ALHENA/cite/) of the documentation. Please also cite
200
+ the models and datasets ALHENA builds on, listed on the same page.