diff --git a/.gitignore b/.gitignore index fd1b966..ebeaf9a 100644 --- a/.gitignore +++ b/.gitignore @@ -69,7 +69,11 @@ instance/ .scrapy # Sphinx documentation +docs/build/ docs/_build/ +docs/html/ +docs/_autosummary/ +docs/src/ # PyBuilder target/ @@ -131,3 +135,6 @@ dmypy.json # Pyre type checker .pyre/ + +# macOS +*.DS_Store \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..ed88099 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..1804ba7 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,30 @@ +## Documentations + +### Build + +First install requirements for building docs: +``` +python -m pip install -r docs/requirements.txt +``` + +Docs can then be built running the commands: + +``` +cd docs/ +sphinx-apidoc -f -o src/ ../torchstain +make html +``` + +### Usage + +To access the documentations, open the generated HTML in your browser, e.g., with firefox on Linux run this: +``` +firefox build/html/index.html +``` + +Alternatively, on macOS you can open the HTML in chrome by: +``` +open -a "Google Chrome" build/html/index.html +``` + +When documentations are pushed to production, they will be available at [torchstain.readthedocs.io/](https://torchstain.readthedocs.io/). diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..228cf9d --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,82 @@ +"""Configuration file for the Sphinx documentation builder.""" + +import inspect +import os +import subprocess +import sys + +import torchstain + + +sys.path.insert(0, os.path.abspath('..')) + + +# Project information +url = "https://github.com/EIDOSLAB/torchstain" + +# General configuration +master_doc = 'index' + +# Sphinx extension modules +extensions = [ + 'sphinx.ext.napoleon', + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.linkcode', +] + +# Generate autosummary +autosummary_generate = True + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files +exclude_patterns = ['templates'] + +# numpy style docs with Napoleon +napoleon_google_docstring = False +napoleon_use_param = False +napoleon_use_ivar = True + +# Draw graphs in the SVG format instead of the default PNG format +graphviz_output_format = 'svg' + +# sphinx.ext.linkcode: Try to link to source code on GitHub +REVISION_CMD = ['git', 'rev-parse', '--short', 'HEAD'] +try: + _git_revision = subprocess.check_output(REVISION_CMD).strip() +except (subprocess.CalledProcessError, OSError): + _git_revision = 'master' +else: + _git_revision = _git_revision.decode('utf-8') + + +def linkcode_resolve(domain, info): + if domain != 'py': + return None + module = info.get('module', None) + fullname = info.get('fullname', None) + if not module or not fullname: + return None + obj = sys.modules.get(module, None) + if obj is None: + return None + + for part in fullname.split('.'): + obj = getattr(obj, part) + if isinstance(obj, property): + obj = obj.fget + if hasattr(obj, '__wrapped__'): + obj = obj.__wrapped__ + + file = inspect.getsourcefile(obj) + package_dir = os.path.dirname(torchstain.__file__) + if file is None or os.path.commonpath([file, package_dir]) != package_dir: + return None + file = os.path.relpath(file, start=package_dir) + source, line_start = inspect.getsourcelines(obj) + line_end = line_start + len(source) - 1 + filename = f'src/torchstain/{file}#L{line_start}-L{line_end}' + return f'{url}/blob/{_git_revision}/{filename}' diff --git a/docs/examples/stain_normalization.rst b/docs/examples/stain_normalization.rst new file mode 100644 index 0000000..de17261 --- /dev/null +++ b/docs/examples/stain_normalization.rst @@ -0,0 +1,61 @@ +Stain Normalization +=================== + +The torchstain package supports three different backends: PyTorch, +TensorFlow, and NumPy. Below is a simple example of how to get started. +In this example we use PyTorch. + +To run example, be sure to have installed the necessary dependencies: + +``pip install torchstain[torch] torchvision opencv-python`` + +A simple usage example can be seen below: + +.. code-block:: python + + import torch + from torchvision import transforms + import torchstain + import cv2 + + target = cv2.cvtColor(cv2.imread("./data/target.png"), cv2.COLOR_BGR2RGB) + to_transform = cv2.cvtColor(cv2.imread("./data/source.png"), cv2.COLOR_BGR2RGB) + + T = transforms.Compose([ + transforms.ToTensor(), + transforms.Lambda(lambda x: x*255) + ]) + + normalizer = torchstain.normalizers.MacenkoNormalizer(backend='torch') + normalizer.fit(T(target)) + + t_to_transform = T(to_transform) + norm, H, E = normalizer.normalize(I=t_to_transform, stains=True) + + +The generated result can be seen below: + +.. image:: ../../data/result.png + :alt: Stain normalized result + + +Different Backends +------------------ + +To use TensorFlow or NumPy backend, simply change the *backend* +argument to the *MacenkoNormalizer*. Also note that different for +different backends and normalization techniques, different +preprocessing may be required. + +For TensorFlow instead perform: + +.. code-block:: python + + import tensorflow as tf + import numpy as np + + T = lambda x: tf.convert_to_tensor(np.moveaxis(x, -1, 0).astype("float32")) + t_to_transform = T(to_transform) + +Whereas for NumPy *no* preprocessing is required, given that the image +is already channel-last and uint8 dtype. diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..7227d63 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,61 @@ +:github_url: https://github.com/EIDOSLAB/torchstain/tree/main/docs + +TorchStain +------------------- + +TorchStain is a modular Python package for GPU-accelerated stain normalization +and augmentation for histopathological image analysis. It supports PyTorch, +TensorFlow, and NumPy backends. + +Installation +------------ + +The latest release of TorchStain can be installed from +`PyPI `_ by: + +``pip install torchstain`` + +To install a specific backend use either torchstain[torch] or torchstain[tf]. +The numpy backend is included by default in both. + +You may also install directly from GitHub, using the following command: + +``pip install git+https://github.com/EIDOSLAB/torchstain`` + +.. toctree:: + :glob: + :caption: Examples + :maxdepth: 2 + + examples/* + +.. toctree:: + :caption: API Documentation + :maxdepth: 10 + + src/modules + +The Team +-------- + +The development of TorchStain is led by researchers at `EIDOSLAB `_ +and `SINTEF MIA `_. +We are also very grateful to the open source community for contributing ideas, bug fixes, and issues. + +Support +------- + +If you are having issues, please let us know by filing an issue on our +`issue tracker `_. + + +License +------- + +TorchStain is licensed under the `MIT License `_. + + +Indices and Tables +================== + +* :ref:`genindex` \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..8e99ff9 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,5 @@ +sphinx==5.3.0 +sphinx-rtd-theme +torchstain +tensorflow +torch diff --git a/docs/templates/class.rst b/docs/templates/class.rst new file mode 100644 index 0000000..b55ecb3 --- /dev/null +++ b/docs/templates/class.rst @@ -0,0 +1,32 @@ +{{ fullname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :members: <-- add at least this line + :show-inheritance: <-- plus I want to show inheritance... + :inherited-members: <-- ...and inherited members too + + {% block methods %} + .. automethod:: __init__ + + {% if methods %} + .. rubric:: {{ _('Methods') }} + + .. autosummary:: + {% for item in methods %} + ~{{ name }}.{{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block attributes %} + {% if attributes %} + .. rubric:: {{ _('Attributes') }} + + .. autosummary:: + {% for item in attributes %} + ~{{ name }}.{{ item }} + {%- endfor %} + {% endif %} + {% endblock %} \ No newline at end of file diff --git a/docs/templates/module.rst b/docs/templates/module.rst new file mode 100644 index 0000000..f22aac8 --- /dev/null +++ b/docs/templates/module.rst @@ -0,0 +1,66 @@ +{{ fullname | escape | underline}} + +.. automodule:: {{ fullname }} + + {% block attributes %} + {% if attributes %} + .. rubric:: Module Attributes + + .. autosummary:: + :toctree: <-- add this line + {% for item in attributes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block functions %} + {% if functions %} + .. rubric:: {{ _('Functions') }} + + .. autosummary:: + :toctree: <-- add this line + {% for item in functions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block classes %} + {% if classes %} + .. rubric:: {{ _('Classes') }} + + .. autosummary:: + :toctree: <-- add this line + :template: class.rst <-- add this line + {% for item in classes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block exceptions %} + {% if exceptions %} + .. rubric:: {{ _('Exceptions') }} + + .. autosummary:: + :toctree: <-- add this line + {% for item in exceptions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + +{% block modules %} +{% if modules %} +.. rubric:: Modules + +.. autosummary:: + :toctree: + :template: class.rst <-- add this line + :recursive: +{% for item in modules %} + {{ item }} +{%- endfor %} +{% endif %} +{% endblock %} \ No newline at end of file diff --git a/readthedocs.yml b/readthedocs.yml new file mode 100644 index 0000000..d165f5b --- /dev/null +++ b/readthedocs.yml @@ -0,0 +1,23 @@ +# .readthedocs.yaml + +# required +version: 2 + +# set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# build documentation in the docs/ directory with Sphinx +sphinx: + builder: html + configuration: docs/conf.py + fail_on_warning: false + +# declare the Python requirements required to build your docs +python: + install: + - requirements: docs/requirements.txt + - method: pip + path: .