vendor: OpenCV 5.0.0 snapshot at 40738fb16ceddb5fb3fea747585f7ce6abb0605b
This commit is contained in:
@@ -0,0 +1,447 @@
|
||||
# This file is part of OpenCV project.
|
||||
# It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
# of this distribution and at http://opencv.org/license.html.
|
||||
# Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
# Third party copyrights are property of their respective owners.
|
||||
|
||||
"""Import-time orchestration: populate the shared indexes."""
|
||||
from __future__ import annotations
|
||||
import pathlib, re, os as _os, shutil as _shutil, textwrap as _textwrap
|
||||
from .state import *
|
||||
from .xml_render import _patch_namespace_xml_for_breathe
|
||||
from .stubs import _generate_api_stubs
|
||||
|
||||
|
||||
def _discover_orphan_groups(xml_dir):
|
||||
if not xml_dir.is_dir():
|
||||
return [], []
|
||||
folders = set()
|
||||
for _root in (OPENCV_ROOT / "modules", CONTRIB_ROOT):
|
||||
if _root.is_dir():
|
||||
folders.update(d.name for d in _root.iterdir()
|
||||
if (d / "include" / "opencv2").is_dir())
|
||||
skip = set(folders) | {f.replace("_", "__") for f in folders}
|
||||
skip |= {_module_group_stem(m) for m in folders}
|
||||
all_groups, child = set(), set()
|
||||
for gx in xml_dir.glob("group__*.xml"):
|
||||
all_groups.add(gx.stem)
|
||||
try:
|
||||
xml = gx.read_text(encoding="utf-8", errors="ignore")
|
||||
except OSError:
|
||||
continue
|
||||
child.update(re.findall(r'<innergroup refid="(group__[^"]+)"', xml))
|
||||
main, extra = [], []
|
||||
for g in sorted(all_groups - child):
|
||||
stem = g[len("group__"):]
|
||||
name = stem.replace("__", "_")
|
||||
if stem in skip or name in skip:
|
||||
continue
|
||||
xml = (xml_dir / f"{g}.xml").read_text(encoding="utf-8", errors="ignore")
|
||||
loc = re.search(r'<location file="([^"]*)"', xml)
|
||||
(extra if loc and "opencv_contrib/" in loc.group(1) else main).append(name)
|
||||
return main, extra
|
||||
|
||||
|
||||
# Skip when input root is DOC_ROOT: writing there is forbidden.
|
||||
if _BIB_ENTRIES_SORTED and SPHINX_INPUT_ROOT != DOC_ROOT:
|
||||
try:
|
||||
SPHINX_INPUT_ROOT.mkdir(parents=True, exist_ok=True)
|
||||
(SPHINX_INPUT_ROOT / "citelist.markdown").write_text(
|
||||
_bib_render_all(_BIB_ENTRIES_SORTED, _CITE_NUMBER),
|
||||
encoding="utf-8")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# Internal scan: enabled subtrees + standalone pages.
|
||||
_scan_internal(SPHINX_INPUT_ROOT / "tutorials" / "tutorials.markdown")
|
||||
for _m in DOC_MODULES:
|
||||
_scan_internal(SPHINX_INPUT_ROOT / "tutorials" / _m)
|
||||
if JS_DOC_MODULES:
|
||||
_scan_internal(DOC_ROOT / "js_tutorials" / "js_tutorials.markdown",
|
||||
base=DOC_ROOT)
|
||||
for _m in JS_DOC_MODULES:
|
||||
_scan_internal(DOC_ROOT / "js_tutorials" / _m, base=DOC_ROOT)
|
||||
if PY_DOC_MODULES:
|
||||
_scan_internal(DOC_ROOT / "py_tutorials" / "py_tutorials.markdown",
|
||||
base=DOC_ROOT)
|
||||
for _m in PY_DOC_MODULES:
|
||||
_scan_internal(DOC_ROOT / "py_tutorials" / _m, base=DOC_ROOT)
|
||||
|
||||
_contrib_dir = SPHINX_INPUT_ROOT / "tutorials_contrib"
|
||||
_contrib_root_md = next(
|
||||
(p for p in (_contrib_dir / "contrib_root.markdown",
|
||||
_contrib_dir / "tutorials_contrib.markdown") if p.is_file()),
|
||||
_contrib_dir / "contrib_root.markdown")
|
||||
if _contrib_root_md.is_file():
|
||||
_scan_internal(_contrib_root_md)
|
||||
for _m in CONTRIB_MODULES:
|
||||
_scan_internal(SPHINX_INPUT_ROOT / "tutorials_contrib" / _m)
|
||||
# Standalone top-level pages.
|
||||
_scan_internal(SPHINX_INPUT_ROOT / "faq.markdown")
|
||||
_scan_internal(SPHINX_INPUT_ROOT / "citelist.markdown")
|
||||
_scan_internal(SPHINX_INPUT_ROOT / "intro.markdown")
|
||||
|
||||
_IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".gif", ".svg", ".bmp", ".webp"}
|
||||
for _root in ((DOC_ROOT / "tutorials").rglob("images/*"),
|
||||
(DOC_ROOT / "js_tutorials").rglob("images/*"),
|
||||
(DOC_ROOT / "js_tutorials" / "js_assets").glob("*"),
|
||||
(DOC_ROOT / "py_tutorials").rglob("images/*"),
|
||||
(DOC_ROOT / "images").glob("*")):
|
||||
for _img in _root:
|
||||
if _img.is_file():
|
||||
_IMAGE_INDEX.setdefault(_img.name, _img.relative_to(DOC_ROOT).as_posix())
|
||||
for _m in CONTRIB_MODULES:
|
||||
# <m>/tutorials/**/images/*
|
||||
_tut = CONTRIB_ROOT / _m / "tutorials"
|
||||
if _tut.is_dir():
|
||||
for _img in _tut.rglob("images/*"):
|
||||
if _img.is_file():
|
||||
_rel = _img.relative_to(_tut).as_posix()
|
||||
_IMAGE_INDEX.setdefault(_img.name,
|
||||
f"tutorials_contrib/{_m}/{_rel}")
|
||||
# Contrib images outside <m>/tutorials/.
|
||||
for _sub in ("doc", "samples"):
|
||||
_src = CONTRIB_ROOT / _m / _sub
|
||||
if _src.is_dir():
|
||||
for _img in _src.rglob("*"):
|
||||
if _img.is_file() and _img.suffix.lower() in _IMAGE_EXTS:
|
||||
_rel = _img.relative_to(CONTRIB_ROOT).as_posix()
|
||||
_IMAGE_INDEX.setdefault(_img.name,
|
||||
f"contrib_modules/{_rel}")
|
||||
|
||||
if API_MODULES:
|
||||
_api_pics = SPHINX_INPUT_ROOT / "api_pics"
|
||||
_stage_pics = SPHINX_INPUT_ROOT != DOC_ROOT
|
||||
if _stage_pics:
|
||||
_api_pics.mkdir(parents=True, exist_ok=True)
|
||||
_modules_root = DOC_ROOT.parent / "modules"
|
||||
if _modules_root.is_dir():
|
||||
for _doc_dir in sorted(_modules_root.glob("*/doc")):
|
||||
for _img in _doc_dir.rglob("*"):
|
||||
if not (_img.is_file() and _img.suffix.lower() in _IMAGE_EXTS):
|
||||
continue
|
||||
if _img.name in _IMAGE_INDEX:
|
||||
continue
|
||||
_IMAGE_INDEX[_img.name] = f"api_pics/{_img.name}"
|
||||
if _stage_pics:
|
||||
_link = _api_pics / _img.name
|
||||
if not _link.exists():
|
||||
try:
|
||||
_os.symlink(_img, _link)
|
||||
except (OSError, NotImplementedError):
|
||||
try:
|
||||
_shutil.copy2(_img, _link)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if _API_XML_DIR.is_dir():
|
||||
_patch_namespace_xml_for_breathe(_API_XML_DIR, _PATCHED_XML_DIR)
|
||||
|
||||
from conf_helpers.state import OPENCV_ROOT, CONTRIB_ROOT
|
||||
_is_contrib = lambda m: (CONTRIB_ROOT / m).is_dir() and not (
|
||||
OPENCV_ROOT / "modules" / m).is_dir()
|
||||
_main_api = [m for m in API_MODULES if not _is_contrib(m)]
|
||||
_extra_api = [m for m in API_MODULES if _is_contrib(m)]
|
||||
_main_orphans, _extra_orphans = _discover_orphan_groups(_API_XML_DIR)
|
||||
_generate_api_stubs(_main_api, _API_XML_DIR, SPHINX_INPUT_ROOT / "main_modules",
|
||||
root_anchor="api_root", root_title="Main modules",
|
||||
extra_groups=_main_orphans)
|
||||
_scan_internal(SPHINX_INPUT_ROOT / "main_modules")
|
||||
if _extra_api or _extra_orphans:
|
||||
_generate_api_stubs(_extra_api, _API_XML_DIR, SPHINX_INPUT_ROOT / "extra_modules",
|
||||
root_anchor="extra_api_root", root_title="Extra modules",
|
||||
extra_groups=_extra_orphans)
|
||||
_scan_internal(SPHINX_INPUT_ROOT / "extra_modules")
|
||||
|
||||
|
||||
def _write_root_index() -> None:
|
||||
"""Generate the Sphinx landing page at ``index.html``.
|
||||
|
||||
The legacy tutorials root remains focused on C++ tutorials. Cross-family
|
||||
entry points live here so the site root no longer redirects users straight
|
||||
to ``tutorials/tutorials.html``.
|
||||
|
||||
Each entry renders as a section heading (the category) with the page link
|
||||
on the line beneath it. FAQ and Bibliography are direct links whose heading
|
||||
*is* the link. A hidden toctree mirrors the same order to drive the sidebar.
|
||||
"""
|
||||
if SPHINX_INPUT_ROOT == DOC_ROOT:
|
||||
return
|
||||
|
||||
entries: list[tuple[str, str | None, str]] = []
|
||||
|
||||
def add(heading: str, link_text: str | None, docname: str,
|
||||
condition: bool = True) -> None:
|
||||
if condition:
|
||||
entries.append((heading, link_text, docname))
|
||||
|
||||
add("Introduction", "Introduction", "intro", "intro" in _ANCHOR_TO_DOC)
|
||||
add("OpenCV Tutorials", "OpenCV tutorials", "tutorials/tutorials")
|
||||
add("Python Tutorials", "OpenCV-Python tutorials",
|
||||
"py_tutorials/py_tutorials", bool(PY_DOC_MODULES))
|
||||
add("Javascript Tutorials", "OpenCV.js tutorials",
|
||||
"js_tutorials/js_tutorials", bool(JS_DOC_MODULES))
|
||||
add("Contrib Tutorials", "Tutorials for contrib module",
|
||||
f"tutorials_contrib/{_contrib_root_md.stem}",
|
||||
bool(CONTRIB_MODULES) and _contrib_root_md.is_file())
|
||||
add("Main modules", "Documentation for main modules",
|
||||
"main_modules/api_root", "api_root" in _ANCHOR_TO_DOC)
|
||||
add("Extra modules", "Documentation for extra modules",
|
||||
"extra_modules/api_root", "extra_api_root" in _ANCHOR_TO_DOC)
|
||||
add("Frequently Asked Questions", None, "faq", "faq" in _ANCHOR_TO_DOC)
|
||||
add("Bibliography", None, "citelist", "citelist" in _ANCHOR_TO_DOC)
|
||||
|
||||
toctree = "\n".join(
|
||||
f"{heading} <{docname}>" for heading, _link, docname in entries)
|
||||
|
||||
# Body: raw HTML so links resolve correctly relative to index.html.
|
||||
html_lines = ['<div class="ocv-landing">']
|
||||
for heading, link_text, docname in entries:
|
||||
if link_text is None:
|
||||
html_lines.append(
|
||||
f'<h2><a href="{docname}.html">{heading}</a></h2>')
|
||||
else:
|
||||
html_lines.append(f'<h2>{heading}</h2>')
|
||||
html_lines.append(f'<p><a href="{docname}.html">{link_text}</a></p>')
|
||||
html_lines.append("</div>")
|
||||
body = "\n".join(html_lines)
|
||||
|
||||
text = (
|
||||
"OpenCV modules\n"
|
||||
"==============\n\n"
|
||||
"```{toctree}\n"
|
||||
":hidden:\n"
|
||||
":maxdepth: 1\n"
|
||||
":titlesonly:\n\n"
|
||||
f"{toctree}\n"
|
||||
"```\n\n"
|
||||
f"{body}\n"
|
||||
)
|
||||
try:
|
||||
(SPHINX_INPUT_ROOT / "index.markdown").write_text(text, encoding="utf-8")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _write_related_pages_index() -> None:
|
||||
"""Generate `related_pages.markdown` — the local analog of Doxygen's
|
||||
pages.html (the header "Related Pages" target).
|
||||
|
||||
Lists every standalone documentation page (\\page) that has a *local*
|
||||
Sphinx docname, so nothing points off-site. Titles and the canonical set
|
||||
come from the Doxygen tag page index (`_DOC_PAGE_TITLES`); a page is
|
||||
emitted only when its name resolves through `_ANCHOR_TO_DOC`, so the list
|
||||
contains exactly what this build actually rendered and grows automatically
|
||||
as more modules are enabled. Marked `orphan` — reached via the header link,
|
||||
not the sidebar toctree (intro/faq/citelist already live in the index toc).
|
||||
"""
|
||||
if SPHINX_INPUT_ROOT == DOC_ROOT:
|
||||
return
|
||||
rows: list[tuple[str, str]] = [] # (title, docname)
|
||||
seen: set[str] = set()
|
||||
|
||||
def add(anchor: str) -> None:
|
||||
doc = _ANCHOR_TO_DOC.get(anchor)
|
||||
if doc and anchor not in seen:
|
||||
title = (_DOC_PAGE_TITLES.get(anchor)
|
||||
or _ANCHOR_TO_TITLE.get(anchor) or anchor)
|
||||
rows.append((title, doc))
|
||||
seen.add(anchor)
|
||||
|
||||
# Core standalone pages first, in a stable, friendly order.
|
||||
for _a in ("intro", "faq", "citelist"):
|
||||
add(_a)
|
||||
# Then every other \page that resolves locally, alphabetical by title.
|
||||
for _name in sorted(_DOC_PAGE_TITLES,
|
||||
key=lambda n: (_DOC_PAGE_TITLES.get(n) or n).lower()):
|
||||
add(_name)
|
||||
|
||||
items = "\n".join(f'<li><a href="{_d}.html">{_t}</a></li>' for _t, _d in rows)
|
||||
text = (
|
||||
"---\norphan: true\n---\n"
|
||||
"# Related Pages\n\n"
|
||||
"All standalone documentation pages available in this build.\n\n"
|
||||
f'<ul class="ocv-related-pages">\n{items}\n</ul>\n'
|
||||
)
|
||||
try:
|
||||
(SPHINX_INPUT_ROOT / "related_pages.markdown").write_text(
|
||||
text, encoding="utf-8")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _write_examples_index() -> None:
|
||||
"""Generate `examples/examples_root.markdown` — the local analog of
|
||||
Doxygen's examples.html (the header "Examples" target).
|
||||
|
||||
The per-sample example pages are orphan pages reached from class "Examples"
|
||||
blocks; this index links them all in one place. Sourced from
|
||||
`_EXAMPLE_PAGES_NEEDED` (populated during API-stub generation), so it lists
|
||||
exactly the samples this build emitted. Also `orphan` (header-only entry).
|
||||
"""
|
||||
if SPHINX_INPUT_ROOT == DOC_ROOT:
|
||||
return
|
||||
from .examples import _EXAMPLE_PAGES_NEEDED, _example_pagename
|
||||
if not _EXAMPLE_PAGES_NEEDED:
|
||||
return
|
||||
items = "\n".join(
|
||||
f'<li><a href="{_example_pagename(_d)}.html">{_d}</a></li>'
|
||||
for _d in sorted(_EXAMPLE_PAGES_NEEDED))
|
||||
text = (
|
||||
"---\norphan: true\n---\n"
|
||||
"# Examples\n\n"
|
||||
"All example programs referenced in the API documentation.\n\n"
|
||||
f'<ul class="ocv-examples-index">\n{items}\n</ul>\n'
|
||||
)
|
||||
try:
|
||||
(SPHINX_INPUT_ROOT / "examples").mkdir(parents=True, exist_ok=True)
|
||||
(SPHINX_INPUT_ROOT / "examples" / "examples_root.markdown").write_text(
|
||||
text, encoding="utf-8")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _esc(s: str) -> str:
|
||||
"""Minimal HTML escape for brief text injected into the index <li> markup."""
|
||||
return (s or "").replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
def _write_namespace_list_index() -> None:
|
||||
"""Generate `namespace_list.markdown` — local analog of Doxygen's
|
||||
namespaces.html (the header "Namespaces" target).
|
||||
|
||||
Renders the namespace tree (cv → cv::cuda → …) as a nested list, each node
|
||||
linking to its local namespace page with the brief description alongside.
|
||||
Intermediate namespaces with no page of their own render as plain text.
|
||||
Sourced from `_ALL_NAMESPACES` (populated during API-stub generation).
|
||||
"""
|
||||
if SPHINX_INPUT_ROOT == DOC_ROOT or not _ALL_NAMESPACES:
|
||||
return
|
||||
# Nested tree keyed by path component; each node tracks its full name.
|
||||
tree: dict = {}
|
||||
for _name in _ALL_NAMESPACES:
|
||||
node = tree
|
||||
parts = _name.split("::")
|
||||
for _i, _part in enumerate(parts):
|
||||
node = node.setdefault(
|
||||
_part, {"_full": "::".join(parts[:_i + 1]), "_kids": {}})["_kids"]
|
||||
|
||||
def render(node: dict) -> list[str]:
|
||||
out = ["<ul>"]
|
||||
for _part in sorted(node, key=str.lower):
|
||||
child = node[_part]
|
||||
info = _ALL_NAMESPACES.get(child["_full"])
|
||||
if info:
|
||||
label = f'<a href="{info["docname"]}.html">{_part}</a>'
|
||||
if info.get("brief"):
|
||||
label += f' — {_esc(info["brief"])}'
|
||||
else:
|
||||
label = _part
|
||||
out.append(f"<li>{label}")
|
||||
if child["_kids"]:
|
||||
out += render(child["_kids"])
|
||||
out.append("</li>")
|
||||
out.append("</ul>")
|
||||
return out
|
||||
|
||||
text = (
|
||||
"---\norphan: true\n---\n"
|
||||
"# Namespace List\n\n"
|
||||
"Here is a list of all documented namespaces with brief descriptions.\n\n"
|
||||
f'<div class="ocv-namespace-list">\n{chr(10).join(render(tree))}\n</div>\n'
|
||||
)
|
||||
try:
|
||||
(SPHINX_INPUT_ROOT / "namespace_list.markdown").write_text(
|
||||
text, encoding="utf-8")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _write_class_list_index() -> None:
|
||||
"""Generate `class_list.markdown` — local analog of Doxygen's annotated.html
|
||||
(the header "Classes" target).
|
||||
|
||||
Lists every documented class/struct grouped by its enclosing namespace,
|
||||
each linking to its local page with the brief description. Sourced from
|
||||
`_ALL_CLASSES` (populated during API-stub generation).
|
||||
"""
|
||||
if SPHINX_INPUT_ROOT == DOC_ROOT or not _ALL_CLASSES:
|
||||
return
|
||||
by_ns: dict[str, list[tuple[str, dict]]] = {}
|
||||
for _info in _ALL_CLASSES.values():
|
||||
qualified = _info.get("qualified", "")
|
||||
if not qualified:
|
||||
continue
|
||||
ns, _, leaf = qualified.rpartition("::")
|
||||
by_ns.setdefault(ns, []).append((leaf, _info))
|
||||
|
||||
body = ['<ul class="ocv-class-list">']
|
||||
for ns in sorted(by_ns, key=lambda n: (n == "", n.lower())):
|
||||
heading = ns if ns else "(global namespace)"
|
||||
ns_info = _ALL_NAMESPACES.get(ns)
|
||||
if ns_info:
|
||||
heading = f'<a href="{ns_info["docname"]}.html">{ns}</a>'
|
||||
body.append(f"<li><b>{heading}</b>")
|
||||
body.append("<ul>")
|
||||
for leaf, info in sorted(by_ns[ns], key=lambda t: t[0].lower()):
|
||||
entry = f'<a href="{info["docname"]}.html">{leaf}</a>'
|
||||
if info.get("brief"):
|
||||
entry += f' — {_esc(info["brief"])}'
|
||||
body.append(f"<li>{entry}</li>")
|
||||
body.append("</ul></li>")
|
||||
body.append("</ul>")
|
||||
|
||||
text = (
|
||||
"---\norphan: true\n---\n"
|
||||
"# Class List\n\n"
|
||||
"Here are the classes, structs and unions with brief descriptions.\n\n"
|
||||
f'<div class="ocv-class-list-wrap">\n{chr(10).join(body)}\n</div>\n'
|
||||
)
|
||||
try:
|
||||
(SPHINX_INPUT_ROOT / "class_list.markdown").write_text(
|
||||
text, encoding="utf-8")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
_write_root_index()
|
||||
_write_related_pages_index()
|
||||
if API_MODULES:
|
||||
_write_examples_index()
|
||||
_write_namespace_list_index()
|
||||
_write_class_list_index()
|
||||
|
||||
for _toc in (DOC_ROOT / "tutorials").glob("*/table_of_content_*.markdown"):
|
||||
if _toc.parent.name not in DOC_MODULES:
|
||||
_scan_external(_toc)
|
||||
# Same for js_tutorials (files are named js_table_of_contents_*.markdown there).
|
||||
for _toc in (DOC_ROOT / "js_tutorials").glob("*/js_table_of_contents_*.markdown"):
|
||||
if _toc.parent.name not in JS_DOC_MODULES:
|
||||
_scan_external(_toc)
|
||||
# py_tutorials uses the `py_table_of_contents_*.markdown` naming variant.
|
||||
for _toc in (DOC_ROOT / "py_tutorials").glob("*/py_table_of_contents_*.markdown"):
|
||||
if _toc.parent.name not in PY_DOC_MODULES:
|
||||
_scan_external(_toc)
|
||||
|
||||
_REFERENCED_ANCHORS.update({
|
||||
"intro", "faq", "citelist",
|
||||
"tutorial_js_root", "tutorial_py_root", "tutorial_contrib_root",
|
||||
"api_root", "extra_api_root",
|
||||
})
|
||||
|
||||
# Snippet basename index (mirrors Doxygen EXAMPLE_RECURSIVE lookup).
|
||||
_SNIPPET_EXTENSIONS = {
|
||||
".cpp", ".hpp", ".h", ".c", ".cc", ".cxx",
|
||||
".py", ".java", ".kt", ".scala", ".clj", ".groovy",
|
||||
".sh", ".bash", ".bat", ".ps1",
|
||||
".cmake", ".gradle",
|
||||
".xml", ".yaml", ".yml", ".json", ".html", ".css",
|
||||
".js", ".ts", ".rb",
|
||||
}
|
||||
_snippet_scan_roots = [OPENCV_ROOT / "samples", OPENCV_ROOT / "apps"] + [
|
||||
CONTRIB_ROOT / _m / "samples" for _m in CONTRIB_MODULES]
|
||||
for _root in _snippet_scan_roots:
|
||||
if _root.is_dir():
|
||||
for _f in _root.rglob("*"):
|
||||
if _f.is_file() and _f.suffix.lower() in _SNIPPET_EXTENSIONS:
|
||||
_SNIPPET_INDEX.setdefault(_f.name, _f)
|
||||
Reference in New Issue
Block a user