--- a
+++ b/pyproject.toml
@@ -0,0 +1,164 @@
+[build-system]
+build-backend = "hatchling.build"
+requires = ["hatchling"]
+
+[project]
+name = "nichecompass"
+version = "0.2.3"
+description = "End-to-end analysis of spatial multi-omics data"
+readme = "README.md"
+requires-python = ">=3.9"
+license = {file = "LICENSE"}
+authors = [
+    {name = "Sebastian Birk"},
+]
+maintainers = [
+    {name = "Sebastian Birk", email = "sebastian.birk@outlook.com"},
+]
+urls.Documentation = "https://nichecompass.readthedocs.io/"
+urls.Source = "https://github.com/Lotfollahi-lab/nichecompass"
+urls.Home-page = "https://github.com/Lotfollahi-lab/nichecompass"
+classifiers = [
+  "Development Status :: 3 - Alpha",
+  "Intended Audience :: Science/Research",
+  "Natural Language :: English",
+  "Programming Language :: Python :: 3.9",
+  "Operating System :: POSIX :: Linux",
+  "Topic :: Scientific/Engineering :: Bio-Informatics",
+]
+dependencies = [
+    "mlflow>=1.28.0",
+    "pyreadr>=0.4.6",
+    "scanpy>=1.9.3,<1.10.0",
+    "torch-geometric>=2.2.0",
+    "omnipath>=1.0.7",
+    "decoupler>=1.4.0",
+    "numpy<2",
+]
+optional-dependencies.dev = [
+    "pre-commit",
+    "twine>=4.0.2",
+]
+optional-dependencies.docs = [
+    "docutils>=0.8,!=0.18.*,!=0.19.*",
+    "sphinx>=4.1",
+    "sphinx-book-theme>=1.0.0",
+    "myst-nb",
+    "myst-parser",
+    "sphinxcontrib-bibtex>=1.0.0",
+    "sphinx-autodoc-typehints",
+    "sphinx_rtd_theme",
+    "sphinxext-opengraph",
+    "sphinx-copybutton",
+    "sphinx-design",
+    "sphinx-hoverxref",
+    # For notebooks
+    "ipykernel",
+    "ipython",
+    "pandas",
+]
+optional-dependencies.docsbuild = [
+    "nichecompass[docs,benchmarking,multimodal]"
+]  # docs build dependencies
+optional-dependencies.tests = [
+    "pytest",
+    "coverage",
+]
+optional-dependencies.benchmarking = [
+    "scib-metrics>=0.3.3",
+    "pynndescent>=0.5.8",
+    "scikit-misc>=0.3.0",
+    "squidpy>=1.2.2",
+    "jax==0.4.7",
+    "jaxlib==0.4.7"
+]
+optional-dependencies.multimodal = [
+    "scglue>=0.3.2",
+]
+optional-dependencies.tutorials = [
+    "jupyter",
+]
+all = ["nichecompass[dev,docs,tests,benchmarking,multimodal,tutorials]"]
+
+[tool.coverage.run]
+source = ["nichecompass"]
+omit = [
+    "**/test_*.py",
+]
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+xfail_strict = true
+addopts = [
+    "--import-mode=importlib",  # allow using test files with same name
+]
+
+[tool.ruff]
+line-length = 120
+src = ["src"]
+extend-include = ["*.ipynb"]
+
+[tool.ruff.format]
+docstring-code-format = true
+
+[tool.ruff.lint]
+select = [
+    "F",  # Errors detected by Pyflakes
+    "E",  # Error detected by Pycodestyle
+    "W",  # Warning detected by Pycodestyle
+    "I",  # isort
+    "D",  # pydocstyle
+    "B",  # flake8-bugbear
+    "TID",  # flake8-tidy-imports
+    "C4",  # flake8-comprehensions
+    "BLE",  # flake8-blind-except
+    "UP",  # pyupgrade
+    "RUF100",  # Report unused noqa directives
+]
+ignore = [
+    # line too long -> we accept long comment lines; formatter gets rid of long code lines
+    "E501",
+    # Do not assign a lambda expression, use a def -> lambda expression assignments are convenient
+    "E731",
+    # allow I, O, l as variable names -> I is the identity matrix
+    "E741",
+    # Missing docstring in public package
+    "D104",
+    # Missing docstring in public module
+    "D100",
+    # Missing docstring in __init__
+    "D107",
+    # Errors from function calls in argument defaults. These are fine when the result is immutable.
+    "B008",
+    # __magic__ methods are are often self-explanatory, allow missing docstrings
+    "D105",
+    # first line should end with a period [Bug: doesn't work with single-line docstrings]
+    "D400",
+    # First line should be in imperative mood; try rephrasing
+    "D401",
+    ## Disable one in each pair of mutually incompatible rules
+    # We don’t want a blank line before a class docstring
+    "D203",
+    # We want docstrings to start immediately after the opening triple quote
+    "D213",
+]
+
+[tool.ruff.lint.pydocstyle]
+convention = "numpy"
+
+[tool.ruff.lint.per-file-ignores]
+"docs/*" = ["I"]
+"tests/*" = ["D"]
+"*/__init__.py" = ["F401"]
+
+[tool.cruft]
+skip = [
+    "tests",
+    "src/**/__init__.py",
+    "src/**/basic.py",
+    "docs/api.md",
+    "docs/changelog.md",
+    "docs/references.bib",
+    "docs/references.md",
+    "docs/notebooks/example.ipynb",
+]