conf.py 16.9 KB
Newer Older
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3
#
# PyTorch documentation build configuration file, created by
# sphinx-quickstart on Fri Dec 23 13:31:47 2016.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
22

23
import os
24
import sys
25
import textwrap
26
from copy import copy
27
from pathlib import Path
28

Brian Johnson's avatar
Brian Johnson committed
29
import pytorch_sphinx_theme
30
import torchvision
31
32
import torchvision.models as M
from tabulate import tabulate
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
33

34
sys.path.append(os.path.abspath("."))
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
35

36
torchvision.disable_beta_transforms_warning()
Nicolas Hug's avatar
Nicolas Hug committed
37
import torchvision.datapoints  # Don't remove, otherwise the docs for datapoints aren't linked properly
38

Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
39
40
# -- General configuration ------------------------------------------------

41
# Required version of sphinx is set from docs/requirements.txt
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
42
43
44
45
46

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
47
48
49
50
51
52
53
54
55
56
57
    "sphinx.ext.autodoc",
    "sphinx.ext.autosummary",
    "sphinx.ext.doctest",
    "sphinx.ext.intersphinx",
    "sphinx.ext.todo",
    "sphinx.ext.mathjax",
    "sphinx.ext.napoleon",
    "sphinx.ext.viewcode",
    "sphinx.ext.duration",
    "sphinx_gallery.gen_gallery",
    "sphinx_copybutton",
58
    "beta_status",
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
59
60
]

61
sphinx_gallery_conf = {
62
63
64
65
    "examples_dirs": "../../gallery/",  # path to your example scripts
    "gallery_dirs": "auto_examples",  # path to where to save gallery generated output
    "backreferences_dir": "gen_modules/backreferences",
    "doc_module": ("torchvision",),
66
    "remove_config_comments": True,
67
68
}

Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
69
napoleon_use_ivar = True
70
71
napoleon_numpy_docstring = False
napoleon_google_docstring = True
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
72

73

Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
74
# Add any paths that contain templates here, relative to this directory.
75
templates_path = ["_templates"]
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
76
77
78
79

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
80
source_suffix = {
81
    ".rst": "restructuredtext",
82
}
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
83
84

# The master toctree document.
85
master_doc = "index"
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
86
87

# General information about the project.
88
89
90
project = "Torchvision"
copyright = "2017-present, Torch Contributors"
author = "Torch Contributors"
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
91
92
93
94

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
95
96
97
# version: The short X.Y version.
# release: The full version, including alpha/beta/rc tags.
if os.environ.get("TORCHVISION_SANITIZE_VERSION_STR_IN_DOCS", None):
98
    # Turn 1.11.0aHASH into 1.11 (major.minor only)
99
    version = release = ".".join(torchvision.__version__.split(".")[:2])
100
    html_title = " ".join((project, version, "documentation"))
101
102
103
else:
    version = f"main ({torchvision.__version__})"
    release = "main"
104

Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
105
106
107
108
109
110

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
111
language = "en"
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
112
113
114
115
116
117
118

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = []

# The name of the Pygments (syntax highlighting) style to use.
119
pygments_style = "sphinx"
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
120
121
122
123
124
125
126
127
128
129

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True


# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
#
130
html_theme = "pytorch_sphinx_theme"
Brian Johnson's avatar
Brian Johnson committed
131
html_theme_path = [pytorch_sphinx_theme.get_html_theme_path()]
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
132
133
134
135
136
137

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {
138
139
140
141
142
143
    "collapse_navigation": False,
    "display_version": True,
    "logo_only": True,
    "pytorch_project": "docs",
    "navigation_with_keys": True,
    "analytics_id": "UA-117752657-2",
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
144
145
}

146
html_logo = "_static/img/pytorch-logo-dark.svg"
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
147
148
149
150

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
151
html_static_path = ["_static"]
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
152

153
154
# TODO: remove this once https://github.com/pytorch/pytorch_sphinx_theme/issues/125 is fixed
html_css_files = [
155
    "css/custom_torchvision.css",
156
157
]

Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
158
159
160
# -- Options for HTMLHelp output ------------------------------------------

# Output file base name for HTML help builder.
161
htmlhelp_basename = "PyTorchdoc"
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
162
163


164
165
166
autosummary_generate = True


Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
    # The paper size ('letterpaper' or 'a4paper').
    #
    # 'papersize': 'letterpaper',
    # The font size ('10pt', '11pt' or '12pt').
    #
    # 'pointsize': '10pt',
    # Additional stuff for the LaTeX preamble.
    #
    # 'preamble': '',
    # Latex figure (float) alignment
    #
    # 'figure_align': 'htbp',
}

183

Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
184
185
186
187
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
188
    (master_doc, "pytorch.tex", "torchvision Documentation", "Torch Contributors", "manual"),
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
189
190
191
192
193
194
195
]


# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
196
man_pages = [(master_doc, "torchvision", "torchvision Documentation", [author], 1)]
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
197
198
199
200
201
202
203
204


# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
205
206
207
208
209
210
211
212
213
    (
        master_doc,
        "torchvision",
        "torchvision Documentation",
        author,
        "torchvision",
        "One line description of project.",
        "Miscellaneous",
    ),
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
214
215
216
217
218
]


# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
219
    "python": ("https://docs.python.org/3/", None),
220
    "torch": ("https://pytorch.org/docs/stable/", None),
221
    "numpy": ("https://numpy.org/doc/stable/", None),
222
223
    "PIL": ("https://pillow.readthedocs.io/en/stable/", None),
    "matplotlib": ("https://matplotlib.org/stable/", None),
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
224
225
226
227
228
229
230
}

# -- A patch that prevents Sphinx from cross-referencing ivar tags -------
# See http://stackoverflow.com/a/41184353/3343043

from docutils import nodes
from sphinx import addnodes
231
from sphinx.util.docfields import TypedField
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
232
233
234
235
236
237


def patched_make_field(self, types, domain, items, **kw):
    # `kw` catches `env=None` needed for newer sphinx while maintaining
    #  backwards compatibility when passed along further down!

eellison's avatar
eellison committed
238
    # type: (list, unicode, tuple) -> nodes.field  # noqa: F821
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
239
240
    def handle_item(fieldarg, content):
        par = nodes.paragraph()
241
        par += addnodes.literal_strong("", fieldarg)  # Patch: this line added
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
242
243
244
        # par.extend(self.make_xrefs(self.rolename, domain, fieldarg,
        #                           addnodes.literal_strong))
        if fieldarg in types:
245
            par += nodes.Text(" (")
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
246
247
248
249
250
            # NOTE: using .pop() here to prevent a single type node to be
            # inserted twice into the doctree, which leads to
            # inconsistencies later when references are resolved
            fieldtype = types.pop(fieldarg)
            if len(fieldtype) == 1 and isinstance(fieldtype[0], nodes.Text):
251
252
253
254
255
256
                typename = "".join(n.astext() for n in fieldtype)
                typename = typename.replace("int", "python:int")
                typename = typename.replace("long", "python:long")
                typename = typename.replace("float", "python:float")
                typename = typename.replace("type", "python:type")
                par.extend(self.make_xrefs(self.typerolename, domain, typename, addnodes.literal_emphasis, **kw))
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
257
258
            else:
                par += fieldtype
259
260
            par += nodes.Text(")")
        par += nodes.Text(" -- ")
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
261
262
263
        par += content
        return par

264
    fieldname = nodes.field_name("", self.label)
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
265
266
267
268
269
270
    if len(items) == 1 and self.can_collapse:
        fieldarg, content = items[0]
        bodynode = handle_item(fieldarg, content)
    else:
        bodynode = self.list_type()
        for fieldarg, content in items:
271
272
273
            bodynode += nodes.list_item("", handle_item(fieldarg, content))
    fieldbody = nodes.field_body("", bodynode)
    return nodes.field("", fieldname, fieldbody)
Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
274

Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
275

Sasank Chilamkurthy's avatar
Sasank Chilamkurthy committed
276
TypedField.make_field = patched_make_field
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304


def inject_minigalleries(app, what, name, obj, options, lines):
    """Inject a minigallery into a docstring.

    This avoids having to manually write the .. minigallery directive for every item we want a minigallery for,
    as it would be easy to miss some.

    This callback is called after the .. auto directives (like ..autoclass) have been processed,
    and modifies the lines parameter inplace to add the .. minigallery that will show which examples
    are using which object.

    It's a bit hacky, but not *that* hacky when you consider that the recommended way is to do pretty much the same,
    but instead with templates using autosummary (which we don't want to use):
    (https://sphinx-gallery.github.io/stable/configuration.html#auto-documenting-your-api-with-links-to-examples)

    For docs on autodoc-process-docstring, see the autodoc docs:
    https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html
    """

    if what in ("class", "function"):
        lines.append(f".. minigallery:: {name}")
        lines.append(f"    :add-heading: Examples using ``{name.split('.')[-1]}``:")
        # avoid heading entirely to avoid warning. As a bonud it actually renders better
        lines.append("    :heading-level: 9")
        lines.append("\n")


305
def inject_weight_metadata(app, what, name, obj, options, lines):
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
    """This hook is used to generate docs for the models weights.

    Objects like ResNet18_Weights are enums with fields, where each field is a Weight object.
    Enums aren't easily documented in Python so the solution we're going for is to:

    - add an autoclass directive in the model's builder docstring, e.g.

    ```
    .. autoclass:: torchvision.models.ResNet34_Weights
        :members:
    ```

    (see resnet.py for an example)
    - then this hook is called automatically when building the docs, and it generates the text that gets
      used within the autoclass directive.
    """
322

323
    if obj.__name__.endswith(("_Weights", "_QuantizedWeights")):
324
325
326
327
328

        if len(obj) == 0:
            lines[:] = ["There are no available pre-trained weights."]
            return

329
330
        lines[:] = [
            "The model builder above accepts the following values as the ``weights`` parameter.",
331
332
            f"``{obj.__name__}.DEFAULT`` is equivalent to ``{obj.DEFAULT}``. You can also use strings, e.g. "
            f"``weights='DEFAULT'`` or ``weights='{str(list(obj)[0]).split('.')[1]}'``.",
333
        ]
334
335

        if obj.__doc__ != "An enumeration.":
336
            # We only show the custom enum doc if it was overridden. The default one from Python is "An enumeration"
337
338
339
            lines.append("")
            lines.append(obj.__doc__)

340
        lines.append("")
341

342
        for field in obj:
343
            meta = copy(field.meta)
344

345
346
            lines += [f"**{str(field)}**:", ""]
            lines += [meta.pop("_docs")]
347
348
349
350

            if field == obj.DEFAULT:
                lines += [f"Also available as ``{obj.__name__}.DEFAULT``."]
            lines += [""]
351

352
353
354
355
356
357
358
            table = []
            metrics = meta.pop("_metrics")
            for dataset, dataset_metrics in metrics.items():
                for metric_name, metric_value in dataset_metrics.items():
                    table.append((f"{metric_name} (on {dataset})", str(metric_value)))

            for k, v in meta.items():
359
                if k in {"recipe", "license"}:
360
                    v = f"`link <{v}>`__"
361
362
                elif k == "min_size":
                    v = f"height={v[0]}, width={v[1]}"
363
364
365
366
                elif k in {"categories", "keypoint_names"} and isinstance(v, list):
                    max_visible = 3
                    v_sample = ", ".join(v[:max_visible])
                    v = f"{v_sample}, ... ({len(v)-max_visible} omitted)" if len(v) > max_visible else v_sample
367
                elif k == "_ops":
Nicolas Hug's avatar
Nicolas Hug committed
368
369
370
371
372
                    v = f"{v:.2f}"
                    k = "GIPS" if obj.__name__.endswith("_QuantizedWeights") else "GFLOPS"
                elif k == "_file_size":
                    k = "File size"
                    v = f"{v:.1f} MB"
373

374
375
                table.append((str(k), str(v)))
            table = tabulate(table, tablefmt="rst")
376
            lines += [".. rst-class:: table-weights"]  # Custom CSS class, see custom_torchvision.css
377
378
379
            lines += [".. table::", ""]
            lines += textwrap.indent(table, " " * 4).split("\n")
            lines.append("")
380
            lines.append(
381
382
                f"The inference transforms are available at ``{str(field)}.transforms`` and "
                f"perform the following preprocessing operations: {field.transforms().describe()}"
383
384
            )
            lines.append("")
385
386


387
def generate_weights_table(module, table_name, metrics, dataset, include_patterns=None, exclude_patterns=None):
388
389
    weights_endswith = "_QuantizedWeights" if module.__name__.split(".")[-1] == "quantization" else "_Weights"
    weight_enums = [getattr(module, name) for name in dir(module) if name.endswith(weights_endswith)]
390
391
    weights = [w for weight_enum in weight_enums for w in weight_enum]

392
393
394
395
    if include_patterns is not None:
        weights = [w for w in weights if any(p in str(w) for p in include_patterns)]
    if exclude_patterns is not None:
        weights = [w for w in weights if all(p not in str(w) for p in exclude_patterns)]
396

397
398
    ops_name = "GIPS" if "QuantizedWeights" in weights_endswith else "GFLOPS"

399
    metrics_keys, metrics_names = zip(*metrics)
Nicolas Hug's avatar
Nicolas Hug committed
400
    column_names = ["Weight"] + list(metrics_names) + ["Params"] + [ops_name, "Recipe"]  # Final column order
401
402
    column_names = [f"**{name}**" for name in column_names]  # Add bold

403
404
405
    content = []
    for w in weights:
        row = [
406
            f":class:`{w} <{type(w).__name__}>`",
407
            *(w.meta["_metrics"][dataset][metric] for metric in metrics_keys),
408
            f"{w.meta['num_params']/1e6:.1f}M",
Nicolas Hug's avatar
Nicolas Hug committed
409
            f"{w.meta['_ops']:.2f}",
410
            f"`link <{w.meta['recipe']}>`__",
411
412
413
414
        ]

        content.append(row)

Nicolas Hug's avatar
Nicolas Hug committed
415
    column_widths = ["110"] + ["18"] * len(metrics_names) + ["18"] * 2 + ["10"]
416
417
    widths_table = " ".join(column_widths)

418
419
420
421
    table = tabulate(content, headers=column_names, tablefmt="rst")

    generated_dir = Path("generated")
    generated_dir.mkdir(exist_ok=True)
422
    with open(generated_dir / f"{table_name}_table.rst", "w+") as table_file:
423
        table_file.write(".. rst-class:: table-weights\n")  # Custom CSS class, see custom_torchvision.css
424
        table_file.write(".. table::\n")
425
        table_file.write(f"    :widths: {widths_table} \n\n")
426
427
428
        table_file.write(f"{textwrap.indent(table, ' ' * 4)}\n\n")


429
generate_weights_table(
430
431
432
433
434
435
436
    module=M, table_name="classification", metrics=[("acc@1", "Acc@1"), ("acc@5", "Acc@5")], dataset="ImageNet-1K"
)
generate_weights_table(
    module=M.quantization,
    table_name="classification_quant",
    metrics=[("acc@1", "Acc@1"), ("acc@5", "Acc@5")],
    dataset="ImageNet-1K",
437
)
438
generate_weights_table(
439
440
441
442
443
    module=M.detection,
    table_name="detection",
    metrics=[("box_map", "Box MAP")],
    exclude_patterns=["Mask", "Keypoint"],
    dataset="COCO-val2017",
444
445
446
447
448
)
generate_weights_table(
    module=M.detection,
    table_name="instance_segmentation",
    metrics=[("box_map", "Box MAP"), ("mask_map", "Mask MAP")],
449
    dataset="COCO-val2017",
450
    include_patterns=["Mask"],
451
452
453
454
455
)
generate_weights_table(
    module=M.detection,
    table_name="detection_keypoint",
    metrics=[("box_map", "Box MAP"), ("kp_map", "Keypoint MAP")],
456
    dataset="COCO-val2017",
457
    include_patterns=["Keypoint"],
458
)
459
generate_weights_table(
460
461
462
463
464
465
466
    module=M.segmentation,
    table_name="segmentation",
    metrics=[("miou", "Mean IoU"), ("pixel_acc", "pixelwise Acc")],
    dataset="COCO-val2017-VOC-labels",
)
generate_weights_table(
    module=M.video, table_name="video", metrics=[("acc@1", "Acc@1"), ("acc@5", "Acc@5")], dataset="Kinetics-400"
467
)
468
469


470
def setup(app):
471

472
    app.connect("autodoc-process-docstring", inject_minigalleries)
473
    app.connect("autodoc-process-docstring", inject_weight_metadata)