diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index b568b75b..13499a51 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -32,7 +32,8 @@ jobs: pip install --no-cache-dir Cython pip install -r requirements.txt -r requirements_dev.txt -r requirements_docs.txt pip install . - - run: mkdocs build + - run: python scripts/convert_notebooks.py + - run: zensical build --clean --strict - name: Deploy to Netlify uses: nwtgck/actions-netlify@v4.0 with: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9ec29824..6d765634 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,6 +6,9 @@ on: jobs: deploy: runs-on: ubuntu-latest + # Required to push the built site to the gh-pages branch. + permissions: + contents: write steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v6 @@ -15,11 +18,13 @@ jobs: run: | python -m pip install --upgrade pip pip install --user --no-cache-dir Cython - pip install --user -r requirements.txt -r requirements_dev.txt + pip install --user -r requirements.txt -r requirements_dev.txt -r requirements_docs.txt pip install --user . - # - name: PKG-TEST - # run: | - # python -m unittest discover tests/ - - run: python -m pip install --upgrade pip - - run: pip install mkdocs-material mkdocstrings mkdocstrings-python mkdocs-git-revision-date-plugin mkdocs-jupyter ipykernel - - run: mkdocs gh-deploy --force + - run: python scripts/convert_notebooks.py + - run: zensical build --clean --strict + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./site + force_orphan: true diff --git a/.gitignore b/.gitignore index 21f63331..cdde51a9 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,10 @@ output/ docs/examples/*.geojson +# Markdown files generated from notebooks for the Zensical build +docs/examples/*.md +docs/workshops/*.md + # C extensions *.so diff --git a/docs/overrides/main.html b/docs/overrides/main.html deleted file mode 100644 index 702c96bf..00000000 --- a/docs/overrides/main.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -{% if page.nb_url %} - - {% include ".icons/material/download.svg" %} - -{% endif %} - -{{ super() }} -{% endblock content %} diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 401ab801..00000000 --- a/mkdocs.yml +++ /dev/null @@ -1,103 +0,0 @@ -site_name: segment-geospatial - -site_url: https://samgeo.gishub.org - -repo_url: https://github.com/opengeos/segment-geospatial - -theme: - palette: - scheme: preference - name: material - icon: - repo: fontawesome/brands/github - features: - - navigation.instant - - search.highlight - # - navigation.expand - # - navigation.tabs - custom_dir: "docs/overrides" - -plugins: - - search - - mkdocstrings - - git-revision-date - - mkdocs-jupyter: - include_source: True - execute: False - ignore_h1_titles: True - # execute_ignore: "*.ipynb" - -markdown_extensions: - - attr_list - - pymdownx.superfences - - pymdownx.highlight: - linenums: true - - toc: - permalink: true - -# extra: -# analytics: -# provider: google -# property: UA-XXXXXXXXX-X - -nav: - - Home: index.md - - Installation: installation.md - - Usage: usage.md - - REST API: api.md - - Contributing: contributing.md - - FAQ: faq.md - - Changelog: changelog.md - - Report Issues: https://github.com/opengeos/segment-geospatial/issues - - Examples: - - examples/satellite.ipynb - - examples/automatic_mask_generator.ipynb - - examples/automatic_mask_generator_hq.ipynb - - examples/input_prompts.ipynb - - examples/input_prompts_hq.ipynb - - examples/box_prompts.ipynb - - examples/text_prompts.ipynb - - examples/text_prompts_batch.ipynb - - examples/fast_sam.ipynb - - examples/text_swimming_pools.ipynb - - examples/arcgis.ipynb - - examples/maxar_open_data.ipynb - - examples/sam2_automatic.ipynb - - examples/sam2_predictor.ipynb - - examples/sam2_video.ipynb - - examples/sam2_box_prompts.ipynb - - examples/sam2_point_prompts.ipynb - - examples/sam2_text_prompts.ipynb - - examples/tree_mapping.ipynb - - examples/image_captioning.ipynb - - examples/sam3_image_segmentation.ipynb - - examples/sam3_image_segmentation_jpg.ipynb - - examples/sam3_interactive.ipynb - - examples/sam3_batch_segmentation.ipynb - - examples/sam3_video_segmentation.ipynb - - examples/sam3_video_prompts.ipynb - - examples/sam3_video_masks.ipynb - - examples/sam3_automated_segmentation.ipynb - - examples/sam3_object_tracking.ipynb - - examples/sam3_point_prompts.ipynb - - examples/sam3_point_prompts_batch.ipynb - - examples/sam3_box_prompts.ipynb - - examples/sam3_tiled_segmentation.ipynb - - examples/detectree2.ipynb - - Workshops: - - workshops/purdue.ipynb - - workshops/cn_workshop.ipynb - - workshops/IPPN_2024.ipynb - - workshops/AIforGood_2025.ipynb - - API Reference: - - caption module: caption.md - - common module: common.md - - samgeo module: samgeo.md - - samgeo2 module: samgeo2.md - - samgeo3 module: samgeo3.md - - fast_sam module: fast_sam.md - - hq_sam module: hq_sam.md - - text_sam module: text_sam.md - - detectree2 module: detectree2.md - - api module: api.md - # - fer module: fer.md diff --git a/requirements_docs.txt b/requirements_docs.txt index 12f6e410..ff12b786 100644 --- a/requirements_docs.txt +++ b/requirements_docs.txt @@ -7,14 +7,6 @@ deadlink flake8 ipykernel livereload -mkdocs -mkdocs-git-revision-date-localized-plugin -mkdocs-git-revision-date-plugin -mkdocs-jupyter>=0.26.3 -mkdocs-material>=9.7.6 -mkdocs-pdf-export-plugin -mkdocstrings -mkdocstrings-crystal mkdocstrings-python nbconvert nbformat @@ -26,3 +18,4 @@ tox twine watchdog wheel +zensical diff --git a/scripts/convert_notebooks.py b/scripts/convert_notebooks.py new file mode 100644 index 00000000..c37cc16b --- /dev/null +++ b/scripts/convert_notebooks.py @@ -0,0 +1,73 @@ +"""Convert Jupyter notebooks in the docs folder to Markdown for Zensical. + +Zensical does not yet support the mkdocs-jupyter plugin, so this script +converts every notebook under ``docs/examples`` and ``docs/workshops`` to a +Markdown file of the same name before the site is built. A download link to +the original notebook (which Zensical copies into the site as a static file) +is prepended to each page. + +Usage: + python scripts/convert_notebooks.py +""" + +import pathlib +import sys + +import nbformat +from nbconvert import MarkdownExporter + +ROOT = pathlib.Path(__file__).resolve().parents[1] +NOTEBOOK_DIRS = ["docs/examples", "docs/workshops"] +REPO_URL = "https://github.com/opengeos/segment-geospatial" + + +def convert_notebook(nb_path: pathlib.Path) -> pathlib.Path: + """Convert a single notebook to a Markdown file alongside it. + + Args: + nb_path: Path to the ``.ipynb`` file to convert. + + Returns: + pathlib.Path: Path to the generated ``.md`` file. + """ + notebook = nbformat.read(nb_path, as_version=4) + exporter = MarkdownExporter() + body, _ = exporter.from_notebook_node(notebook) + + rel_path = nb_path.relative_to(ROOT) + header = ( + f"[![Download notebook]" + f"(https://img.shields.io/badge/Download-notebook-blue)]" + f"({REPO_URL}/blob/main/{rel_path.as_posix()})\n\n" + ) + + md_path = nb_path.with_suffix(".md") + md_path.write_text(header + body, encoding="utf-8") + return md_path + + +def main() -> None: + """Convert all notebooks in the configured docs directories. + + Conversion continues past individual failures so that one broken + notebook does not hide the status of the others; the script exits + with a non-zero status if any notebook failed to convert. + """ + failures = [] + for dir_name in NOTEBOOK_DIRS: + for nb_path in sorted((ROOT / dir_name).glob("*.ipynb")): + try: + md_path = convert_notebook(nb_path) + except Exception as e: + failures.append(nb_path) + print(f"FAILED to convert {nb_path.relative_to(ROOT)}: {e}") + else: + print(f"Converted {nb_path.relative_to(ROOT)} -> {md_path.name}") + + if failures: + print(f"{len(failures)} notebook(s) failed to convert.") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/zensical.toml b/zensical.toml new file mode 100644 index 00000000..5ce3a543 --- /dev/null +++ b/zensical.toml @@ -0,0 +1,109 @@ +[project] +site_name = "segment-geospatial" +site_url = "https://samgeo.gishub.org" +site_description = "A Python package for segmenting geospatial data with the Segment Anything Model (SAM)" +repo_url = "https://github.com/opengeos/segment-geospatial" + +nav = [ + { "Home" = "index.md" }, + { "Installation" = "installation.md" }, + { "Usage" = "usage.md" }, + { "REST API" = "api.md" }, + { "Contributing" = "contributing.md" }, + { "FAQ" = "faq.md" }, + { "Changelog" = "changelog.md" }, + { "Report Issues" = "https://github.com/opengeos/segment-geospatial/issues" }, + { "Examples" = [ + "examples/satellite.md", + "examples/automatic_mask_generator.md", + "examples/automatic_mask_generator_hq.md", + "examples/input_prompts.md", + "examples/input_prompts_hq.md", + "examples/box_prompts.md", + "examples/text_prompts.md", + "examples/text_prompts_batch.md", + "examples/fast_sam.md", + "examples/text_swimming_pools.md", + "examples/arcgis.md", + "examples/maxar_open_data.md", + "examples/sam2_automatic.md", + "examples/sam2_predictor.md", + "examples/sam2_video.md", + "examples/sam2_box_prompts.md", + "examples/sam2_point_prompts.md", + "examples/sam2_text_prompts.md", + "examples/tree_mapping.md", + "examples/image_captioning.md", + "examples/sam3_image_segmentation.md", + "examples/sam3_image_segmentation_jpg.md", + "examples/sam3_interactive.md", + "examples/sam3_batch_segmentation.md", + "examples/sam3_video_segmentation.md", + "examples/sam3_video_prompts.md", + "examples/sam3_video_masks.md", + "examples/sam3_automated_segmentation.md", + "examples/sam3_object_tracking.md", + "examples/sam3_point_prompts.md", + "examples/sam3_point_prompts_batch.md", + "examples/sam3_box_prompts.md", + "examples/sam3_tiled_segmentation.md", + "examples/detectree2.md", + ] }, + { "Workshops" = [ + "workshops/purdue.md", + "workshops/cn_workshop.md", + "workshops/IPPN_2024.md", + "workshops/AIforGood_2025.md", + ] }, + { "API Reference" = [ + { "caption module" = "caption.md" }, + { "common module" = "common.md" }, + { "samgeo module" = "samgeo.md" }, + { "samgeo2 module" = "samgeo2.md" }, + { "samgeo3 module" = "samgeo3.md" }, + { "fast_sam module" = "fast_sam.md" }, + { "hq_sam module" = "hq_sam.md" }, + { "text_sam module" = "text_sam.md" }, + { "detectree2 module" = "detectree2.md" }, + { "api module" = "api.md" }, + ] }, +] + +[project.theme] +features = [ + "content.code.copy", + "navigation.instant", + "navigation.top", + "search.highlight", +] + +[project.theme.icon] +repo = "fontawesome/brands/github" + +[[project.theme.palette]] +media = "(prefers-color-scheme: light)" +scheme = "default" +toggle.icon = "lucide/sun" +toggle.name = "Switch to dark mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: dark)" +scheme = "slate" +toggle.icon = "lucide/moon" +toggle.name = "Switch to light mode" + +[project.plugins.mkdocstrings.handlers.python] +paths = ["."] + +[project.plugins.mkdocstrings.handlers.python.options] +docstring_style = "google" + +[project.markdown_extensions.attr_list] + +[project.markdown_extensions.toc] +permalink = true + +[project.markdown_extensions.pymdownx.superfences] + +[project.markdown_extensions.pymdownx.highlight] +linenums = true