From 52ecb265c4265856b0ae667f15d0eb6b1e62d663 Mon Sep 17 00:00:00 2001 From: Burgon Date: Tue, 31 Oct 2023 12:36:35 -0400 Subject: [PATCH] Documentation update --- docs/.buildinfo | 4 + docs/Makefile | 2 + docs/_sources/args.rst.txt | 50 ++ docs/_sources/index.rst.txt | 45 ++ docs/_sources/info.rst.txt | 30 + docs/_sources/src.rst.txt | 30 + docs/_static/alabaster.css | 703 +++++++++++++++++++ docs/_static/basic.css | 903 +++++++++++++++++++++++++ docs/_static/custom.css | 1 + docs/_static/doctools.js | 156 +++++ docs/_static/documentation_options.js | 14 + docs/_static/file.png | Bin 0 -> 286 bytes docs/_static/language_data.js | 199 ++++++ docs/_static/minus.png | Bin 0 -> 90 bytes docs/_static/plus.png | Bin 0 -> 90 bytes docs/_static/pygments.css | 83 +++ docs/_static/searchtools.js | 566 ++++++++++++++++ docs/_static/sphinx_highlight.js | 144 ++++ docs/args.html | 163 +++++ docs/build/doctrees/args.doctree | Bin 24588 -> 24522 bytes docs/build/doctrees/environment.pickle | Bin 314930 -> 299623 bytes docs/build/doctrees/index.doctree | Bin 13929 -> 13881 bytes docs/build/doctrees/src.doctree | Bin 163892 -> 163100 bytes docs/build/html/.buildinfo | 2 +- docs/build/html/_sources/index.rst.txt | 2 +- docs/build/html/index.html | 2 +- docs/build/html/objects.inv | Bin 777 -> 745 bytes docs/build/html/searchindex.js | 2 +- docs/genindex.html | 215 ++++++ docs/index.html | 159 +++++ docs/info.html | 114 ++++ docs/objects.inv | Bin 0 -> 745 bytes docs/py-modindex.html | 117 ++++ docs/search.html | 101 +++ docs/searchindex.js | 1 + docs/src.html | 379 +++++++++++ 36 files changed, 4183 insertions(+), 4 deletions(-) create mode 100644 docs/.buildinfo create mode 100644 docs/_sources/args.rst.txt create mode 100644 docs/_sources/index.rst.txt create mode 100644 docs/_sources/info.rst.txt create mode 100644 docs/_sources/src.rst.txt create mode 100644 docs/_static/alabaster.css create mode 100644 docs/_static/basic.css create mode 100644 docs/_static/custom.css create mode 100644 docs/_static/doctools.js create mode 100644 docs/_static/documentation_options.js create mode 100644 docs/_static/file.png create mode 100644 docs/_static/language_data.js create mode 100644 docs/_static/minus.png create mode 100644 docs/_static/plus.png create mode 100644 docs/_static/pygments.css create mode 100644 docs/_static/searchtools.js create mode 100644 docs/_static/sphinx_highlight.js create mode 100644 docs/args.html create mode 100644 docs/genindex.html create mode 100644 docs/index.html create mode 100644 docs/info.html create mode 100644 docs/objects.inv create mode 100644 docs/py-modindex.html create mode 100644 docs/search.html create mode 100644 docs/searchindex.js create mode 100644 docs/src.html diff --git a/docs/.buildinfo b/docs/.buildinfo new file mode 100644 index 0000000..416abb4 --- /dev/null +++ b/docs/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 6b4bd43eeea172ae57af29bf18236b28 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/Makefile b/docs/Makefile index d0c3cbf..6ad4c31 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -7,6 +7,7 @@ SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build +HTMLCOPYDIR = . # Put it first so that "make" without argument is like "make help". help: @@ -18,3 +19,4 @@ help: # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + cp -rT $(BUILDDIR)/html $(HTMLCOPYDIR) diff --git a/docs/_sources/args.rst.txt b/docs/_sources/args.rst.txt new file mode 100644 index 0000000..d1daea7 --- /dev/null +++ b/docs/_sources/args.rst.txt @@ -0,0 +1,50 @@ +Command Line Arguments +====================== +Command line arguments to be used with the files in the ``test/`` directory. + +.. autoclass:: src.args.CustomParser + +General Arguments +----------------- +Arguments used during both the generation and analysis of decision regions. + +* ``--save_loc/--save-loc``: directory in which to save program outputs. +* ``--save_name/--save-name``: name to use for the created decision region hdf5 file. +* ``--classes``: model output classes, in format category:option1,option2 (ex. COVID_positive:Yes,No). +* ``--class_order``: used to designate which class corresponds with a model output of 0 or 1; default: 0,1. +* ``--subgroup_attributes``: attributes to be used in addition to `--classes` to group samples and analysis. +* ``--overwrite``: if passed, previously generated hdf5 or analysis files may be overwritten. + +Generation Arguments +-------------------- +Arguments used exclusivly during the generation of decision regions. + +* ``--model_file/--model-file``: file path to the onnx model file. +* ``--data_csv/--data-csv``: csv file which pairs sample ids with image file paths and class and attribute information. +* ``--batch_size/--batch-size``: batch size to be used with the data loader. +* ``--shape``: the shape of the generated vicinal distributions; default: triangle. +* ``--steps``: the number of steps to take between samples in the triplet when creating the vicinal distribution. With --shape=rectangle, approximately step^2 virtual samples will be generated. +* ``--n_triplets/--n-triplets``: the number of triplets to generate for each group. +* ``--img_rel_path/--img-rel-path``: the common directory for the image file paths, include if the file paths in ``--data_csv`` are relative. +* ``--random_seed/--random-seed/--random_state/--random-state``: the random state used during the selection of image triplets. + +Analysis Arguments +------------------ +Arguments used exclusivly during the analysis and plotting of decision regions. + +* ``--out_function``: the function to be applied to model output scores; see `utilities ` for options. +* ``--aggregate/--agg``: how to aggregate composition analysis; options: class, group, all. + Note: decision region compositions are always calculated by triplet before being aggregating to ensure that each triplet has the same impact on the calculated compositiond despite slight variations in the number of virtual samples between triplets' decision regions. +* ``--threshold``: the treshold applied during composition analysis; does not affect region plots. +* ``--plot_only/--plot-only``: pass to not save composition analysis files. +* ``--plot``: type of plot to generate; options: composition, performance, region; no plots will be generated if argument is not passed. +* ``--show``: pass to show plots. +* ``--display-only``: pass to not save plots. +* ``--hide-percent``: pass to not include percent text on composition/performance plots. +* ``--hide-errorbar``: pass to not include errorbars on composition/performance plots. +* ``--save_dpi/--save-dpi``: dpi used when saving summary plots. +* ``--plot_output_format/--plot-output-format``: Output formats in which the plots should be saved. +* ``--plot_palette/--plot-palette``: color palette to be used during plotting, can be either a matplotlib colorpalette or a custom palette. +* ``--plot_threshold/--plot-threshold``: threshold applied to ouput scores in 'region' plots; if None, no threshold is applied. +* ``--n_per_group/--n-per-group``: number of decision regions to plot per group if plot = 'region'. +* \ No newline at end of file diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt new file mode 100644 index 0000000..c56e8fe --- /dev/null +++ b/docs/_sources/index.rst.txt @@ -0,0 +1,45 @@ +DRAGen +====== +Decsion Region Analysis for Generalizability (DRAGen) is a tool to analyze the decision space of an image classification model to increase understanding of the model's generalizability. A model's decision space maps a change in the input to a change in the model's output. DRAGen utilizes triplets of image samples to generate vicinal distributions of virtual images, created by linearly interpolating between the triplet images. These virtual images increase the density of available samples in the decision space, which allows for characterization of the decision space beyond the original finite data set. This insight into the decision space composition indicates how the model is likely to behave on data distributions upon which the model cannot generalize well. + +Getting Started +=============== +Three inputs are required: + +1. **Trained Model:** the model must be saved in onnx format. Information on how to convert your model to onnx format can be found at the `onnx GitHub page`_. +2. **Images:** Images can be saved in any format supported by PIL. +3. **Input csv:** A csv file which can be used to map the image paths to subgroup attributes. + +Examples implementation can be found in the `examples folder`_. + +Scripts to generate and analyze decision regions can be found in the ``test`` folder, +all arguments used to run these scripts are located in ``src/args.py``. +Example inputs are included in the ``examples`` folder. + +.. _onnx GitHub page: https://github.com/onnx/tutorials#converting-to-onnx-format +.. _examples folder: https://github.com/DIDSR/RST_Decision_Region_Analysis/tree/main/examples + +Terminology +=========== +* ``class``: An attribute by which the model classifies images. Only binary classification models are currently supported. +* ``subgroup attribute``: An attribute by which the model *does not* classify images, but can be used to group samples into subgroups. +* ``decision region``: A portion of the decision space. The decision regions generated in this RST are the regions of the decision space near to a 'triplet' of sample images. +* ``virtual image``: An image that was created by modifying existing image(s), rather than obtained through a typical image acquisition method. +* ``vicinal distribution``: The collection of virtual images created by linearly interpolating between a 'triplet' of three images. + + +Contents +======== +.. toctree:: + :maxdepth: 2 + + self + src + args + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/_sources/info.rst.txt b/docs/_sources/info.rst.txt new file mode 100644 index 0000000..e1a711f --- /dev/null +++ b/docs/_sources/info.rst.txt @@ -0,0 +1,30 @@ +DRAGen +====== +Decsion Region Analysis for Generalizability (DRAGen) is a tool to analyze the decision space of an image classification model to increase understanding of the model's generalizability. A model's decision space maps a change in the input to a change in the model's output. DRAGen utilizes triplets of image samples to generate vicinal distributions of virtual images, created by linearly interpolating between the triplet images. These virtual images increase the density of available samples in the decision space, which allows for characterization of the decision space beyond the original finite data set. This insight into the decision space composition indicates how the model is likely to behave on data distributions upon which the model cannot generalize well. + +Getting Started +=============== +Three inputs are required for this RST: + +1. **Trained Model:** the model must be saved in onnx format. Information on how to convert your model to onnx format can be found at the `onnx GitHub page`_. +2. **Images:** Images can be saved in any format supported by PIL. +3. **Input csv:** A csv file which can be used to map the image paths to subgroup attributes. + +Examples implementation can be found in the `examples folder`_. + +Scripts to generate and analyze decision regions can be found in the ``test`` folder, +all arguments used to run these scripts are located in ``src/args.py``. +Example inputs are included in the ``examples`` folder. + +.. _onnx GitHub page: https://github.com/onnx/tutorials#converting-to-onnx-format +.. _examples folder: https://github.com/DIDSR/RST_Decision_Region_Analysis/tree/main/examples + +Terminology +=========== +* ``class``: An attribute by which the model classifies images. Only binary classification models are currently supported. +* ``subgroup attribute``: An attribute by which the model *does not* classify images, but can be used to group samples into subgroups. +* ``decision region``: A portion of the decision space. The decision regions generated in this RST are the regions of the decision space near to a 'triplet' of sample images. +* ``virtual image``: An image that was created by modifying existing image(s), rather than obtained through a typical image acquisition method. +* ``vicinal distribution``: The collection of virtual images created by linearly interpolating between a 'triplet' of three images. + + diff --git a/docs/_sources/src.rst.txt b/docs/_sources/src.rst.txt new file mode 100644 index 0000000..76b5538 --- /dev/null +++ b/docs/_sources/src.rst.txt @@ -0,0 +1,30 @@ +Data Input +========== + +.. automodule:: src.data_input + :members: + :imported-members: + + +Decision Region Generation +========================== + +.. autoclass:: src.decision_region_generation.triplet_manager.TripletManager + :members: + + .. automethod:: __getitem__ + +.. automodule:: src.decision_region_generation.generate + :members: + +.. automodule:: src.decision_region_generation.vicinal_distribution + :members: plane_dataset, plane_dataloader, get_plane + + +Composition Analysis +==================== + +.. automodule:: src.composition_analysis + :members: + :imported-members: + diff --git a/docs/_static/alabaster.css b/docs/_static/alabaster.css new file mode 100644 index 0000000..f98defb --- /dev/null +++ b/docs/_static/alabaster.css @@ -0,0 +1,703 @@ +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: Georgia, serif; + font-size: 17px; + background-color: #fff; + color: #000; + margin: 0; + padding: 0; +} + + +div.document { + width: 1200px; + margin: 30px auto 0 auto; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 300px; +} + +div.sphinxsidebar { + width: 300px; + font-size: 14px; + line-height: 1.5; +} + +hr { + border: 1px solid #B1B4B6; +} + +div.body { + background-color: #fff; + color: #3E4349; + padding: 0 30px 0 30px; +} + +div.body > .section { + text-align: left; +} + +div.footer { + width: 1200px; + margin: 20px auto 30px auto; + font-size: 14px; + color: #888; + text-align: right; +} + +div.footer a { + color: #888; +} + +p.caption { + font-family: inherit; + font-size: inherit; +} + + +div.relations { + display: none; +} + + +div.sphinxsidebar a { + color: #444; + text-decoration: none; + border-bottom: 1px dotted #999; +} + +div.sphinxsidebar a:hover { + border-bottom: 1px solid #999; +} + +div.sphinxsidebarwrapper { + padding: 18px 10px; +} + +div.sphinxsidebarwrapper p.logo { + padding: 0; + margin: -10px 0 0 0px; + text-align: center; +} + +div.sphinxsidebarwrapper h1.logo { + margin-top: -10px; + text-align: center; + margin-bottom: 5px; + text-align: left; +} + +div.sphinxsidebarwrapper h1.logo-name { + margin-top: 0px; +} + +div.sphinxsidebarwrapper p.blurb { + margin-top: 0; + font-style: normal; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: Georgia, serif; + color: #444; + font-size: 24px; + font-weight: normal; + margin: 0 0 5px 0; + padding: 0; +} + +div.sphinxsidebar h4 { + font-size: 20px; +} + +div.sphinxsidebar h3 a { + color: #444; +} + +div.sphinxsidebar p.logo a, +div.sphinxsidebar h3 a, +div.sphinxsidebar p.logo a:hover, +div.sphinxsidebar h3 a:hover { + border: none; +} + +div.sphinxsidebar p { + color: #555; + margin: 10px 0; +} + +div.sphinxsidebar ul { + margin: 10px 0; + padding: 0; + color: #000; +} + +div.sphinxsidebar ul li.toctree-l1 > a { + font-size: 120%; +} + +div.sphinxsidebar ul li.toctree-l2 > a { + font-size: 110%; +} + +div.sphinxsidebar input { + border: 1px solid #CCC; + font-family: Georgia, serif; + font-size: 1em; +} + +div.sphinxsidebar hr { + border: none; + height: 1px; + color: #AAA; + background: #AAA; + + text-align: left; + margin-left: 0; + width: 50%; +} + +div.sphinxsidebar .badge { + border-bottom: none; +} + +div.sphinxsidebar .badge:hover { + border-bottom: none; +} + +/* To address an issue with donation coming after search */ +div.sphinxsidebar h3.donation { + margin-top: 10px; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #004B6B; + text-decoration: underline; +} + +a:hover { + color: #6D4100; + text-decoration: underline; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: Georgia, serif; + font-weight: normal; + margin: 30px 0px 10px 0px; + padding: 0; +} + +div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } +div.body h2 { font-size: 180%; } +div.body h3 { font-size: 150%; } +div.body h4 { font-size: 130%; } +div.body h5 { font-size: 100%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #DDD; + padding: 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + color: #444; + background: #EAEAEA; +} + +div.body p, div.body dd, div.body li { + line-height: 1.4em; +} + +div.admonition { + margin: 20px 0px; + padding: 10px 30px; + background-color: #EEE; + border: 1px solid #CCC; +} + +div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { + background-color: #FBFBFB; + border-bottom: 1px solid #fafafa; +} + +div.admonition p.admonition-title { + font-family: Georgia, serif; + font-weight: normal; + font-size: 24px; + margin: 0 0 10px 0; + padding: 0; + line-height: 1; +} + +div.admonition p.last { + margin-bottom: 0; +} + +div.highlight { + background-color: #fff; +} + +dt:target, .highlight { + background: #FAF3E8; +} + +div.warning { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.danger { + background-color: #FCC; + border: 1px solid #FAA; + -moz-box-shadow: 2px 2px 4px #D52C2C; + -webkit-box-shadow: 2px 2px 4px #D52C2C; + box-shadow: 2px 2px 4px #D52C2C; +} + +div.error { + background-color: #FCC; + border: 1px solid #FAA; + -moz-box-shadow: 2px 2px 4px #D52C2C; + -webkit-box-shadow: 2px 2px 4px #D52C2C; + box-shadow: 2px 2px 4px #D52C2C; +} + +div.caution { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.attention { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.important { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.note { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.tip { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.hint { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.seealso { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.topic { + background-color: #EEE; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre, tt, code { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.9em; +} + +.hll { + background-color: #FFC; + margin: 0 -12px; + padding: 0 12px; + display: block; +} + +img.screenshot { +} + +tt.descname, tt.descclassname, code.descname, code.descclassname { + font-size: 0.95em; +} + +tt.descname, code.descname { + padding-right: 0.08em; +} + +img.screenshot { + -moz-box-shadow: 2px 2px 4px #EEE; + -webkit-box-shadow: 2px 2px 4px #EEE; + box-shadow: 2px 2px 4px #EEE; +} + +table.docutils { + border: 1px solid #888; + -moz-box-shadow: 2px 2px 4px #EEE; + -webkit-box-shadow: 2px 2px 4px #EEE; + box-shadow: 2px 2px 4px #EEE; +} + +table.docutils td, table.docutils th { + border: 1px solid #888; + padding: 0.25em 0.7em; +} + +table.field-list, table.footnote { + border: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + +table.footnote { + margin: 15px 0; + width: 100%; + border: 1px solid #EEE; + background: #FDFDFD; + font-size: 0.9em; +} + +table.footnote + table.footnote { + margin-top: -15px; + border-top: none; +} + +table.field-list th { + padding: 0 0.8em 0 0; +} + +table.field-list td { + padding: 0; +} + +table.field-list p { + margin-bottom: 0.8em; +} + +/* Cloned from + * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 + */ +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +table.footnote td.label { + width: .1px; + padding: 0.3em 0 0.3em 0.5em; +} + +table.footnote td { + padding: 0.3em 0.5em; +} + +dl { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding: 0; +} + +dl dd { + margin-left: 30px; +} + +blockquote { + margin: 0 0 0 30px; + padding: 0; +} + +ul, ol { + /* Matches the 30px from the narrow-screen "li > ul" selector below */ + margin: 10px 0 10px 30px; + padding: 0; +} + +pre { + background: #EEE; + padding: 7px 30px; + margin: 15px 0px; + line-height: 1.3em; +} + +div.viewcode-block:target { + background: #ffd; +} + +dl pre, blockquote pre, li pre { + margin-left: 0; + padding-left: 30px; +} + +tt, code { + background-color: #ecf0f3; + color: #222; + /* padding: 1px 2px; */ +} + +tt.xref, code.xref, a tt { + background-color: #FBFBFB; + border-bottom: 1px solid #fff; +} + +a.reference { + text-decoration: none; + border-bottom: 1px dotted #004B6B; +} + +/* Don't put an underline on images */ +a.image-reference, a.image-reference:hover { + border-bottom: none; +} + +a.reference:hover { + border-bottom: 1px solid #6D4100; +} + +a.footnote-reference { + text-decoration: none; + font-size: 0.7em; + vertical-align: top; + border-bottom: 1px dotted #004B6B; +} + +a.footnote-reference:hover { + border-bottom: 1px solid #6D4100; +} + +a:hover tt, a:hover code { + background: #EEE; +} + + +@media screen and (max-width: 870px) { + + div.sphinxsidebar { + display: none; + } + + div.document { + width: 100%; + + } + + div.documentwrapper { + margin-left: 0; + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + } + + div.bodywrapper { + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + margin-left: 0; + } + + ul { + margin-left: 0; + } + + li > ul { + /* Matches the 30px from the "ul, ol" selector above */ + margin-left: 30px; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .bodywrapper { + margin: 0; + } + + .footer { + width: auto; + } + + .github { + display: none; + } + + + +} + + + +@media screen and (max-width: 875px) { + + body { + margin: 0; + padding: 20px 30px; + } + + div.documentwrapper { + float: none; + background: #fff; + } + + div.sphinxsidebar { + display: block; + float: none; + width: 102.5%; + margin: 50px -30px -20px -30px; + padding: 10px 20px; + background: #333; + color: #FFF; + } + + div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, + div.sphinxsidebar h3 a { + color: #fff; + } + + div.sphinxsidebar a { + color: #AAA; + } + + div.sphinxsidebar p.logo { + display: none; + } + + div.document { + width: 100%; + margin: 0; + } + + div.footer { + display: none; + } + + div.bodywrapper { + margin: 0; + } + + div.body { + min-height: 0; + padding: 0; + } + + .rtd_doc_footer { + display: none; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .footer { + width: auto; + } + + .github { + display: none; + } +} + + +/* misc. */ + +.revsys-inline { + display: none!important; +} + +/* Make nested-list/multi-paragraph items look better in Releases changelog + * pages. Without this, docutils' magical list fuckery causes inconsistent + * formatting between different release sub-lists. + */ +div#changelog > div.section > ul > li > p:only-child { + margin-bottom: 0; +} + +/* Hide fugly table cell borders in ..bibliography:: directive output */ +table.docutils.citation, table.docutils.citation td, table.docutils.citation th { + border: none; + /* Below needed in some edge cases; if not applied, bottom shadows appear */ + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + + +/* relbar */ + +.related { + line-height: 30px; + width: 100%; + font-size: 0.9rem; +} + +.related.top { + border-bottom: 1px solid #EEE; + margin-bottom: 20px; +} + +.related.bottom { + border-top: 1px solid #EEE; +} + +.related ul { + padding: 0; + margin: 0; + list-style: none; +} + +.related li { + display: inline; +} + +nav#rellinks { + float: right; +} + +nav#rellinks li+li:before { + content: "|"; +} + +nav#breadcrumbs li+li:before { + content: "\00BB"; +} + +/* Hide certain items when printing */ +@media print { + div.related { + display: none; + } +} \ No newline at end of file diff --git a/docs/_static/basic.css b/docs/_static/basic.css new file mode 100644 index 0000000..6e0b03a --- /dev/null +++ b/docs/_static/basic.css @@ -0,0 +1,903 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: auto; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..2a924f1 --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1 @@ +/* This file intentionally left blank. */ diff --git a/docs/_static/doctools.js b/docs/_static/doctools.js new file mode 100644 index 0000000..d06a71d --- /dev/null +++ b/docs/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js new file mode 100644 index 0000000..b57ae3b --- /dev/null +++ b/docs/_static/documentation_options.js @@ -0,0 +1,14 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/docs/_static/file.png b/docs/_static/file.png new file mode 100644 index 0000000000000000000000000000000000000000..a858a410e4faa62ce324d814e4b816fff83a6fb3 GIT binary patch literal 286 zcmV+(0pb3MP)s`hMrGg#P~ix$^RISR_I47Y|r1 z_CyJOe}D1){SET-^Amu_i71Lt6eYfZjRyw@I6OQAIXXHDfiX^GbOlHe=Ae4>0m)d(f|Me07*qoM6N<$f}vM^LjV8( literal 0 HcmV?d00001 diff --git a/docs/_static/language_data.js b/docs/_static/language_data.js new file mode 100644 index 0000000..250f566 --- /dev/null +++ b/docs/_static/language_data.js @@ -0,0 +1,199 @@ +/* + * language_data.js + * ~~~~~~~~~~~~~~~~ + * + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, is available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/docs/_static/minus.png b/docs/_static/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..d96755fdaf8bb2214971e0db9c1fd3077d7c419d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu=nj kDsEF_5m^0CR;1wuP-*O&G^0G}KYk!hp00i_>zopr08q^qX#fBK literal 0 HcmV?d00001 diff --git a/docs/_static/plus.png b/docs/_static/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..7107cec93a979b9a5f64843235a16651d563ce2d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu>-2 m3q%Vub%g%s<8sJhVPMczOq}xhg9DJoz~JfX=d#Wzp$Pyb1r*Kz literal 0 HcmV?d00001 diff --git a/docs/_static/pygments.css b/docs/_static/pygments.css new file mode 100644 index 0000000..9abe04b --- /dev/null +++ b/docs/_static/pygments.css @@ -0,0 +1,83 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #8f5902; font-style: italic } /* Comment */ +.highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */ +.highlight .g { color: #000000 } /* Generic */ +.highlight .k { color: #004461; font-weight: bold } /* Keyword */ +.highlight .l { color: #000000 } /* Literal */ +.highlight .n { color: #000000 } /* Name */ +.highlight .o { color: #582800 } /* Operator */ +.highlight .x { color: #000000 } /* Other */ +.highlight .p { color: #000000; font-weight: bold } /* Punctuation */ +.highlight .ch { color: #8f5902; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #8f5902; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #8f5902 } /* Comment.Preproc */ +.highlight .cpf { color: #8f5902; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #8f5902; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #a40000 } /* Generic.Deleted */ +.highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #ef2929 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #745334 } /* Generic.Prompt */ +.highlight .gs { color: #000000; font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */ +.highlight .kc { color: #004461; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #004461; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #004461; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #004461; font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { color: #004461; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #004461; font-weight: bold } /* Keyword.Type */ +.highlight .ld { color: #000000 } /* Literal.Date */ +.highlight .m { color: #990000 } /* Literal.Number */ +.highlight .s { color: #4e9a06 } /* Literal.String */ +.highlight .na { color: #c4a000 } /* Name.Attribute */ +.highlight .nb { color: #004461 } /* Name.Builtin */ +.highlight .nc { color: #000000 } /* Name.Class */ +.highlight .no { color: #000000 } /* Name.Constant */ +.highlight .nd { color: #888888 } /* Name.Decorator */ +.highlight .ni { color: #ce5c00 } /* Name.Entity */ +.highlight .ne { color: #cc0000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #000000 } /* Name.Function */ +.highlight .nl { color: #f57900 } /* Name.Label */ +.highlight .nn { color: #000000 } /* Name.Namespace */ +.highlight .nx { color: #000000 } /* Name.Other */ +.highlight .py { color: #000000 } /* Name.Property */ +.highlight .nt { color: #004461; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #000000 } /* Name.Variable */ +.highlight .ow { color: #004461; font-weight: bold } /* Operator.Word */ +.highlight .pm { color: #000000; font-weight: bold } /* Punctuation.Marker */ +.highlight .w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */ +.highlight .mb { color: #990000 } /* Literal.Number.Bin */ +.highlight .mf { color: #990000 } /* Literal.Number.Float */ +.highlight .mh { color: #990000 } /* Literal.Number.Hex */ +.highlight .mi { color: #990000 } /* Literal.Number.Integer */ +.highlight .mo { color: #990000 } /* Literal.Number.Oct */ +.highlight .sa { color: #4e9a06 } /* Literal.String.Affix */ +.highlight .sb { color: #4e9a06 } /* Literal.String.Backtick */ +.highlight .sc { color: #4e9a06 } /* Literal.String.Char */ +.highlight .dl { color: #4e9a06 } /* Literal.String.Delimiter */ +.highlight .sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4e9a06 } /* Literal.String.Double */ +.highlight .se { color: #4e9a06 } /* Literal.String.Escape */ +.highlight .sh { color: #4e9a06 } /* Literal.String.Heredoc */ +.highlight .si { color: #4e9a06 } /* Literal.String.Interpol */ +.highlight .sx { color: #4e9a06 } /* Literal.String.Other */ +.highlight .sr { color: #4e9a06 } /* Literal.String.Regex */ +.highlight .s1 { color: #4e9a06 } /* Literal.String.Single */ +.highlight .ss { color: #4e9a06 } /* Literal.String.Symbol */ +.highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #000000 } /* Name.Function.Magic */ +.highlight .vc { color: #000000 } /* Name.Variable.Class */ +.highlight .vg { color: #000000 } /* Name.Variable.Global */ +.highlight .vi { color: #000000 } /* Name.Variable.Instance */ +.highlight .vm { color: #000000 } /* Name.Variable.Magic */ +.highlight .il { color: #990000 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/_static/searchtools.js b/docs/_static/searchtools.js new file mode 100644 index 0000000..97d56a7 --- /dev/null +++ b/docs/_static/searchtools.js @@ -0,0 +1,566 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = docUrlRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = docUrlRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms) + ); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + `Search finished, found ${resultCount} page(s) matching the search query.` + ); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent !== undefined) return docContent.textContent; + console.warn( + "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + /** + * execute search (requires search index to be loaded) + */ + query: (query) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + // array of [docname, title, anchor, descr, score, filename] + let results = []; + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + results.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id] of foundEntries) { + let score = Math.round(100 * queryLower.length / entry.length) + results.push([ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // lookup as object + objectTerms.forEach((term) => + results.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); + + // now sort the results by score (in opposite order of appearance, since the + // display function below uses pop() to retrieve items) and then + // alphabetically + results.sort((a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; + }); + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + results = results.reverse(); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord) && !terms[word]) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord) && !titleTerms[word]) + arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); + }); + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); + else fileMap.set(file, [word]); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords) => { + const text = Search.htmlToText(htmlText); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/docs/_static/sphinx_highlight.js b/docs/_static/sphinx_highlight.js new file mode 100644 index 0000000..aae669d --- /dev/null +++ b/docs/_static/sphinx_highlight.js @@ -0,0 +1,144 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + parent.insertBefore( + span, + parent.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(SphinxHighlight.highlightSearchWords); +_ready(SphinxHighlight.initEscapeListener); diff --git a/docs/args.html b/docs/args.html new file mode 100644 index 0000000..8bc3df3 --- /dev/null +++ b/docs/args.html @@ -0,0 +1,163 @@ + + + + + + + + + Command Line Arguments — RST: Decision Region Analysis documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

Command Line Arguments

+

Command line arguments to be used with the files in the test/ directory.

+
+
+class src.args.CustomParser(mode: str)
+

A custom argument parser.

+
+
Parameters:
+

mode (str) – The purpose for which the parser is being created; must be ‘Analyze’, ‘Complete’, or ‘Generate’.

+
+
Raises:
+

ValueError – mode value is not ‘Analyze’, ‘Complete’ or ‘Generate’.

+
+
+
+ +
+

General Arguments

+

Arguments used during both the generation and analysis of decision regions.

+
    +
  • --save_loc/--save-loc: directory in which to save program outputs.

  • +
  • --save_name/--save-name: name to use for the created decision region hdf5 file.

  • +
  • --classes: model output classes, in format category:option1,option2 (ex. COVID_positive:Yes,No).

  • +
  • --class_order: used to designate which class corresponds with a model output of 0 or 1; default: 0,1.

  • +
  • --subgroup_attributes: attributes to be used in addition to –classes to group samples and analysis.

  • +
  • --overwrite: if passed, previously generated hdf5 or analysis files may be overwritten.

  • +
+
+
+

Generation Arguments

+

Arguments used exclusivly during the generation of decision regions.

+
    +
  • --model_file/--model-file: file path to the onnx model file.

  • +
  • --data_csv/--data-csv: csv file which pairs sample ids with image file paths and class and attribute information.

  • +
  • --batch_size/--batch-size: batch size to be used with the data loader.

  • +
  • --shape: the shape of the generated vicinal distributions; default: triangle.

  • +
  • --steps: the number of steps to take between samples in the triplet when creating the vicinal distribution. With –shape=rectangle, approximately step^2 virtual samples will be generated.

  • +
  • --n_triplets/--n-triplets: the number of triplets to generate for each group.

  • +
  • --img_rel_path/--img-rel-path: the common directory for the image file paths, include if the file paths in --data_csv are relative.

  • +
  • --random_seed/--random-seed/--random_state/--random-state: the random state used during the selection of image triplets.

  • +
+
+
+

Analysis Arguments

+

Arguments used exclusivly during the analysis and plotting of decision regions.

+
    +
  • --out_function: the function to be applied to model output scores; see utilities <src/utils.py> for options.

  • +
  • +
    --aggregate/--agg: how to aggregate composition analysis; options: class, group, all.

    Note: decision region compositions are always calculated by triplet before being aggregating to ensure that each triplet has the same impact on the calculated compositiond despite slight variations in the number of virtual samples between triplets’ decision regions.

    +
    +
    +
  • +
  • --threshold: the treshold applied during composition analysis; does not affect region plots.

  • +
  • --plot_only/--plot-only: pass to not save composition analysis files.

  • +
  • --plot: type of plot to generate; options: composition, performance, region; no plots will be generated if argument is not passed.

  • +
  • --show: pass to show plots.

  • +
  • --display-only: pass to not save plots.

  • +
  • --hide-percent: pass to not include percent text on composition/performance plots.

  • +
  • --hide-errorbar: pass to not include errorbars on composition/performance plots.

  • +
  • --save_dpi/--save-dpi: dpi used when saving summary plots.

  • +
  • --plot_output_format/--plot-output-format: Output formats in which the plots should be saved.

  • +
  • --plot_palette/--plot-palette: color palette to be used during plotting, can be either a matplotlib colorpalette or a custom palette.

  • +
  • --plot_threshold/--plot-threshold: threshold applied to ouput scores in ‘region’ plots; if None, no threshold is applied.

  • +
  • --n_per_group/--n-per-group: number of decision regions to plot per group if plot = ‘region’.

  • +
  • +
+
+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/docs/build/doctrees/args.doctree b/docs/build/doctrees/args.doctree index 59924da6168ccf6ba26923570d8853f82cad8a1a..60414aabddb218d76fcbecdc2ddb8f544102bb4c 100644 GIT binary patch delta 134 zcmeAbQuSSe9Nkm% fHqT((&8*-@K-J_dZj;S>S<6`os@$B#y(k0##(*$H delta 140 zcmX@LpRwlvBWnZeRK|pjtg(!YUX$%uC1rwwL*iXhlQWAm^Yh|^Qqw_{V_srTWpQTl d<|fA7%#107)y1-|W+g)1o#)IhJ2QC@4V&jCn}o2*By3^`dG++n+?npXJ>8vt z?9PM)Q4q2}E+&u;5FR2wK~Y3h1Yal*5%{Tp`H2GZ0~G@TilB&Ze?RdNB5KTbl<95b?Vf4)u~gbUbgPZRVS@FiT-&zTa9w1wmj+7j#irW+LBXicfIY` zbt={Q8v>7>=&Topzg0zP8=$0IE&$ zai>Latvh)0!8;D?r%k7USI+{{iwfsOf%Bq|^Nx427)`l;tT$9b5yFMo$Y!kFvt<;W)Cr-=T>NEiVT&q=F zs8m6bn1ij{h4D!A>IN!p5p5M)odrxd1~*r)Rt14!f;ZjPoOhbe{J{$73xto+ubphR zh=rR@3rL!4cTn60$#+}cmRe^CtW#X5HhA}mD(bebiDOPOVtiD-gtR;`3!Fjgg!oSH7PfZ2%(@>vZve8`;ows!plp_z%{B ze@iR=&yD=ExUkgjPK!52s-@auhq-yRgNC@7O&9{CR}tcfXJ&)=-RhP^`RM{3MfEPv zVXZ`raq3H&^?Lh}$(N|IM_vMCKO!40EIMGQ<#w@Dt%AT7IuAXxQfxO%wHDZsdE~ga zLDnJkc%w_qWW?AY=t^zgS(Z(WP(S>Jtud!SM%QvAV%1#UO`>1S{ zDlFCKJ3u1g#V^OZgQH;WqSL5U>x*h?_z_qs@MtF^iWDtGKyjg3Lf>+pwl;vn zQga1##F+I~H^3~+1Dk8m=0Fge1KsIqqQ>pwSwLYvf)W&LUMkf{fVYaBW>tYQQmeEZ zmANA;-Q!$otNdN8EY|7}CijbXcbShdk&7U5S-4(^C^c~>ONj+3g>S`qrvV*uXqCYK zE#+GU=-VL8%0*O|b(+L!>g^r;+<6eBqGX#ce4AVd|Tdsnbrs$ZcoH!4ZO zqG+{Sf`T4l;|anKc!CWE?2`^8sqS<{duI#kEJD+lORy%O%c}sqT(3ew__Vi$wGb4! zBc#`EC`Y9mg=+LQWBERwXnsM7-J7l*VXN^ZZ!J9+5{qjHGok#I@>l4so%&h87!YNn zqO8&eAC}J$&o=Qh!L(f{2yR5cs*uqwyW+7>r0Bg`X_cY&RduOgeH9KB!Od*rwxP#s z?aD%h?1T-S+L2oQ7~~395S_Q?*w88evv@edZ*?m3T|D6l6L7OF*xeI+w1`@cdL*H1 zI=-40RRt7{Ix(cw|6EYMrhKjFQQ%A&Vn!uTpaSwzGQb+qsDPXv!?2Yc?pJL=eFDR; z5B5(cBlsVDt5{?gkCO~8elE?fLnn#1#AsNeg3n1JS8A|o&@5w46oVg%LQy>`9&hJ1 zVRp|oD{Ki84*3O90)rhA3Z@)PT~)G!-#ON-kO@JW1gdouws7SkR4!FThvf^(7fOlC zrV|sLvOu|@yr;ZZib_#~$;{iAYIqk4%8X{6EM={SUym`PK@dL@8ku zpBH&3-s+EjKsG9@-5{~>dlJo!YNaiy=(uQ56p#&AluJLOCnid5XDcvHWia$}-)>$z z*@m-MFv0y95tA%5Cn-j?f>=FS zkS~i4B&@!mmBW?mB3|nRg*YQgbgM==2kj14zK^xOoB^_@=cP~~oGBcLp^^q7%RwRi zEJhtICwMi1fz+H@U72g{DR5SZ*%2MH_J;9CYoDgl!rbz6izGwvP|gVqm3G;Mc;khy zcSX6~ZnXAKP0h{MCf(M&Q>`3rPS%|ERIRZzCF@>$>6FvDYzlKRv9wg3m?PLwSt5;% z4Qer94l+)*s;;DrBu%~gzVR+T_H-QqzW?%kr3L-G!s>kWefLtO`&A{sArJ)FCvrm6 zyO+Q?jC+1*__AmBk_D=PX7UEcc!-s?VtP#)f zi?kqVmzsD=W{RkDnyOPJnWR=eHB~<5oei^kUV3FX?{)yATV`yCbi zN|Dcxwu?vbrh$xEzyf7la;7LD@g%-2VL1kATJ0>=Fe|}yWXfe zN1ZC<5L=$KCY%;Ec8MAh;T~)@pzv4M>JqGz#fw}<>Hk#V_C#+2a4L2B?tOL&3&Nc zSiO0K)&+;wMP4e^=3w-Y8sX+n*FDJ+jSz431Lr6jKTFqZg5@B>m!Q%W1UFsV{4g&a zM57zQA=2o;go6clC$eDIdse#jMrb>$bZC{&ZCj{uO~aB#&!DGEr6W-5f+@(`gKu@{ zXIbUs78s@K2sN%ef68Xs0Z~w?E!2IVtCAimpksY=mivD}qTaJ_t2qT=$SDNu;zE6) z;5&!6+*)bPIaR1(Oj%3Ij-9IC7*VlZl|?yEmBcf;KNyWBZ$!CPBo&^bny6vSPP=%4Bs2$(+zdeR=A!kmq1b8hP_u!y!71X%t+<1g{#z@V}uwKukF;} z;uMPGWG*AOl5fFK)2w(1GHzNf`3=iPEY_Ccx`vab-S*e=+vbXu`6B$=y!1eKARDf` zlbqNvCgRGr&lQE0eZ*M-%gpnfdmCW-F4jskApTZg0PK)i@C9!mWeev$Bo{5HDm60y zybW zYDH2=o!Wy?ezew}=e-E&5R^4f0U3kEoN5EcL~UOB3tuM4Z<>l~JU978Z)z!q}%x@sj&yiUW z8@!*i$#X{HKqhpn+jXmWmwjk)?KYhya&?;Jr|EZvuTTl><9-vnb(N(`4R%v=saSj)r|PyCA6V)LV}ILJr`4RAt<wMLezd;Lm z>-)%xV&w!MF*&e9QgM%BiZJ+-`tx7zTAyDbq7l zais=#Doi$iq`to0t*X$rh0{EWjWV%GaPdZeyF@jlVN349=Z_0^V>Ch~##o9T(w3SkiX7uE5R(Hpa9>*^_Do%iTtrKi7MYu}D_Li^iYh2B*6T1u^<=2&I^C}U;|IPmf3{SG zXZaP>PG1EZE^zE(iv*Q@jUB0mt)Zi2xc9EvYGAPpp%<=szTYO<0i}8DV6lUdHeVnr zOJo`OBy&z$W!mBMYhIXEGhld13uA{-9b+PA5Od5~A;e4#;#ZmTEwa+&UJZm(P|% zU@nzw8;(7pI0~_0AUf-GX1y)KcZ9_@EIBOyR(U5IPlUM(h2pJ;1A&4_rpvp%tun&J zU#x7nUB<@VR`PNO+gpQJAK1un4z;?EcV^3_M^Lz3wknn= zt;xCWq1E2$;fH1H*F);G3D(oXg%yDdDpO%Rert$RIyB<#3cbQ60@dFgDsW3jOF|kG z7%I|7wMM=@dVWgiEn)X|53Pyx4~0!Hgu#NRi5{;P?Q{=4ujB2Kh9^k_OonjEBLroq z@f;pr?4crDK!OA(i#Gzi(1r<#s&Q{CiD_EofD}1aWHsz!Z;RO2sn$y(G+-_1e|P~& zg<^3<`{CF}1AdQtr_7<&5o{r1tq1m2KKSZIABxMgQHt`QSrDnDNsOO zxGBW6P|W~OPJ5f13v+>YFI4YhccKJ;;2hi#lNG1Ezzu+SrpWrCmgqgBrLC2k;R!E4 z;%&tXY=0J)N%s-bhyz-kS@Y=*wdQX*)dlh{A3@d8GJ}&9>|x)k?0TE|g`(OK?sy|m>MW{hqTFv{ zrE=4O&)i_qtH@M&Me|`5YyWPK4zpfem&cAj%gkz9{5Z-@^oG9PuZIY@NK0}K5 z5j(@60W3z>5SKjB{ju(0*#~jpj>*X>{0IJg#n0gA;y*7VPzw&{N9oJ2i(jIaHwt`(dPcdv!0duNYVh;3Bp%{3%$I z!Gxgoz3QGJJ6CrTm=eY}Gcr|r90Vnm`+V0uW6en?xo6{pCf?hE9TD;;l&Z%{E3NKh zh)^n0PzLSB5VK%ePZ14ehvdiUVz-0}B87rXy=_w9K^)X*r3Q*aZ4q>+gul13Svn?- z9cYe`xiWSM;86jOFo)9Ot2HTJFHftt-2H&Le4l#-{=f<7UWLEjIUgJKk7$@u zZN6{|InWL?7rA?^<;APw&9!%8F-9DbRQt!eB7${2J4f1G_aJq+kp?b?<6ce=*2{rR zdn73l%hQ=Hd7}Zp5sBJ06?O%|-9t`emIheqaNrZhJc9jr{ezq(kYgk7?WyMk$Hw zJMkC(hP%X{yTzY-#Gm{4Pse=#e_fY8cuEO+z{v>N6vS2EV|47hpP}xFdfXy?FfFW! zBQZdB8ETY|8C2o3qD9Z%2H1!k)CUd*>%bPF7}3J@Y|P#cr?1m2j5CuE2|)`@k`~(2 zg&`%sPX_CfCj)+bfzR@>IN*7NniOat-db8nn{?ohW}L?gIfN>KwYxEN@3bHg;}m|f zzW^<>N0E?hWE(f4ZE&+o?~;43Jda${`wL?{aBIpsnjrQN2(tZLHc&m@Yo8R)Aqi}oEy~Jj~4%!dFFI+mb4--$0f{9j1h9PSV zL`15PM7W9OjeZLzx&zM*wEu1te4nZl&_3>Mg7awtDB=FSEd&lz8vk};XT<$5b@UN@ zNDTfC{pPue!Jzw5DrM2(k9s4II%de5gIr^d+AJ~#{yFL)fu0iwK0*T*qaGn}YzF)p z+X3(70h97vB0G(uYQmH4iwJ+%0Z#Pa?D>|ID?E`ToOrJ!^wwIAhEui~e~#|s0D-v+en*ILtnx2lm2?OJM+ z=~_bja{xMM_4E>ir`QAKegJ*+DG4Q1{}_U54P=myEG8nXw&4VTGhi8Jqe%?DRuiI} z-!%}sC&L)xi~LrcfI_6A_Hx#LNf+`fGekhAIwU zk;cB}H`Y_iMu2`D-YrRc6Kf5y8iHX-019_^H(tm|nF7LvAAm#2%CWDA{Ym1U;; zc{EPPzp&(koPvYTy_;r(9={U1)?A=#$_aalM#H9MKtBjvy`GAEajt}f(3EN$#}E!a z4UfAeyc^`u-3JPDY&rUCk^*3a4e2j31l_09Gq_=vM&`q2T8>FH7dN{S3%-FpEIUJB zv}iyLjPlBO6dUSQ#0f-{Q2?J}3C$G~PFf+49iaHYhn%r}g)uC5$)QEx(a6|M!qw%B`6R0r zAac11mn+WME}B4l{VDm^4;Iy3AJvcJ8!*cgy}B@NDr+iC;v#n(g#i}!iBYV&wyQy_ zV=19w8_JJ+kE*oyq;#KwiQi4lF@3!Y-|VD(P%HA$Db{<=GNRY#D&#>=)|2jg(VM9z z9}TKWmcjv5$pSf|E`#&5YLCScoto_^UAq19jN{9_wGZ9D zQLzjE2E*WyVi`P|?0-lRt=Zc@!}6P_{x#N9FQ?+wKJ}1vvRl~7CXV_|w73cP82-eu z(75}sS>mlfT*B^sNHD91hEV<+>SS=5PYdO*QNuAp*)u`aav-eZ58g#5gKwY^1d_rm zllibP$y;o0#tYX(*_tSSo-_gSMs>fwxy|$)EHH4mdfh;AsHN?H0<`Rc$NeZ?PZT^j zX}yfCRx;nEh3>AUaeN#vJ?ee}pTgezBz+^JFCt_910_lq#7PV)Pa-X$L9UBVYN{Zh z$xW35XH+N(7F0pM^x1?`LzbHWwhi)EalMH%q)7LQ7R+wx3vv#FQJt83VC$xEtE$rMCXZOb=u1>+C2 zWdnT=L`Uh*Vg~m0S8@SVZH&XGT5fEb#R3}J*1eZs8>tFMb%ku*8)s%FCR!y#&mo+4 zN_?Hb*O{69dWVR1N#sJLUZBc!Kmc2KO9gQbFo5yUw7MNak=ji-5+dK-iJhCu@RV2( zO{?so!On%=H9LlemFAhSLyAEx)J~FyN)T7Eu5&3%;)rFFAeI(GoPhuzBoLW@Yf)e| zQPO5-6BPpHp?8Nrm9%_UghbzolISn(<6;w+SGBZ-E7z}K?k$w(7cS%dUCqW}qIwfS zmqb)&ra)qn)oGHf!VsH;^|mM~@-Vq3thb*j++ z5$1B#+5cjvwQstRB^k%Rk*_Bb2`5)D=%A3$=Xf@Cr*;Oseeh#Lr-Y>Aka zl1Z#ES}vmX#l=^k0nTi+zi`pMi<61(D}>-A;@c5{&m_Ptk^lwtCK3L1%FJcJvL?c> z@#u!geU#vc3{nM{JUXOCzIjE^(ka1thqP!h_E?UbZ%dOPW;52wZ`EEE!B!>R$sTka zrYV<`rY|SzTl01ZE~rlUF}sL~F*ZK@D*hHavRJw?(W>QXfid=qnetz+KM#@rsDCHf zF5x7jK>s8^S$q{k-mRoiO9t7bU1dk34F`sLGKLx!69+@CGEVr>svLzpl3p`fUk4m! zy?tbZY0^O0tD@t|vu=*_ckB|xVqy>oTLLi$oD%^DYS?XYEy+G!M{)){_$~Oy1PO1F z`t%oapL~#Yc0<-{wPk6`Q(3a9^d=g9lEr>@YPY5U-kk`bDbC(DSbS@7{1!BI^!AXY?5Tnb-pFuf;6-gGJQ)R6^J1Lat3wmXM`Gq$M?pRGbpl_BcJN2gk8qtf zO333Y?d<|iYKVm)AD_`=)?!0kEF#&>tulkats%S`biT+ZkdS0>KSb~+5gps-v9S8> z@umUn^O#yDv3)Fxs65QAiR~jii9+|^H&h%NR7%|rnMzL+RQg*yz!OWQFCZ$NEw$&$ z2ytDiRnV279DaXGG^ozt?op_rgOQrcP6(8#kno)}{a)CLwK*@R?+@l^QcG%fU#l zO=wuD$s6}G5^`h2peO1e3340rKV<`Hx1gsDJd_hlPaooWY~$=U=}sackw=Sp~o@~M5yad2)+x~|~nqUxX{3VDPd7G=D-MEl275=ViPg%{CTLP;W5QO-4{!hMua zr&(WyUT!-usByVOr zp@9tZrG?%_Jmo^@z5m4F9}X{Ol0i&>!7MbMAv4Pvf?4)4<}x5Zukb|*EkW`vDD>Kd z`VnOxRl@!~>9FE$SO-V0g&#PXeD5GcCXp}e^+Y&Kf`(pCsA-b;-6=Dd0n3`ie~2ea zkoa@MB;FT}3<{&>hD>2+3JQB61D;{*IVkMaB#tXfi%6D0iMZQk&=`JB;O7MWR8%&H zL-6pc_y@23?fvk=hBjvTjt}yrl2rDW)-BqyrS8t3BN}}F~hj^u^xL@EZ0rVVmH7SmaRMUV2lWf)Zs7JYriG^4v5SLFgrwJSLD zc?y}zl>TJPM?-@Qz*#PWTX*s_{D{XOH zG;YU4dLqLP>2N>ZF72F8*)$2%8qB^4Z6;i8#$X0~8EshmX%7`J%#eG^yREN16MyrTe6Njao%5LW5n?>>)(tfNLv*xxTo!RV zDdz~DGw}*E_P>+TB>9SPVE;xoWg)7Zn$opM>1vULnSL4Zd$<`0AyZdC={SWM%FTqh z6{l6Wl5%5+R85VQt7q7}l<82D*DULBAylGWxHAf z6CyhU=1gFz%_Lymb0EqBn`GV@$yhh>b9&P3n4O=M(s>5v*D)(WOl>gjQPUsitmn_b+B(mPCN3(DUjGRS1;h2BP4FeY7oJ??2C}*WdWDP@fr~> zJ_nX3T_aS08A?WJpqId~<|z=ffz(@t7GIrMK!VkZ$Vf`7OL|ee6T{9tIuIf?1w?#=&oafAE-G3vrFM5-wyp228{ zGej;BBwk?>&(P)~fyfcTf=e=R-u2W(Jm=8`t#(Jt~NF9MMLxUaPLany2p*^qr`WV$aE_CvxHwxN#)MdnzfU_3MPdB+}w@ zlL`HsWQKE-vGq)n`|Xt3%79sIko+c3jv%>7OUxMbL=6p@p2h?{{VIc;VbnS3>6MC} zSa{*;2=Zgmyb&3xxP{;JB!h_* z>}SA~7A$)i*+Q`F)@0yocR6H(6zH8Hlfqs>3KueT83vex>+d2`DC4@l3Fw|Vz*A?W5whBU)qbI?tR=tiujJ8im1Oq}xam;d~5LOdi| zx{V*ut|tQ2IdU?PA(-qW9K)!0s@Q_VRv2h-KG|IUAR#u1v?i?JGRbXE-ykg;2FlAgCdXnGv6rTJOyto6M-V^mwyr! z&ef|(bs!&*KS7-tlm1YuPgel$!**?r3LPA_z@Q7KWAo4YDL^{N^O%uznU`h8D>dS=$Cn-1%;MR7=8_^CJ=^9 z)l-71|AGgb0be<&nuBF|o_!Cwq6_~4QMUiXckzR|k#MI2w<|{|Q8>Yv*aaIC%KHlP zeofbF@RY$rrW-)uoD6nKrsVabDU^JH&xwAcF)*9de2%2%@nO&ZUD{3RC&DqhP2Jh4>F)S*)m!$9ON1n3*Jr@2AmR`?bq@5J#uBVDMW|soeIwuT`)R9_hF`h zqUsgQdo$A$>|Bm8@DP3K?Mb?<(}tk@>q)r@xp)5q=)v1M*Fh%4T3g*ZkDHg(6LF8J z`(3)aX$M_*%J;D@;2IJn$-))U>p3-kONZxB`1iE8QEpw;o4CYjBaV#W>Q5X5#*aD$nwd_msDN_S2>je66++j+IS9RtD@ z&pVuDTx=}vKGzpNubX#fJB#RM>##o7?&i95@5^BQrAZNU-qTT4* zXPE_|m%Z%-Z40PCL`97(ZF-(1FI0Vi~sP3-9hqqcQ50`4T_wZ-o4ELzJ=wExN{COFKcp2A-ZAL-2u2C z#q9{CWBvu+AgLY10+{jIV!I5gUBYE8w{9PEM^Jg%T~FiPK;u1yZZD_Pl0?eIHZHB9 zAX{AMi{}#;T=ZPrw!Dd7*+{R9(kmO=rCHG9JY5ezLbri$Z>`kYrR8F|vRLK-S=@VH zr3-)DQ&9g=cPG7iGQE2CTp4#Q%>g5V7J(fG6V9q5oksV#y9Kpyy*N0QQquSg;%=qq z?rLgi4SlTT-|O)CsJo56m+wX#vo|8R07QuiKnDE^9lbA#TOkNn-VP*(1UB1{2MDi4 zoF3gHj~mp3Pbp(FKSpZ{{*U$eL02H7`UZL^zK^>u>EcEDXwey-27UZFovHW&eauop z5g#3IBfUkNynJ;%zjvJ9InMQubKT<|zI&JM;N>Is|#pNM+E6ee|PwqAwHNQf}5x-0b1uf5Tz zwo&!UFYPqihbU^cyCBe0!fnYCT-65NoU3<;ap?Z{4cE)>PXPIRYT-t8 z(TUHG#^X)?W2T8sawh}%=og6`Mq$U+#Wn5SX|1uaw4f{M-5&(b1E&~#%VgZ*pws$; zhY#F@i@~LNE388G&{$o3i6)X)OaF(yQM8*MLS~~drHUktCXBd0i#F_C*6Z3>x+-=` zXt^oAi(=BeZaWlNb~lO8gU(zFd{sE?EYctUh!@0U0XU$mt1AzcX6g9NN?}|eW)FpQ zl(1l{S83J8))A!&LQuWG$l#lCDZ&K)9&uEZH6h)+$iyuV``~rVFZ37^!FOs%Cq3tFMVu%Gk z)AA7F_0%B%0!R!*fQ zfPeY`qnDkFj$4y-8ypa&&MSyEH3G#1)S#$V81qL3x&N?f|XwB19Z&nte@T!P7!#N1h0i}q7N+ys&EENE?Sf)K&AT6e) zz@9DY%oB|O^?Ytc(E!n0wQ_`Vu%L}ur%cyQfMU5PXk?&D<`OD&kZ4=oPvO4~$0O|; zix~qQAwjo;lPw05-%U#P`+_6j5J}+)E;VNvj#g`~;AZ}tFaB%tg`wyEE~<|5=s6u} z`z>nth2XkFYvL4N%qn>Od=mx)-3e`Gl-1AgL36ON@8ds0v#K<*tfftT?P?IP%C)c( zw%sIF9ECf`JaS-l3vZY(7b2wwK1VhU$!=H|Z8$K%^%!8t2=LCh6H72slW=K^<9ki# z!49nO`OxjyX%&sZof?H<(8RJq5Q{ll3{ITJbFff{;et6yJB>FUiSd8)-o1A=OVG;m zgAfiEZ@q<$3}uakwFbvjPcO02r-*wU&YXL!4?0@s7Kcmgk~ z{jdet%ece}vA9rJGhE@OO8fbpS!}8;Ix|JBr$Y1RK8LWa{IEQJTH8nOZrvFjoJp}; z&9R$~*GmRFrx#Ike7s}=-!O5UXteW;VA~uh!INas3-Y2%3uE*6pByu9CW^O*C)Vuy z#+1HoRukS@TDM|780pTVB{42i>v~s|+wDec|J2kXc49lTld!<1sQMJv*)ZndHaSlc zFKp6@b{)}}wPhK1pmKyCG-i=j@~wImAqH3~swrI+c7vx3G3<9W1$>V)*jkFq8`Acm zCp)jDT!d+l^o3DiwJ?o2gY8|jckj(hw7ezBA-*XF_oYUuwjzZFLVgAL zWF|jCkSHd<7(W6_TyImLSCig0NN7wlYea{YM?Py_UP8la;-8yo{DvN(=8{-(K(sO{ zgiwV6w_&mTv~n+IjdU)hg(h;rrSfKeXe?O#Fbb@AV863?i(B-S zFDbd0?ipUEit@(YFnw+{J5?2h%pcWAFsbzQDYKtprZknlE>a2?N9aMSO4+>x?SS?-qa!`4G*fxdds4U&z{CtR^PU|MwI z_-H`1L%M|QAMctQmQ~(YIKp}BMffZv{|L)UdOl_xRe1rYIGtv|uo@TixX-3JbH+cE zGUM(IC=3eiol#Is#@H4D)$HpNDSg>+hcXo+y}5Ux2k-eYR>HxX58iQj>hK+R7U3zu zt_Ym+!g*L!&ci8eOgU3au%xHdAk<82lYy~yH#$i+7Sa%ELy?lo;#QsV7CDpbVyNsh zcg$h84-2HA(~puuDd(r}Z4m)0Ev5HO@|&UENu;wB)VFUGJ~zEz?B5~R?6A)gp1zjs zm9nbf5>{H$(8^NIDTcAQb#t=19T-83NP-A0YSM-_AUP$kK8wgH zvEUcGr8+WVKU`nl3nwQFI64j=MZlAQd@VWFeagdSM)`tQf zk488ch1HGwB4R#~P4PmSPj(ERoNgI-qyBnaE_t@-C<81z zHB)cpN_VG{x3T~1lAG>C?xji%f#i!TCdV5k2Gid%SfvpA?OG05@>@H~3%+P@mNhxewJIDK@`&orf*I+mP^~FpIr4oDEXN!7Wtbvz0+q zbU}e~D)`zxY%;q=8lCbcgm3@b=!0v{!aDBUlsQ^1 zY~C%)yTGd7l!M%_py4FZ>8bIcn?y@dLLv8vNx{dX7#j=)zmjKDtSQg!O~EEq^l}o> z2`BUjqP~z6u@VS}WTiiNi3tn)H@ zk>$-dKg!dX0TVfx^5Lx(ZQ0fRy-f#`4(*ZR zGh@U&Nev}j;hZh@7!t$=LaSg+RTfq#nhLSh6pFhj=yklc2kDF8JI9e1ya!&IZHoMwXHM|e^eDO6iRd03`ay2F&<(;%k`84v`^C{B$51%$QXRq;myx2#t2Pv z|60oIW|#?0?qA`_6y%P4MlesGDV!SAOkfR}n&nkGUt$Dgz+n#g{&J!O1WSX)f^lm^ z)OSKC<1B69nhk?03>xndK3FOkQk%}(QR-ycotzv^zH$B;Ql-gL8$N1Op^{d16w0!umg zrT3BKipyN($GA&2A)MX!NrYV`p~B2jXXH)j-%T=dJ)eidfg`DD1kLNmh9JE%g$y!G znRbvoFWRtPY4U{|?3ocz(C}vP;dmoUrDdE& zqLVYLy$y4v2AKkAXYFFOKHEL+tw-LcdBh{)2#jpTe=s7Bky1bmPDtPg3!10=ov1sC zqdyuz>GQmedZjZ20aVJ1IBr2vVl<1lO(JmOSQ97A+1GeCnn4g`3{|=>pl*1PmO6{@ z7lawl!+g?dJWyYl8-gV_s?J@$7ecpwuvhzrFhAY#wT2Je4VDpSx~CvR zw~^@HsKOTMi0aZ*EpLvFtryb9y|v32L>J80#!1c%_{7D53th)eAya}-km(!HVzp`iPH* z6l#+h&i6M(m+G=+r}I+GdE{=*v76grb_{!7+2`=_(89g}K8Cg1C)7Z6e) zn`%DBUfRir*__q{Oq2;}qJ%9cRVQf1P6pD*RlAI7KqGWz%td>mTk{;2r#a{QTgU%{V0#-FdG0+sl{ zeHHy$=RV3s$NBSr;8W!^a9@odoOZx{4SoA@`gqKL_BefeEq%O>KAxZtkyF6!;ya}f zaG%7NVD13-^;F`%fj{5KpHK1UoA~q1{P`CCd@DYMs{9H5{Wkjjlk~xd`Q5kUD~9?% z@h9fczWY;D^BwdNI-BpllU}4l`R-3s@z2ml=n%g9EK7N2i_oei)LLW!zQXK!D%UYalZ;U%r{J%w?^iVB!RW7*U7o@I_=GW0eg zwJk^WWWsceyKgc}?mi1EyHI!IIQ+mlP4*-5sJRbOEMngts23;f;B-N*Sfs4Mz47G4 z<~!(-a-!NAZgj{*x4CF1hU_saW$56cyRZmrso^}7RG5Nn{Kl$!Sa2J(g{Z*2CzT;2! zub_F%=sMGl=dB8z$;NL3s*h)YYDX`q){>Wtd0ImCb0N&FDwrt4Zv(17$pF;{&_^n+ zem0D&@O;A8*!1BF`9rm>z-|DpYJmLJ3?Rq(GJ~$i@_Y2c+*unSK()x;-JKYULybM6+V`KqD3Mae&1~y3L-piP52Ee@nr^Dsm zXZNtj)!ao{ct_+-RafwDOt~?)_h*<}3+8<>&w{lMK9T_{3+9zj*)s1+2B<8US3(tL z-dsBP)fqs}$GW|AaBt3i71PWBI5*=ozcT~Q?5CNdHg+J%>0A+c-HLUD9F!h;+E^SQbucP@4#^1kZfXYIDNT_V}$H;bjL)n5aBvfJj zF-m?p;XG4QIv$Q~>A99WBLnEM91;j4$+wp4tw&A;%%C0HS(?l+REt@XP({2F539)$ z{I=?0Q_x?T0V)fM&WNiQWPr*ZS3MRi+Q=fIahU;N1Ly>F6BIY1T zD_9ua@#5*p)hA)k0ROyRzNS_N^jXZR1n*ZMql0ebUZ&2EWPs|!=p$ZLM{J#Vr1iwr zV;P{b$5q(Y5!9x_ci8_X9vQm%tr=kZvqX&bYupG#CXsO*o7sE2&h5PcrNn8}#5p+4HdsP+2Gm36-rv{d5MXELcQB6*hZvEt|iR0pvK! z9-QmCx4H8rrkMdOn=?-HcQfG3ewuBy#^xRNHn)Y=$cU>mGC*aItFYGCFediqO=0Ih zH3Zyv1_<-(4uQ_$>W-d)2?{+?&4GI~{Fnw+OK1$E#6aYM42ZPQ6j!1744UG$3{Y8U z3JI01rno-?R2FO@p&F>BsAmBAnbs6Xm}Ul`DKbv;@eDY#pJrQ4@rDdgS!jxkxO!&> zsO)hS))ZUDf{|LLs0)=D!tQ+;pv|u}1Zsz?HO@>4R#W(iDh#Ca79nn`iqIDNv4Oom z$bdZyRq;tQpFvgppA1l0s0s;{t*ZEX2B<9fLP8Z*6$A05{(A<{pJ|ox&w_SrXKCwB zd+Xa`mLycRzSOfaKxIME8F95Y16207vi7B3o&hW?YF)&-anP<*F$GB)!Cq$C(*5BK zNVAwrDV=OBoS6(zSy(t3C3!6aRQ9+ETR8oBP#?|!TYh^+ATo)13Q08PI6Kq_0Ku84R1B$^eyx8jw)gijH5(0F?zhNT?!)jXq(a zCbj1#Fwzo~KsC5{IjhKQY)nG3(ZR!5Z3`nz>ODUaf4Vyg~pt3M*GD`9v%m9@= zuCf_6H)Mbhp_+0kx1}y5i8;zh*r%?QCT(jXR66*d>LD5Wx(ggCkUX&Rc{&{s#^YsPqxz z?qNEYbq78YjuO5IMUhiOeDDY%b1$Zs-+}hrd+6IM2`c*eN?e>u@9hX3eH`DTuJCz^ z2#>Rr1x_D|5qZ>}f0_nn?E`2k5GUh@>WznU(t8JQ!Rck0^&XcP~8Kw5Zw+eEO z>CE}Kq`FyOD&UOm(aJn-#K8$Yenn@z=ctmwG-=SXHy~P;5j=jbr)Tlb}jyu4RITu&EE9+b=xq<}-lHYECkvj*$O8}4<-%n7(AOdBhl*wR;$lCG!){`OUQ>TUc zWo{yT&2uuZw&Ko<7RKoSA~WtCk;^2Yu=uJrT;bDP!tKpBA&Gcn|Glc6*8PQXXL+*l zl5_8Hme1XP@42^@s?NFl&b`rDqA&M9vL}V<2Xnx6M^l(Z~Ho`1o9+uVS)Rt6FRrwv7TEN)M3${%2SD=z4u!;4oGAf zl$O?QE@}Vs)>D^T+P^mD5Sljjo?kyko-N$2HDARI=B1?uE@mjmSNH$}EtQea@rr^x z)3F~{av|>`5+LKct~uPbfx>+#be8HzoiUX1m$ar7pGRG1vB%*IMG;%Wn7i>)l7+X8 zJd}vnD&u@KoVbRj!V{x1u1e`C!}##e1Sy>HXHj z7L0+bo|q+Y^Itr3PzB}+QKL757zCXLY%XDRj`f7+7DiVE&NXQvbffUcK@N!xPi0#M zo5{1ly7dPmG{Ap3;*Imq%>NSMvC5n#dz4UQNU3 zPf!FA9~Km;cuy1*z26$|n|KZ4OKUh6Q!iOhPHv{YQJp=BEGG{-NO{?Wtw0%=VMZL~ zWn-Bf@bd0T+B_z9FQ=^<^(hvEwe@2IwI3cV-D|%7Afq;cuc`3F@bza>da_~42w#7i z$7j<-9=`q@4WmE4mWU58!+3>gP(*ai_|0JG(78S7RNJ|YT{EWg zRHDr8{nmt?Tr#9fTI;z4_pe*eP;SBfvOpX?T|b~w-wRh@XKB`H7O)ZEr^lz2Vh2mV zV`Y-sb7g1lh<{l#?*<4-j*R>ZGJQX4w+iB7MQW*Zv{b3m-GA6zXyR(yimEvGpNBvs!6VN$Oap%fHfZpyMq)cXrkdde_9z4E(>$7j<-9>ISL z4WmE7FA<+b@XrnlSrVoJ3;x!yz-^q+x$RibZLHu=<*7u$-}|j=rt=9k(;uDMUx+@u zP5Y6_ZT}1E<}eR`k# z4ff=-9$E4EHcz@4$q*FpL}NOuzRYcxbere#aG;gAXi;E9!l|$Hd=tfI@!cA0MEIaN zIZAq;vyT*5s!B4nz1fPXQgUhgNo%|&X!{IMRM!Dl9Jck|tYKTP_QUWfOw;ZXJ2{Y} zeiLuzCfxp`R5u)!aq$MP_)m=){GvU(q=mfc_|(r+!#44$;b7A%{SbUIjVkjkd0CQ< ztJuf44669Fpf}97oWyE0UsDFYzByQY=@7xc$x88>)(GV%n$|W6S}TaS(4cHKN(>y1 zo(9qwoFS@dZUZ%(hvv4+!xld2`8H=rU+K6@)YN-#~^r)O6`*KxjLILU_;@#pa=c-1_c*zhU#RNtlLlA65d`<03M1WT8`= zqn*YCyQTaZuIE0VRNG;q#+&_bFTl6s?jioXg+ArR%)c9KNcaDZ z@rfOd!s~JM-){3JA#q(Azc|EO|Gb$1d^%QeA6-!B_?PS*zUew#uGhjHdw3??2kj0v zknhF!`(;>>yvYwP1ze*CPe5a3veayrR+Qiw?6rAEXzKNx8Z()&zRh!O-eR;nuR6?Y z^LQ$PB%tq;`z)w7aC*xd)ibDYEsTX=fg!~oijKx-gGA(IzalRNdF~2 zLPqrNxl!G`sua1%Dy|M|$Y6ni)yM6zYIARahvx0HSLBeRzVho)ZY*-;*NzC+EBr*W zcC4kks#p-5cw`{uUBgAnJH$95Sq*wz$oGK^P+4R(kWks4g#TCus4Px*OQ^zmI*j1u zbmNe#8q@pP3?RpyGYZ;+!@k7f(@Zl7a?XY$Gfwm0XTX{LG}|UT_-+QMED|1M#MS20 zt<}8Ey+0DFaKeLiV`T7{vQ0iSsQ-)%(B)5gAb?IVqhnnf_KM(wzHB{(p{R0L$M36h zp&jHi1A~`lz@SA+k|{I~kD#r3IFJD<3-utOvQ-baWq`^;Zb+!Y>fy8mb6?G^eNLxR zhH>Q78MMQeE5pS7f{<*dtdn7=7E>mnija4Yi+^680V)gf&WNkmW`N2bSLp`MI$qV9 zn?G;M09QV8l~4?VU?Ze)ui{%FlULnq4)%Q+25V0m>65UAxYf`SAISh!zA0cBhB5&- zXdm<~ue#qH>Sri9bH&l5e!%W0ss(**k zrr=&gE|p#d>^VKKI?Y_qDTqwLc@0YH)ti;CIVbf!w5vLvy%KIWKJ{@U=Z=)?$V%1q!kxKyVbI>eK@3b~%sq$} zg$7N<_Sj|<+cy;kgI*uGhanRm>5jX1vTXFWV$vp9$4t;Xbd|{`cXqgZlFL~tq5K{p zRPPh-Q&e`RS?Ri$5xck#;Hi5d#^62|ABR@;F=D355ntRH(moAUxupHqS$JiT580*t zL%d0)&O=24nnZF*ig_Yb;1pBkAx9|xE9l%4yp;P2$G~8!2$jaUMHwlzhpW*!09mhA zZ_BzVI;7v7vwH@_a4JU558%6t$R;VBEENtwi;#!cFpZ)<-+D?<8K^{Skt&bRriq{y zad+c&UPbnoKk`8uMt@pFB0i#W`$(Ads$n5Z!qm5YAh#}zxsBUYdE>AEei995w-Ib- z_f5HH7aK4JvcelOzc?&J+0HC;r~zt}XFoD5V1N6B&g$>vp4HX&hjN_J14EB~epskV zkRq$YS=Z=+0nyin3{l@Qq(7JLzYYuBe?r42PJ;Y94Z})5uyqz*WzeiA>gB{$M(?-I zxFga2F{vlDsnF?1Z^O(CEP!yT;HW$~>&)a)*(h*n0bQSVz)vz6Wzc_`nBSVU2~Es2mnWK-*B_>Yq@fFo)I(!#e}6!8D|uLx@F%I^ zJS2SO4Jwx^Z7khc#^#E;FiW_jDo9!YM-jF{OSp%q(Wp1u*a#IjnW(j5O3-cilA?`% zf9ho+Lj%tZ%zb&VnA5uxUt)r>AzKT^+Kn9^uHG&+7acg4{)&dtpI!Y$225Zj?ou8q zPq(e7Y^Tw{cv|}z%ook&pSQ+%L}d}V4}{Az4CF!IC28((GyD1H6*Dxf4=bB-3t_%a zJFLuZz&;KuQ$2B5`TVo+jSnmHw|s|{!=oN|AI2qr-spU(U7D!W8l5)IPaB)9q6}h% z%0hbvV%p2oVsUhjab2K5N{ycJan0564PT9gRQv|}^=6581`@DUS~$*KIP5IapPQVT z)8stUrZ`HgXmHD=ghsoAziDRnb^e<0qG-de9`-1P{R(q%I^e+E!7=e3+MU^e%M+Rv z=&<{-@+tEUd2)y#j~BZ)4EFRsPGOLuu?5jQc?L(q%|-4*IvlSOO@rKYN>VAjiqV} zTQc-e_N^W@u^n1pDz+SFp5NS~Xms5D$!0f~6>F^fLD{;?2gOj$Oph)j@$_1lJY zY(??t6HvmO=ShnYGi`2@K=4oYe>Q}it#=yI&K!HV&=}f}WEooaqy@c)_Vv)ve){`E zs40|3(;{s22cYUivLeJ(;Z=6vxiM*Pu%9%W6WC9nc{`^}Lw{q>@O) zNu&Z}#D8kwBi1G4}<=oNIe_<(nCxvN*dUp~`kj@j)>ah-;e`tgn%i}@{o>5x6xVh%k9C+W&7GS8DhryEP-UaJi!#8L zpXLO}!=<@XVi#kIn4(m-JzQT^6?7LZHYo4r40yDlyc@Dn-itCoWkGoos%(_!WPmL{ z(f6f4v1+ht} z!o=2JG~(K4*&Dw3Nlkz}P*Tg)m_A_M(|Z=_=THr3Q96#NG#;E|J?hPT^=}u2L&i3-c9nRAa*fn`SM(Mok}z=DiVpr;gy}0kvt! zfxQ{1I^{?&?@SXizdv02Vw%Sc9AKK0PFGt5=#Ac9YT}ez8&{~CA~{%K%)~VY6y^?i zf?q#v*Hg!{TuBXw2%Of~!#rI5Lf_AE#(ZTUZ4VZ(J$MC<9=BSEe*urs-gc2+ z6?u{MmY^RV7(J33a7?Rsu~dikJ%dHG4u81|4Fj*-dE{^|6(((Jhm&l0qvC=Y$Oj!f z9Ez}ZYt&#c&s6l7ZX>171d_^ey-bydpU`eBsd~xR%RGd}Y_FGLDNo0Dh2;O$Eog#~ zA|<_l!_4=Sa0(JtD~N zM1MwdD<*_*O@T0hW^?euQ|QfZ*D8e>piT*Q$Tqm~$3*+;Zxi+5_K7g~pjd~jqJ{4GS z^-i0W7)A3?cuT#xSgPR&@qA%+rO+<5j-+TUfAi2FXaA{T8vJhno}@bxXg5~gN{ncY z^#=kY@fwQ?lX{J4EO{jwAu5@Bzd|hrxpytqHwoygDZM6)H3y$Q%?LUFaQ4EgA$9V2zT;Fw@3$_p<^Y3|(>ll{9{!j0MCKL`KNQ_1 z=R^ac8&_2=V+}|auv3~V7kHt^p-QDCg!<_vU)0Wx+ekOKDXxNn{d1n3%+WSH5RoHi zqhX~HB9<=|_AN)GZ2~J&Dh@{S3q7do**7Nk$ownRz&~sW(xunS%))SgrnjDRZ2N8w`?1~2Y zh)Px81W|PhdVwq}g{=_3!W9K)d9v`5u{)gQvHkas-8i;y?AEdSA5lye6*sZrCkETa z)-WchY0rkS>=F?;HJ+e^r8X7zEpYlWTwX~n1|?36;qquouL+_dhsb#aBOsyOSRAGp zF0VslgAh4A4VU#o<`}cLFO;Mpb@t9#ZyZbTMrRanXm!DYX+%9Yt;=`WPf{#0B|#Pd zPIO3NF`7D}L^0a?t$WB8%PtC@e%Tu#44!`3Q~U45*BkL=AO61;|3fSoY@7y4E;)O| znu>GF+561MCw5PUBlY2OR{?L_eYHU1Q|+& zeap~(+?9V#4F*MM)Lr?Fls*$AT@DHQH6DIKyRl54B0>KNjSWJA#=0wAz499o(#Dv& zumDY+y1W8(Qz4lyY;YM7pur)91!(Gs5(Q}Qx1QaD(@iOu+TH+toMVLIAg84H?F><| z$9{5hFwh0)ZD2mmOVQXM7$^-_lIHwvfB;`@(0rw(o?6j{eVl28kV_r@p|wcLtqy0y zA(?Rs@#cetxq7v;RO4N0-k%%D&RsO%)1doku|e(k*;9M0SeH1|it%nH>;%O~g_=Z6 z2Y19P+K*yvP=i5sk1EDyN}mZrHwRzWc=!qJ#$qrThau+AufK=eu8o^ z(a)o|fhot|Kx2boqBLBkD94SOx3uI-QjTeakV`rKkTphgE600{xbea`@@Q8>YsH%v zaHzc#O#j<*8VLPqJHoZ!qOHJ~HG0a z=%Ej#vyJem{WW3t{=ZO*K^9+2^-Xc}Zz;VdXs#U0{7;O4gmzrQ%s1kd>^}OtyZg6Q@Pr2md7p;jj zw|qPlIOULCI&zdniaazuu^@IolYuDBKXMKpf6c?54G4s z7T%ZAYXY0)kcD?M0utJ_m4ypvY!HS`Pg%HC$Up6YNLdn+#U#7L4OZr`7O_mALF9Q@ho9wO6vbFjp6guf5MQhvT+>uS_ zY_iYG3WZL`W`@Lvth(VO$-1+M%vbG7+gU0W$M?Vu)tsB0PfYz2PK^~xbX7p;eYrl5 zgjqM|YG*P4RBZCBR7CZkJnPwa8aZ=<-Hsn|Rsvb7!r68<1tBz4g;8Q~;iv3{(YTvE z&(7~hyYt@ly`1?ei2kllbQI18v<5Sy;wK_GWsJgq4CN6r3Rw{I6JwIT8pT?#JWn=x zb)L2sm?=3a{ycwU`e0cMxwwBu$WR%lA|m?ZC_NDe%fz_?I$P$Qk<74Napr9Oxu_OS zcjXyO`KqpaA2oJXa$|9Ka|Vzh%$iRUcKn2iFi(@tsU@Cl8n-ywwBfMR?lfzmb4~tJ zgNRQ75;SC{9qd$Ts77XmbO z1EJlBy6k=IMlS?56RLdGl3Rf5yv(M~3e#v>Ugics^41FjA1RRIP9v9J<5De=mHGAb zjk7ZIH=C@?Y~2(4_?A}&Wa=@S=gGyG1BcENXM0?b{5oDyClC3j{2{-?A2NTl88T-B zHiqnmuE*3|Zze_dCUKc%r|L}fSaGR?@ZoDO%>!WGx=IV}bsv@N$@g0lT;31oTso-! zZL`Iy_tc6&*j{G#WLO{rUmZV0X}_lgZm;N-(pCfc9?(GfzG-9v7Z@xbiv!r?=)nX3 zIL+8?3aVmgLEoZ-_~B z@y3o~t+reQ4rHcl^^{K+2J6k1+H>V1T~fMV+)XO;f)|e*!<`7Nt6uWRK9yY}_^5u= zX&!6BvvSp)*s2Wp62fVXd-+_{uzY8|*c0skHNztPED0$))siQX@u#ERc~3pVq9lbp z{nf4;_W0~p(-7e%bMcT;&zOh5iXz5WcL{5>Jl>#~d2qi%AgJZN8}_sOsGm$C$P6cx zX%uFo8I@0t0Z!KrJASsBwN*vIN8v(amj1(jmTcTX|AOYByw<@e4qW@-t=eybh~EZO z8_%=mVRr}mD8-s>Qq`{|%bYE736*VH{L?Z(^#LfVRH%Ly6%44tbIT!W{jHRuA%1%@ zfE;J}1{X@@_vpPfPK*@1K1XE6;A@VhdtZ+z-=SZo+Inx;?+yYxK zV6hjY1K*e&;7_62nzQBe7gE>jRcjtEwpG=3>an8 zQC(A25?mTAFqrfoGR&d{lYWa4Zq1}8oe#X+M#$l7)(l@-((G9sL z$kZki-)h&SBBB^-GwZ5~lG;Lr2L4tu;Ln2EI@zf0RT-eNpf(AWJ+(cV0j~ViCcqso zwQV)M-im^vq_)wosOktR3p_Un>=!bi%!0svE*pV;FauN;1SX*h6WChXP%zOZ9vK+> zy$rDBCo%!@K#43@V>-aCrw2UJ&;JK^m1Tfi-`+bTW%}31%gia$zd@5mKV`~v@65z) zhqxHEfJa|AO+Q5~`&MyEjM}&x4%NEoCUGM!rPy?imZ}|`u4{3eR;zRr-_)jJL0q7# zbLa_1wtwBA-fw51-Z(2%E|_$N^zZ%6Coyy1G7x~7dq&cuufHHjKjzkK(yA(D8gz)% z8fuspX@OlR+6qaGBwWHp^Z}=_9}Y3ze`eWfa~miwM>?{hQ|KXA3@2Nt)=Tq&n+E;u z*k}^B@RpL=p5S<`aG^o==L{C#-L*h4@9CV4hJjv&Aaa6=3bm9lXE}U=%16BW^JqLU zBVudA7;1FGcR#$ua7+oC%$LBxko3Z>$m zrz$R8Z11-&)S1N72-;i%r8XOKB6!c=ztE}8?VkzpMBz%_y-L1MAXNYA@PhHXFz@f2%VI%%!sVqMRACUUdxrGc$Z&9>9HiHO%r zjZ(YJIlSb}1;oTD{A+BF8oX z902bIcN&@Uq8p0Wru39ye42N!;_=xuk%xD$qha*NyFtWX5T?tlpe2;J`;`3HMO7=w6vwT8gPl6hnu})%Hw` zbI|NWRTL&xc)#CJ6mPaC{Iu1sUYxywQJrAqQDJ^XVV-DZITv$q^%qn6vXS$W611v# z50B8Mi9D*}muVRNsfr-#!>S?`^NFgW_gm-ore;&(rZySY9xqbb!{msb`a>S_FG}_Z zrItz29iJs^2X#j{{e!_oe`-%+xwOZh*i&Az_IMhN<<}lp^*CaXJ`)km@qKWfODHsx zjFS|aS!;j_t!qsQxfR;YX3UP2LK6lUUG&*<+9?a3t!5t~eHJP-SaI`MvSzUn)e=ow zpN*hlHNBCA4;7v$eRfVtUl~TI_1Uv{gf>m&(P!t;F#6MHLDYx!St{le^;z$?to2!H zlVN>!uhM4*L-bT-f%sv1BGWA2g@-(p6oIy{aIAu> zuyhS)xzcJ^YKvl*s??S@_3~k%6a^9p`7=m9@Z3-v58IP_f6Jg{#&5zhhzbXwHaMxa zG0d1e+pkUO%SPr$mO($xBeZEEkJ@;GhS8te2%@OlO)bY|6ESzYn3_5*V$&?_;;P8wh8%&I44h2lXU3NxB6w@lW_h0Fj;ZaC;k zOC0&Q*zz5rSP!~pG$HT=DV1nzSfo^Xn)r!RD)F}ylwU}mOz)LnNE!M;?{C&zLvP5L zAS4bJ+q^?@Z9F46h^`Qs0ea>jiU;@`pqcNHd_;$kXt>B`tX2M@xN#vDQ7{W5xi?1xAA6 zOk4cu^Ge@8>QKz2XCMMInRIT+Ci(g3?YP_xxo4ebDF1n|z?hGB7+~a{4}Eay?bI+I zT+&2Ln+?M~xCG5w7_W7f8Y`2fX0x=yez-f!O~)zlQv@d9X{%6QkOY23VX4z<7fNu@ zAxweOsZ%&AMYE;2;`&q49~vxf^}fw-qG9D#jqKY{p%xM5jD~|vy`B5(v+n+x0LGF} zn*1>}9Q3ts3cO}2q|cG#2o) zuOT?hup<$19z0{hQvH)~qD+f3CI(_8xcTD)TkKS($ zNP47oluLSyTF>b~q{pQvN_zBOvCaYmNqlTW>k;YkK!DsLrV$k;^_t5xI!X=qCq0%^ zU`QBw4(ZWmbR@KED?NS`jSW|NycIf8`q+f@2u^2MdZf-~qV(wf)_|l(T1UC0$Gz5b znjk$gK%uuhe>tlKq_;e2iI}+Y7fYOSAbQL5Cxj7o47PW`z3k^+7kMJ)@$S6riN|S#^IbZe_7gBm@U?uxJ7a?D%3A2h?07(gjKC_@U-hqeP@{P{sbI8 zI-5sjskG>{Mn`9(4c`gBQOlXD;|?;v**aYjG3zYUn-2a^RLc@hegz;H9i6M=NI7RIBCTkd`k`0*WM zp{a4dKyxcp!MGc~fNfg{M86dlSDOOho9pQ>4VDu1^=N;Fh7*l#DjbTs{6~!=VHg?< zW@82S&nYk@sI?sG@*fx-3GK%6c8VwAq`fCjU9J)8GB}+t*i(4=Wlt0aLhQzrq>)7i zmEyZ_U9J5`#c)1vJslBE_gQWtyvp|O*t>T&MM7i}bUrhOMZ)vf+O=i> zo6*>bvX<8hehE(J6@vY>3ke%~bEKi3-n{qp-Yd-n<}L^C~EBQw_KNSL>m zh(MFn{iyEMWbpL@7?aDJ)YLGzyv}G%h}WgUq+TOjzDZbAj5>crN6gp`_mvtYqR+Kd z-yG|sDZM7}Uk<*01tTD#U0c3>9U9Ba*8(Gwc>SG$fC5}67$P`xdjv!GV>=t{n>N7% zV&BaI+9TFzPGDafu-xi;5bzQ{WMjNU<;7LRp7^w3Oxgvw$kui0t50P$PV@qp)`0_A z0NWr;;oOh;HFu&->mozLI_B35RtPV6Q!Y^zX$Jd<`TYWkk>{_&#~0}v$NciQoH4%x zza{f=$Vl%teKtv?M=6YRf(4|}>~*2rs0_qG1T- zeR!VhPF%0h%osKT=uIxMZEQRrm1zndZ^JM@w< zDC|!tM6((G zY6g(=i2{Zpg_+%B04bF>--imxUIA<38!m6|h^%##QjXptv^iK;RTJXeEHb3|7cxwt z1(H6;*tWhJ?XNRHWpS5~ger2E5G^K6%&p}|2FCs+18n*45)vQ}m)f=l*UJ0%?J|lR$XPnhu;QN&&mLm1))i(!i2V3BUdme zJuu+fmjSN)L?*x;E|Kku^UbJ9ijvySXmwRtpf0Y0fyx^)pwdFZ9mqy_cVvLdg773% zVZvK=k%>I~HgHwS09Agn6Tl9X>~b}x&mQ)4enk5DTll_C_}RmANhR^w!ylyyq@VKa zA%B;<0Z)MtNVIItmvn--)l&nF zUmPqN^{KBvMZ=1(BIh=!Fmbmm>fE0JX9X`PkoY?RQj?{S-d#4=3YU=dfZ=IgV1O_puHI!9S^R0KEv7R{IWxBI8>ooOYcajK|+FZ2*(+e>NP6_w6@OvrJA$T2& z!6?B31FaJi$=qr~%@QYCM(jbui86u;`<4--!qUT~EXl{+4^V?a$q@B%UZ2uu!bo$- zgKK#B3GLd-gIm$qAmjlfC2Y$@Pk{!fE-VF7XD(3+^nPov1^F8UoYr(Mxp1NN4Ca;# zmj^w+h4FfWk|>s{Y$_aNBLR^zBq5qkyVIoXFCs%mibOCy#s-U+MFvt|YR@aNG#!x& z4>3}uudFa`>Iy1MlnUn{XfSYU%p09i=+RPv>2E|mHyeLErEwcJl|w2}AR`O^;E=-H zk~*S9Zt4Bj+3D0^%w<|@xftXa&t(SO>FY#o73b{%e>H1PM2HzoQo%hoEier<=Df2^ ztU_K3aY}AXMVA%kRkakxF&y9q-xwtZs()*+G^Um5uQRF>*oF!d*(Pjo_oGbzh#E{+ zrhlB$X97j!P^O>a;U}~kD|k|j>pw$d!&9a@j9H%YGNx`{!4yiVQmw=O-Xcs7o;pMKd>am;NnXIdY*4W~y;jI+>i;=+Rp2P!L{OAV&W${$GSGhw7T zWaTA1{DgL6`8!2cUW>*~gshY&XpE@~%gWTL%PTAIvY(?^Tt;MNa7bZUnL46GS=sxo z|G&L&kGG?$4kjV*cYp}tAx9$Sgd{hh2FZn>fe=6-iAm%gFX!Gn_uey{d(P!Nl3Wc6 zLL-5(ARPSEDlf%nQLCj;sjZ({q}E!cNb%Fw7F!jc)mA_G*&lvut-WXV?3vkfX6-rW zhW>tE{+OIQduGkr>$x9mt$ogBhTdl}ooS!UWjdF$h1Fm>EwE74G5+3m1t^ppL?A4% zP}MOWv&BLk2%xXt>+9wgxT)kIeOocQg5EJXNFE599McV_0ek?pk1~Vq!C#aaM895? znL!t8>-f3Tg1QhC%dUlgH*8UbCey)6Wzl_~-r}97b+(Abt;4n7*AyB}XQHuj{hETa z4=U0QZ-n9?jj<9W^sKBqL(NikwBoFs@{FBFQp`C!W9P3@fSLR(Bc2m+?OMjpXXChf z55D*_b|{S@$U}K?rt3TvCvE4d_TKUPoOJ&IQ3%NNbVOAn zd-mF>G}5Mx#Gke4n0=y7=Z-}abxyalI{p@EZ;JZ=mQOWK803EecwSUUOqNu_z<&_1 zSi(Rziux~E2O??6jWiiYMo``Y06!IEP(=`psaZA&oVsFXt@+lz0Z0X6B z+C)h)lb-O~#cHIyrxD|Iy(Pw3L=1L>@Ky_~7=fA+t5~4sdYxX=*6S;PS6g6}KTuO3 z%b61Jt;-yLKGwE5s-8WV^Gtm|B;}+CnNY;n#B`W<8Qgl zrXtNlQx7pe`7{Cu27a*Mo;}k1HRn53r%nY$O9fdh1vV!d&3bvTg&_}xk$P=h?A%r- zT1~ls8*J7JWwwT*iSJJ>#5XA>TM{g&kN7`q)-8DYoJI`b=^=^!opx$ivh-C<)085G zB`W&&1?(P0|0-$5t)gB=|GpXLuJPi$7gF6`A?JI?$`INI(SA^?7j&RC)FjbQQVd*M ztFxagNJ1G)Y`{j-S94jW8xe3e z%XCu;4u+oRuuN|wav0!pEz>&zSnn;9I=^n37}JDV8nsNR95q^|&Tkz8%an#xF3U8+ zmVvHVri9~f4!vdSybk=AeEm$+qnKs-9I-)+)nb+j8+&4zo=EVU|WMQz}P|mZ|ewhrlwWA(hKAJ;IiO+?HvAc0iIs zEmiu8cm$?%&C<1a%1U#u<3Kz*P59u*l#vk#i)hdc4^HHi8DD$K^=1pse-Td=M&*SPg8hV<;0=@7FP;^AlhRz`ff>F+gamc_rRaiQK_!BtbvDsjOu zu*1g8W^{qblO@usoVaKulmiGdY)EI)3;L$mEh*nMn7kaO^kyP~0dAuDrwr!q1Yq4T zrIIXCn5Hr?uQB~7pG%3B-Bo`GYY*a2 z;kPc{Y1rg4sn@b4Ik!pOO8eY}BJ3PQ?CDP06Obv8b!QYe*5EI)nz-HIk1`95()dKh zq4Ne)HVv|FsDG=wQmHKEH;K3gONot{Eu|cA0?EvF@;L+;wv&g$umWn*ggv^R4&hA+eHDD%Y)m*ynuY!5tYmHI|5-LUn-YJ z-;3AxK`khxa(VQ?jp%-O?p&T7sBo0avlD+&E)V_6m&+6FvUlvTGgQV@|Fy!kt(EQ! z?zH`A8fg{PvrssL+@SXCl@Lb{W!O>v?5;gK2apt99f$)Cb#*y(wd?0B zy%{L!6~=4Bt%`Fd9jY{>NHkFLN#va<9$AZL5G6j5l>d~_pCDbM`(3#M=;*y>)M*Zc z5Ns4z;ZmXOp|OK-G}J0n*ZQI$6VHE2?Q8a2ua8DQRval;oQYC%tRK?6V0hUqHLh*+ zL#Wme&fQy0h|L-pX;p_%r0J2u{sPw7E^MU#;Dxce0}(5gVR#GA6@-SXPVHO28`eQb zM?n`M1}xz{sCRRA;jNfWR%!sl>rUTDy)^FhVbPN<_{-OtV(es}J3=g=sGPsDg8 z(?Sg)^;|S{V~+ov|AGp7!y$1y+pHJ|tG$(>|WEz>1L_ zDzS?4ljdFi^fXfACm4eW9>?dcKd?YKDZn!O!9RpU%4r|oF(#ALPCWzUS&}4!A?Ax@ zbdc$$gm9%K#C)2tily7l54lfY4HNPf=qbNluyCfhr|IbYSH*^=oopqCAhQ9yg^n(_ zz>1-x5-YBbuC&04p`#M3sE*FSaZ3(^V>~1L=qiIt606vValg+T zp>@;t>kIS`SzyJGXiHi>W`Px-Rvr9J{5Y(?u|SI@t)ol8dCaXlBh6ABripD&oXM5T z-v}0%+yvida^?Q}7DQw4Qd%eOVOYPkz>1MQYH7)TZ-Et`R?(bRA!Pnki8fyn&VpO` zYW`?Ce-^7Cq)B>{l#4&>IL5RF=bO&>&}N=`#eYcH*KmV2pm~J_`54N15`edujJ(3{{X=MIA&} zX1w3CKs}$E$Uo!#j*uNLm;TeDs|+qlthh7YY3uppScXJf(rU21+#~E)fV5)9Y=U{-+ky_DY@AxbsDjgP$=ctU<3dNd;&UpyXuBO%n=Q}d1G!zQdz+fLN z739ZC%J|0y9w=Cf-YsB^NRR*^#`E0+tBLoAyCmL{q^j!Hi#J$GaQK5-$6?B1s<2h; z=>fZsX=v!d#isR=B=aKauS#q^ih!xamJ)89nf0>7)&@VeiIQOGu)HlG$ps8tLZlG`k~}?#Aqn&Tp;EDzzGK(%{B?;iV9! zoZ$p>BEjFYc_XQ)j`#Z-fyiExYBj|<6^a-*LwbBkZP=-ZgWJm^<)ISA&eKDIpNi|Q zjF!@5Iw^u`#p!1K;fSQ{w$j=Y0kBbPu`$2a>aj&)SBt`pG}$;KFfddLA0 z)?4>5c%7lQ{n(9cdK)HvRBux$Z`9k)ZylE=r5ZodVCB-)l7ut48n|N=`7K)?Fk=dSfbn zEfL({n6c6H)$Tgxn^V4I(Lbf&H&XvNAaW=;WT;F#4F9qSc>5eM~i4SJKUg=@!z^D>1ssw zFGNOzxyHu)=2{Q15F@g99L4h~-{M$X$z^4*e?vXwfXHF6-QQt1vf1e{>7%-wN_nF$ zcYbTRSz0xDOM{e4ZU2eQjofN`RC_~_V$hE{!1~QkGUd3jM!Ae_T zp(>XAjqN(yZNV}suDa)}Sn{w~s@n@NL#x9*qHH>v?oq|LkDMpc;7d2XZxx2KWtuI=y{ol6ItmFvE zIn{DBU&s+1S)xzewHA$r|Jq#|DtY>61e`~nmdIUnwXN^FNTaVJQyPCy>+o;7)ZrXM z1}~0c#sS2yNUAs^8);^hZ2!U*gpzG!ZC7O5!q{IISZaO$S6`5{0IaGy>7fHr8BVF{ zBo^c6_+?z$$#D^INc&+p=cB?-tImL5RM?4raSA(8r5aSQAXmLdwRp~&sMMMR{?_k+ zsQZ+?AWxsE9zkCs++J4Yd28Yqi~gp!EX~s>?QsSwwITUubzs7&4~*7pt%-gJ_Grq5 z`FLR-(Ymn!r@Bv+Do)cc$g{Dqa;MXLW%%g!=&{@>HFSKVN472HInBtOgJQ*7tj|+x z=j{0ByxXU+*3MAff$~QKBjwQ+Zh$0mxvfgkJRKRiEHk_iE?H@KMx**0;$#SP(_piU zu6@m&1gg8+5d~w-ozW?hbbs(MRYi72`Gn^b@E8K{uwD`iAn*74DaNtW`t`k$mIYRf z0CI^HH|Y2#3#=G{v=XamN`$r^lse<+m)bVrZqrs;gT0LkpA-wN`$gWTp#RDG1tIt$bIOL0}nO0Y0j+_nj6f=X3># zL$0~(L6Vs+xB^Qyf5d`jd^U4k!NV37Ac)yjr;rPnDm&6~KQ#884X_;Cn|c;IadG@gj8*v+atZ(R>scI~xZ9jm)FQN* zD{{QgM5w{GKKPzD>5+t?7{h3E-f7z*h$EW659d_YYva+ol+drz2!)5WcwZCN-&zQ3 zlCciQGhy({i#EZ&}BSf>Yc|IkzzS>4?Zs}Rj10;Uj6Wod9k>lv(y zJ3dnj`3;$yx}hZ|F{u?sAhX5~GxLuZ?0$ujE!`!j%8WY)00Z^NVaF2WFIkuel~!{(^^XH0yvV|>FtQuF5lX8eKl%hnX1u~knDEiOxKz3mt7aCSz2UA% zT65>OLKQ~RrB<_DX>enslS{!>WUVmrS~hQD`4?vqi0lp%xO<}`?k1A{`-Hi&{`K?6};nYMd?e$!G>9JXmTQ4sRMStL# zseZsR<(_YCjSo8Y0z@1D-7LCT=L;2{1 z8h?jqZ1B6-X!t}pt;=fY9#L+=107{L2N&Vx1$fL%;!ER(zV@Y_Mbk+rD znc2eWX^dDBC*!tK6xnQbLr&-wy2k=wUO~@1ygd=S!Qw3$+^C68)hVNi?)=sXsn$tf z{7r@SvRT?Z#hY~vR;<28*WiSBlTF=aa{2Za%pt@=4oTaQL+-UZvI%LJn!T<}?abSiK6~^%)s2SQm;4(9Ct{zDC=T?s8m> zEeA=l4NeIgr^YG0CFM)G?Cd`39|uGp!*xA&Bb(t06FzFVQt56qT%F%K)-1xh)k+0r zYqjp}E{~PfO2wAQb<~emIfr3^%`JHU~cQRRBk@28L!zA1zXaZ9SZJhH2QLP zIjF4A7l=j%D};?*utMQ2ToQ_S0?)TozQi#hvC-bs)ISc0JXYvC*o|yfC`|aM6-uSM z(F&!#l}nF4&!(Y4k1ViIr8_p*jzv^jIxc7DOiMqRKrrfs2Sqw`E%yt0Xj*>kMzg+g zL|Bqvt>Yh!fH~{<(QTXsv^gl!XML2gl8N&Wa2}aBX1vtI8A+u)*f%kWzxsrL_%6@9dO|kL{Jutj-CZI; z4!iD|QO#)WuOMl&&|xL>m)e3*GLJ0pip*OW^6N0v!L*4SB*9w%R%JQ#;D@MerDN3cuEO~=mV<7=aSf)58>`yh$)+|@H9?k8 z=wT6#%ddn2#Tx3vBVz3%x%>u|sP8XeMfONtjs0{J%5h+A(1KrOV~r}#NC2^cmbzE2 zDKy4vd$F3wkW+;Kg~3{_0s)kuU1Q{9s3W}f{5&;A9*lp^J8+0=jQHB3DsDU-DJ`-fEa^UP@xBH&)Zk_E6cR}wt@{X~U@9bX`Ng2a|6gs@-ddoR!4hSEfupv*WW)=mOdlTc z8{KI1bhwSiTzEqhq_6V@iIdg+RRGT$sHY^0Jx;)4wJGMJnveq$cVZk;AVO~uXX4h`oVu2MyDyPvmWGis|C9l+IWB{%htyCSYXA_ zMu`6<1k*Yk?I*StV9cWnBuJ zqXkU_6z9`bH@=BF@3s6Pc|Of09C9h{EWjC3BDlA%#v8eW@;TuH?6V2iXqsR zwCcCOichO_VNLKe7g(T`Pg=X+XDnH8wMAd~EZ~Z(Yk^ho#dV(rR(x7@5ZCx&Vh1eH z$|H{9X*9$4^jtH2p9Rqvyp%qbTTI}S7FaQg30RIl{=xz)KCSGF34GN8zx>4n z1Tjq#oTOmq5IlE{QDNfW2oo%06dJMVh*6g%JMHQ zuwpm>i51sq{N4g9h9XF;q8!b&vb^+l|MN!P3jyb@tSnoyd9?-2_-y9-h7A^2F?@q1 zt2Oo&gH_+sjU@H_rva#LSx z(r8$s|8jJjgVMHox}``2nzVwVGcphY7BnA1=2}F zoa`Hr0^wY9i1(1#bYW@LlGA@?K{P(6xqjly7FaR-ge9$>w7`l_tEiutfgx^zR)~EC zx}^#KzgXaw-$MvO_SQozOAHP42@>}XbNuK1M}%ikw>7#wZ$USPSNJu6x9|$HH}OZj z46h)u;(CQ;7FaRlUt-l|udvbr=|k=nRshmnnLSu?dXojw_?+f?g>4pCF}#8$t#(^r z#ix~>SGd*!xBOl~5VE&k;fQ!-xNq;{E@1{f>^IB!M7pK9f}1Uf#c%~T0C)>maHj=U z3|Anr;<|$SEU;n-y~L`^uHZ8kNFQ=n@JSMzE=&+CIsG*YqVYM+bp`*&0xO0qu%y+G zEU@C!D(VVmQeKR1-sC;azWv4myZp{Tkg~VVU}-ER$frhvF?@lB}sL#^*HG z7dRGJF?@j~tr`|s@o5$H1=$vhZnVHHzb6oc?5!s_G9eSxH}-K~APSyB-l+eGTq^Q` z=sw|n7F1*Sgm(jY3!m^&3#=GEL1M-A34dmR6+`+ZR#Bgj>tw4ZEKttp5eVH}y9Zw; zndyQ=MX%%${heIm7Zr$jPHrQQoE`P9_-yH}t z_SPL7H&U)Rh8h4qRTA8S|CaxVbPK_4jd~|rP>WGjAkq?`ByBk;?pWRn#ooz{96{dt>m(|tw!+=SyT zP*0NP>|t~H=e@@O&LBHnE}d%8RR)(NR@{Nj*%nwaB-)Zz7h7P(r&YSJCL9rPr3G3n zY3;?pMzFx-8u&JoEB8$cqA_?WtrK@(^L7iY7y}ziOMZt1R(x974Q%eQz%Bp4Mi9~@ zy-CVtF|bK5+@`!+)CGnPme7VZq5ImP<`D}LG8FW)0N!Fy^Vb$wF#UI)nZ73D3TwBxwF4Eud9~8S#(MD}_ zbAtsH89KTjz{BWffYj=Bvw(mo8#By*6F8iYm|Gx1&;aUPoUma`O5|Dhlhi| zreg#;)})So=RPQ;TXHW22)o-wcgGBXv=c6lo2W*YL6diksTh9Sy=nSkhr#*gS0I?r zVTs-=@z>{(OZex3wtF=~SQIYeU0l_c2a&uN`$4B~gz9LEjy<5&`vqx!6Tn$@WBYnQ zx*uK(9jYEZrscHlQ0BN%7^;m=)EebxxmJaW_)dKUYrt2BoIjSBEh!y|>_M5#JlYV>U`HA`EN7<2|? z@E1sTwOJ}x%hl1surpL{fRy`E9TNOPxjIs>!O;iMd}g85aE1$e%gr%5LlcUzho1zk z2q{UR67jPmJC{@c&+alqozwOU01UJjMcfNTeIAL8rh^vDhwwxXx?K{byDot7W=#y| zY%$|t3jaz`eRa6;OaN@oG)w{!o(_A{CniORg0pX;QXVQd+Xubbb!VhGIRUX2XvFF9 zW~n~vK!x~u*p1K$Ze-7&0Eyo!^K_M0*fzK)!0WUWmd!R?hTX_!8^VN-+J;oR8*M}9x6bK;AZt3G_Q_m2zk$s* zX?*Q<{&b3%uhfQ=-fyW@hMhW$6AF9B%0puXPF18XhKm zRKrv0Zq)G3Z@oN|$ZCR@1~iur{{fpZxpjCo9b`}_;z@5bC%B|G>{JRvl~SWo9w`se zSie9Ir6_LJgAShQu1G7DeTpb&P+4ruud<@jv9{fqP=WbpDc|B~Tuf$o&2)pRutRxVxrCYv9*b#+_#AauRJJ*n=wwF*wVOBJWt zbkOy}485;V+_i1iFRp?k}e^7fr< z-jZL9dg4@jY4pUM-#V4;bToUB_JLgP`2;qTa=YhNQ1V>Ohc9uOW3}PJ;G~)ltEcKt zqg81())dBDaFjAEtK)=u%n6QC#zlBqWFKzTab-TdzFw-9DwB; zU+k{JC`bO6#0$gV92=*`k^f`LmpEQHHZ*^V`o{s0$B};HadG++;| zQYj2NuZNqp18>h-B)Kb31 z(dgKSqC!37fXHJMuETC*GYVnSM~y-%<&8$6^IKb#QOG6MnsBClHkT>b$!1+{Q?MFG z4M}Ez1_s0B#zdtw+0pZ7E0_D>?uxup>IaB!2BpTvsZ#34QohB}$e2<;NM-e}N}Wo1qf&Q%>-a#alY~@L)ihwaRP|mqLvpL?2HFiz7}ygP)K)nb9cuWy z=L4`Kp0fEi17Fh@X2mUyiofcvSS!W-IZ@G|xY#&ViaYy4^S3x!7gO9B0N6fSjvEm( z7rT*7al@pKDsC#}jf&g(ty6TxO%+*90MkI{QsbXYy&A8>ZVx~S$wCo|F^E{&?H3jx z;0+sK0^Vv)w3-D9cqukkr6@1280FdB6>+7+{UqWBCC0}5O01P>Bms<9rP!JBrCch! zo%+WCk;nJG5xbF1g~No8s&FdZjVj#vtrIq<%BrTdY0z?M?Q7T^$?bgK^^XH0kCNYy-N>folJLMR@TkMxRI5~u7N>fu6)LWUBunV;wtx_>>Ty!|Shw`7i^20T@#j0U{(TjzDhUTIb* z?W?&A{Y`An%!h6<+92rX>ux3wqAXch@`zeLddaX5qGcJ*;AeKuC zVCJo@zE)#Rvx!=Z#)aQ^SAUf4{~d9`VEeJL3$`DI!V%k#ceMT;^?LJ{IEFiB`en-OO3=mcujJx;ypPvjdzH3-(h zhEid$TrJfnBN6gz3gr=+POK?ZYazhph7Z-+jf84iX-*_`Ut?jjyK1Oh$ONgO!G&OB zeitH3DQW#oD)MwN7(HTI^h^2KPmmeBu3qkJL$OU>~pTgR;1tdN#4&wv^0zVt^ zj7baxmCMMT7nq{ufiwdeZPW<;Tz9oid5eE1LJMz^l$Vd~Mu$OyeYz!;wEm@iCYK;R z+buz|z@ieQ>Hoks)Bm=h6^wSl|GV3=3R7Z^vo{;!MeAj! z(Z(`Ocfj-B8MKELiW6-VSK(qwVZ1ing5_{im8Ml~?-Wy8Ukqk@?CPz}c1d)2Y_F*D zL48#H)_Z%mp|?f%eR_*`Q|xQSaCiC(ZH=X_h#@#|a8#-ZiO%RR`4e+2`N`hwEJ@6Miitd*q*ey2_$$v)}$CT$ZOLiUz zS4dbiq8SFPMfV$kl>1G%Buj6suHBdLd6L6T@y~g8t{bb3JL@39a35qI47Tc{wd%Sd zNElwXdB>*no$5O1GW;DB${O&q6xIz4Kyjhwz(C)`WdCq&2hbqi-1N zHI0|6&OoR+LB8BK0LmAE9=lJ&kC2*pbUtkO?MU^fgm#VKyW$iGbka!hXOV#4hf79+ ze+!>C5!~I0C#6>ZmuO5y(S98B=ffYk@C01O`eZ~SS zhUQAFqMEBE!y6ZMNN3}l`aiZnIiI!?4!Mf1|A1ts3tA}%+FPwW+Au!#l`pQJbAvbh zXM}==?rR2j(Zw*>FpSE(K%{v9-om#YZ-Esdo^XhII# zSr$m=(@}v$E+svK#HM@l$K~{;7DVH7np<3Z*a9m?acxVjs9Rvgr&YA%_e`3n>o!i_ z)9l*~7TD!4_AN-+Ti19%Gu@evEG z7!E>W#WfobTVTad1&LMEL3HKJAIg16$PSlF-?8W_gG&;t*rcSxna9sr zV8xJVOIrP33#|CG>M*E@pLsm<68@-_C9S=2xd5Q7Oi43)AUVzPE8{2 zG7vhq&ATwsn8MJ8jMm-IeXXw#r+n2y#y%0q*k`D(91!7POy(&i>AOGSC`~fwPXLzj z<4eNai6#T3GKct?6eiXAt<{^eOTNBsnF>2H5ciJh$4Hp|Yof|?P2eo9)%S$me?qx9 z6K+Ci3ia=Z=(})qmnKQy<4aP}eQ+TJv(Sk&vnM9kB9ews9qB_{G+MOd{vE!2&4N>I zAbz3k{?tF@W*E*xDp%X?a{x?jE_yQvk)(q#_Ll0^_Cfb6DCSC(+()n*OQIC=k2a$d za?sp*?$pI#v}Ndlpq4Hzz73kzr?XIhN;g5V!PliiI8q#rI@9L8Y-UM*tMe`1O(3%S zn-l#FPF2qnhfxLC8l|%MzT_t){Q7VDj|c@YY>ghd)oivo)=zd=Ig7;r*k@GCSzu#+ zXQ6Ex8ukW@DPOYapK=faG!$^~o=s)5Js_|0#?!!*I1G z{=hu^wCb0ktx?T$h3;!~9q6uPE6u%%=xP||VPk&H)i)DK@Dlb6nfvXx_vpDL%DT(SFrOGE75_;0DdbL z#)k7ep|Z9-g%YNB6D3U78!1#v5HyOlvLS)5ihBGsx3JA(s=efr4`2P5(TW62};Ya|2NeR-dBjI`K#1(P7z+S>DH|1}ER43E+Wiu?U(D2{{KW zuwq2eNUXR~7i|ly7?A`Lt7uG^Ht;tNDrw9^k7~mHVGER#mUF|y`Sc_l!Xa10@B<_> z2K&YrKF&BNCC*_Y6{{qg#ek_ehmJ-ehV<*<^Sz047)BAHIEN?k7yjwTIV@1d6}Ech zIP$%?|3W**Au}en7%oNtfxiO1&mQ1#!cHvYCQLu1=}uBTPt5p*EK;@NCuZ0kp4AlK z84aaOE;McIsR9^YRE|-Q|65vLf&7OgWZ`ET7P%>|LKc3EfFpv3&cztAP>PHdcA#Tb zdqFtWYfer;nT`>0zWJa-D^GQ&)PyQh;av1JSYnV$eN>0wdB1dQTHHZ4_d9)iSYvV% zORY$~0K@{?kXwjt9bu!=Q~j&i;|w&%Wbi*ncej6P`S~aSY+o;s8C&2k$8LmHq{vbx zhY*AbA6>ObrMq#}qVrpuyCTY(-Awyv%sFxPfqzGWY4Yw#epLD$wQ_)lAm_@#4{fnf zD+ju@)s@JCLTcqe_o7jYm$q`S2E^3MuyTOUiIs!h_={E!=vTg#gJ`dd?tUnS;vE$V z`&#PE1$32kCk%TyJp~K>OJB?rrdg>*3{{ zc=FL&bQF1#FtFS>3Ues3L{x7EEHe!zhL?9^i~GOm`X0Kz zm#*)l>mSke{dD~RTm_l$p}+6Nzwg7#QUG*|dq4byu08<&U9x1QI|puVb!Xu>X5;09 z_~eK1GM`%J;>~$@c@STmGj{ty_ruuy5xgvehFjbtu=z;5d=zdTbQi+UgYL)h`DyMV z`eQn_9*&n8^!H4--r_FC-(#0=1yA^KczUb*3AjQy(?jsz^=z2M^LiP%CH`V1Z?R2-t2E z(EWH>xgZduet1G{5JbuX;luvWKXGj8%`7*d=ui6=DYdTvz+^VOOS7!r440ZEDDMIX zUZj_KQP277F+vTgN8wTbt=PNU@iKTcTt@Li3RuXcHrglm%-(4@$%3~ zaQP@+{thos;^ikR;qn8#%rC-a30{7B3S54PmrwS=<+FG}cDPiV2<=XG`ohDO^gU$3 zTzUm25bs;D$G78U!x?Zn8!z`^>8uCwf>H7=1=r&|(#7d#%l#b5t@{_^zrTe4Kr_A| z{`;c%@5|!9$Hjk7i2uF{|AF59E&g|y`!)E_{W@H(hrQMvZTCs|32>gG|BCKLkZf;J zW3th7#s|ioMx!+9wC`$%mhhC^N5<=_frduAE`YImy;81TTWFT*5NKC8NmRO8i>t)` zp$`LfXB0M#2S%N$Q>Q4eKKau*sQ}enF}egJhUyNCHzCLmfc!KDErup4%I#jX z2rLlZ%!;$eskASe=FPy_NZXrn;npqN+R7h&7IeUa1JbJwJS0{81N0sD9{A6Kl-Jmj za!+{(s^?Y);7mHG05#ac{rQe${d|71el9;*-A4e1BI+-}oFo&~ai0d+-Rl0M5ae&u zA0hMB^5(#*L_vw~5uTrayMmd3W6vV);Q5>*~UuuE+L18n+GE>5XG5)mZu17&w zio;zv<(W3SM(d@CF_XPJZ+>2(IN}Xli{;IMn*L55l(>D1htbe*G<}<7)SFkT4vp2I zY21NLHEC!z1CBxf7sI!BrcHua2rmi{^9Y1ZD#;>vGf{txG>k;EN1Vxmb6bsOZG1bN zu6a$68RC;d@^^f(+aQ2)lBQGc1FK7Krn0dKXXo?^gTY(ey#0Y_{Ci$2+a z0dDf>;X^$m|B);FC7m?l-)~DfMf`hgK}3lkU8d3(qLLPzTow2vT$lw2g~e>!jvyBP zNX|4mNlC}FG^N1!K@=XrQO^!V$WRx`2*sEF-mW;C6f~D zvk8Q89^-CbXqc@;R(7==5Z&>3vmjOyDAuOAsw)5PUInnd1?B2cr8VqOK2+Ns!h3VZ zOZ$eMiRM`QqUql3q0$5nh}v7d8KafjVEdpqYpgsx>_F3O#T-;wm(20z8t)X!moMC`b{Ow97GvL&$cvVy?5yljsQ_7N9W zVW9^~ao}=IvowgSJYwTibpGa^$@Gyc%Hq?ohc>)cemW-IH`)?kN%vcAK}79{1^?s_ zhgOaFLmX@SWLkpybeFh^{pmE$dB~RRigO<)5JtPy)v;mv`pVKutu#DPYKm222SUCz zEqRhnS1IUfk>&5%%Rv~cq=e5uKzci9$C45Y$DgG8*zp|&qpI8!+*nuY+$&h@fBW7 z7!nH*O6)#D9WjcXwW&TfD3`K>cKQlx=}9v&>vwX6Evuv)x5D3STq`yk*NWvo8gEH% zDBJK#z$53tejS0ZFzYr%PFLitGGv9dDlY@yz=)K0=K zF?cFGOnLzJ;jm|Yk&edrx`U5uMia0to!v&2vY4EuGy8a*27Lpts}7R`*vKD0U_T%2jAAg!h8*(*du&0dnV1m9T%O=!!eZ*g_Bd&d zodMrYOLdQqLLURfa*jtHWm7n-!8)gt)cU;);#*?fjZ5ppd~`EP>u1=IW70a+axx#R z*}z1lRCOZO>__?K;@{ci$SM~d9a&P04#D3CY6%PX2Hj z6B*$w+m+j3904GB!@;bAab72bkti6k{>BD6i}@I5l#c~$$cJ1$7Akr{Zk#sZI48ew zyp~OntisXJn{*Hk>}*Uj&f=q+Q8G5LAsZyaV&J02K4RWw0iyS_;G-a7aH7^Ii--%k z_pgcLKDIb!m8HUpP~2@Ow<9<$G%hJETYjpw%{qawm;`ryT4XMRDi%-vh)sDZylQdt zJ#2bqW!@>|C6c&CV}wvcBhJRV`DkTS_&eE zf6XRMR#{kQmW9N=#RcMveB3e$#9y%?A98_ML`0L-OCoac>-=)?D>g;4%E2o-$bsxq zoc+Jxqm+^TKV?Hc`>5My~PM#O#;NY(*i_v5?}#B zL_@T^#gLhT5%~jtB#xg~1WCdeX$Jq{viC(xTHYdj6oU9gL=UF3h+AO8L${W|Q_Z^L zK-wq85#e(LX*VP+y$M5mu7Qiw@Rm@7(Ll2XOQ?gba%GtEWAKUF;Ys}V9rS4MSIb+7 z@ka0leW204z3naa!;7@L^F-x+%A_))rzZTE59`1cs5~bFqGrPZ0F>k+ z%YJ!_%Z(jQ_?d&=+<~Dn=q*HGwRg9@g)p_LRnJv-T7%x~+Mo-$5=gnR(nXLyGb?&I-~AQz-2Ef?rT5ys z7jA8J@58G%mwLFZzHI_}xd=99V0EJDKvD}7*+YW(cOWJl{&pXrS3gKsOp)K}eh7Xa zbRUE(q-=hK{(}HxEW#jrHMZAB_DbM6B8Ta0@bF#mOi79i3EysLyB>3XZ^O$F=KOBO z)ZVXPR__;1g3H(O@&cyuF2D@lpPd4iPhsBf^{<4>ZFuR&wAK4DV|62DtX>3ps!prr zO?O3J*2e%t5a`)r{;5bmW4r`mZ-||IV&Pb+G2mR+B6^E=5$}V9@Ik`&V2ju`_KY}@Mwf=F+O;;c9MBBt=Hnlh({xIdbe;1TQ&~|f&ZY4ritCey!JNyAk zp}z+dP<)^h-8y^#?Oqr{gj0+V;sfe17|PYhI<5jtrcqh= zeai61YA*DC%YB@TW>J`-Z@?ek{7Y)Xt%{?p77iY$CdJs*V^`B_Ldj-b8W=59GSy02 z5Nbs&P_4x4P%Bnr2wH{N^$xE$$WKa(EnFy6l%fHKH-DmD8?8|UGg@!otk0^H2AvAo zb8imlF$Baq``YeuKy&PBZ=p2Oc)Uvm^5~Zi?5dZl4RDDN2)(^t+b2x)vGBCFc%V{x z^W;ENK7b_&+~O*5~w-0WbK+fMu4;8j!ZG|L!*M%Tz zNV@9@tas#ga%cq&H|X4P=nMNnFDW}6OAr*`j!bbBneC%6i1ADb>;U2=1VU0Z_V&aq zcsn6+6JA^%dXbj7qhJ2cYw^R;nMz;BbN@Cg7_fiWd%wh9_~H z((gx9Kn)EnvjIo(`DHfbn^@h&ON-G%d=XN}*sF3+^6LmAd57a2ZAk0K!ML&nr2f(d zK;g<)fJD11Nvz>7R2!)~@E1zE6nXgT4E&3#GoTE^jWdHAD3);J?BE8<8r;|v+(7w( z8=LV)ARut#{NM%(0o-_fa07V^H!cZoASdC*_TUCG3U2Jg8-Y!R8g_P|@Fw~d!3CSa literal 314930 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 diff --git a/docs/build/doctrees/index.doctree b/docs/build/doctrees/index.doctree index fa3c34f9064b680bb59cf0bd9d2a7c5d88144223..82d444894bdbeed3323e080ddd1294d234dcaffc 100644 GIT binary patch delta 95 zcmaEvvonXKfpx0hMizTU1ylWu{M=Oi#GKTM%woNy(xUYIJpJVSlvI6}AV>Gqyv>!2 xMeK}HlizSia#>BuV2_=WA=U%t@>)%4ozgg^c8W%ZbVk_Zi|j_58#!O-002VvA`t)p delta 162 zcmdm)^D>8}fpu!!MizTUNpJm({M=Oi#GKTM%woNy(xUYIJpJVSl+?-B1?4uUFcz|> zsE1?}rKT!m<`tBd6e}bar79Gq7M5lfrKTvP z^?IBl4O$q{E^fn(78V7w5#6;YtEJ1y2tuZy?nspHtGB$r^Z%WD&gDJN>XXHCEgP_u zXIclapDt*qFKa*1*3qBhqZn3kY4s5;jIe`dw!6&iu`0}=XbqnTBzF9#h7Uy_Z^NL= z!L!}NVVWLc8~-$0G4q~tYZ@?Vt3KX{h&f~S@usoPwe-F6U!p|#jM=MtC;IsZy3vd$ zUbZvl+>1&cdD$DpbvIAv5AzLgnTq=^#?$$hYL9a>oj=P@zW=KE`4&Dut@6tqD(E`M z+3O0u-i079F5hK%-`}t9|T9EN62-6_2x!h6|D5a0~<`5I_fCpYmA(cOB z?F$of$!|hFmpet#xa~P^ich<7v4*yA{C!s`7^9_Q2#R$_u~m05Az5c5Zrdho9>-<6 z*3Jy^Es5niI~9`7*5Q^j(Irw-$gTGPB*9KG&+)Jx6Yf}H4oCJ;aT=R)akht5TfZ_Z z>r#@Y+}rtvq)MYfwq4{HYtuahBn1(1&tR1K||6Ri;33L u%rG$S1YMZ$pV)v`%S49Fs=onGMMV?< delta 1705 zcmb7^&ubGw6vy*sH*J!3O;SiPF=`b20g)<2>_yR_XiGpe?xl(ZD{c#)GAoic$>Ni=`2*6aRqB>s;pYd7t^dH^a;{-0$-q zJ?|`q%lZ|70qWlhh7&i;#a1Oy7sQtx&L|K3>Byd{SN9L<$(zx^WJO`rYkqx#PI-H6z04ccgZ=KxzRk}qo z4Ud8dkV){Ncu)j$FPppop_ZQzdox0Av7s`A=$9o!q^DsbfUFX-suHBz1HM7xw2 zlUPRdn&h(%dF5?1ha4Bc HENlM&zG;zE diff --git a/docs/build/html/.buildinfo b/docs/build/html/.buildinfo index c4c6ce8..416abb4 100644 --- a/docs/build/html/.buildinfo +++ b/docs/build/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 711d18d864881307490d74b453c647f0 +config: 6b4bd43eeea172ae57af29bf18236b28 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/build/html/_sources/index.rst.txt b/docs/build/html/_sources/index.rst.txt index 0b3a69b..c56e8fe 100644 --- a/docs/build/html/_sources/index.rst.txt +++ b/docs/build/html/_sources/index.rst.txt @@ -4,7 +4,7 @@ Decsion Region Analysis for Generalizability (DRAGen) is a tool to analyze the d Getting Started =============== -Three inputs are required for this RST: +Three inputs are required: 1. **Trained Model:** the model must be saved in onnx format. Information on how to convert your model to onnx format can be found at the `onnx GitHub page`_. 2. **Images:** Images can be saved in any format supported by PIL. diff --git a/docs/build/html/index.html b/docs/build/html/index.html index f3914c1..d971699 100644 --- a/docs/build/html/index.html +++ b/docs/build/html/index.html @@ -37,7 +37,7 @@

DRAGen

Getting Started

-

Three inputs are required for this RST:

+

Three inputs are required:

  1. Trained Model: the model must be saved in onnx format. Information on how to convert your model to onnx format can be found at the onnx GitHub page.

  2. Images: Images can be saved in any format supported by PIL.

  3. diff --git a/docs/build/html/objects.inv b/docs/build/html/objects.inv index 275dbf8c8d7ccc5a0d6aa678d28b4755d2b67096..50406dc5d6ada102bc99fe5290689a8c5be522f8 100644 GIT binary patch delta 615 zcmV-t0+{`Y2I&QmjepB-;xH73_dJEA&Nk7^mK_4Asx*~Q+r4FCH(2r|vYl#p`#HW5 z2MB@E4RY-N|9m+<$5_+U#I&|v_tt5-CB`y?y2G* H(Rg~7d(;^`Nv78c}L>w%)-XS&Z1w+4)7X?S`sjeC8mN{Gu1kz`(8vg;)AAea_A)U|;Q`T_nyA5j} zBYpC=H|%==OTO6S8?taJ9Wg}C+9`1abJg|krWg$JT4+KsanA5v@7Ns^MJJb}WwWMn zOmNE-GsLUw)MU3Ud!BP_+JOK1rITf(MfO8Ray6(eDCo)(cqUi zP`Z6k9oY#sy@w>N{Iy8Zmos<5I%XtSTIlvL$tyJoZMj!Uwlw}xM#`5BY(2eT=-Bde5gpe%hYVBqM<4bo6LHPtRepv{{h-gx^0Hul^X)7s(;2J84r@a-YWr2qu`%cm@{30+`0^Jj76#5db zM>?>k)AV})gTLtW2QmVk}FIqY^`Sd&e8`q3Qi_)LuXCn zoKy{!RAZ+u+Vh;#;Ne2J0f*qU9C9HbGY!?{XLx=KkNG|MIciyf)-p>)Rr%mh zdsO7XExuUIpnsSu!c0i8JNuK7zQ*+Tx8+zqQwmqX9$LAobLIB^V9&tercIJv??c9t z6;U-RvR;TbNz8vm^I_(A50|qoGnSUho-a52bY`ZC4# zeGT*=f!IoW`ErALc@k{mAR38=`zj}Gae1^CTo=n^10Jj`C6nH$E0XRd`aZ&Qq`@z% zp>?~YTD<3U`V3K8xUz`S*9&*VTB-$;imRrd + + + + + + Index — RST: Decision Region Analysis documentation + + + + + + + + + + + + + + + + +
    +
    +
    + + + + +
    +
    + +
    +
    + + + + + + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..d971699 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,159 @@ + + + + + + + + + DRAGen — RST: Decision Region Analysis documentation + + + + + + + + + + + + + + + + + +
    +
    +
    + + +
    + +
    +

    DRAGen

    +

    Decsion Region Analysis for Generalizability (DRAGen) is a tool to analyze the decision space of an image classification model to increase understanding of the model’s generalizability. A model’s decision space maps a change in the input to a change in the model’s output. DRAGen utilizes triplets of image samples to generate vicinal distributions of virtual images, created by linearly interpolating between the triplet images. These virtual images increase the density of available samples in the decision space, which allows for characterization of the decision space beyond the original finite data set. This insight into the decision space composition indicates how the model is likely to behave on data distributions upon which the model cannot generalize well.

    +
    +
    +

    Getting Started

    +

    Three inputs are required:

    +
      +
    1. Trained Model: the model must be saved in onnx format. Information on how to convert your model to onnx format can be found at the onnx GitHub page.

    2. +
    3. Images: Images can be saved in any format supported by PIL.

    4. +
    5. Input csv: A csv file which can be used to map the image paths to subgroup attributes.

    6. +
    +

    Examples implementation can be found in the examples folder.

    +

    Scripts to generate and analyze decision regions can be found in the test folder, +all arguments used to run these scripts are located in src/args.py. +Example inputs are included in the examples folder.

    +
    +
    +

    Terminology

    +
      +
    • class: An attribute by which the model classifies images. Only binary classification models are currently supported.

    • +
    • subgroup attribute: An attribute by which the model does not classify images, but can be used to group samples into subgroups.

    • +
    • decision region: A portion of the decision space. The decision regions generated in this RST are the regions of the decision space near to a ‘triplet’ of sample images.

    • +
    • virtual image: An image that was created by modifying existing image(s), rather than obtained through a typical image acquisition method.

    • +
    • vicinal distribution: The collection of virtual images created by linearly interpolating between a ‘triplet’ of three images.

    • +
    +
    +
    +

    Contents

    + +
    +
    +

    Indices and tables

    + +
    + + +
    + +
    +
    + +
    +
    + + + + + + + \ No newline at end of file diff --git a/docs/info.html b/docs/info.html new file mode 100644 index 0000000..3319b4a --- /dev/null +++ b/docs/info.html @@ -0,0 +1,114 @@ + + + + + + + + + DRAGen — RST: Decision Region Analysis documentation + + + + + + + + + + + + + + + + +
    +
    +
    + + +
    + +
    +

    DRAGen

    +

    Decsion Region Analysis for Generalizability (DRAGen) is a tool to analyze the decision space of an image classification model to increase understanding of the model’s generalizability. A model’s decision space maps a change in the input to a change in the model’s output. DRAGen utilizes triplets of image samples to generate vicinal distributions of virtual images, created by linearly interpolating between the triplet images. These virtual images increase the density of available samples in the decision space, which allows for characterization of the decision space beyond the original finite data set. This insight into the decision space composition indicates how the model is likely to behave on data distributions upon which the model cannot generalize well.

    +
    +
    +

    Getting Started

    +

    Three inputs are required for this RST:

    +
      +
    1. Trained Model: the model must be saved in onnx format. Information on how to convert your model to onnx format can be found at the onnx GitHub page.

    2. +
    3. Images: Images can be saved in any format supported by PIL.

    4. +
    5. Input csv: A csv file which can be used to map the image paths to subgroup attributes.

    6. +
    +

    Examples implementation can be found in the examples folder.

    +

    Scripts to generate and analyze decision regions can be found in the test folder, +all arguments used to run these scripts are located in src/args.py. +Example inputs are included in the examples folder.

    +
    +
    +

    Terminology

    +
      +
    • class: An attribute by which the model classifies images. Only binary classification models are currently supported.

    • +
    • subgroup attribute: An attribute by which the model does not classify images, but can be used to group samples into subgroups.

    • +
    • decision region: A portion of the decision space. The decision regions generated in this RST are the regions of the decision space near to a ‘triplet’ of sample images.

    • +
    • virtual image: An image that was created by modifying existing image(s), rather than obtained through a typical image acquisition method.

    • +
    • vicinal distribution: The collection of virtual images created by linearly interpolating between a ‘triplet’ of three images.

    • +
    +
    + + +
    + +
    +
    + +
    +
    + + + + + + + \ No newline at end of file diff --git a/docs/objects.inv b/docs/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..50406dc5d6ada102bc99fe5290689a8c5be522f8 GIT binary patch literal 745 zcmVNERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGkXQ&c)2 zL}g=Xb7^mGAW~&#X>V>IL2hAed2?xV3L_v^WpZ8b#rNMXCQiPX<{x4c-pO&%WmQ@6o&Ua zg{96m(ae?|0;#Grl~CKgWnwp2@+GpJYIyrOz7Yoqfzl0f?En9KIX=f&)6~SYwqEzv zX}KlFGK0FqvJu2u^aq8(y_4eU7pfK(8Fi}JtEVtevHSGmMzmXRON9?Jt z8S9ogTnq%#XRjLn0n;B@S0SCy4pY`}>$?qWA0vJ8wm0m10875u;~TPYDjhLI&e|z) z1asB(?xq+F@>*y@F>%iDUGLZ(6GbPNq-C?FaZGT_6f?xD>(peoEqk7GY}|bjuE61N zIsvhe5Sf;l`Xhe7#?LZOevVpW&^qpz#5f!db9+tioZ^c&94-^4+{OU=y+3%;mzW`c zJ3-VVSK%T!K(W7d{69>NpI3sS@)cL@9{g);FmX0x_wX`*$Fniha|21wMf#JGk3x|W+Ycy z==LzlD>WZOdjaL-LVUVIl?P$~USl1Kkt#-KY}*6WLN#_PW~}k7pR=vVGdt>P<*&&$;BgoveI1mP%M0? bNV?0^Y^b84Dh!* + + + + + + Python Module Index — RST: Decision Region Analysis documentation + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + +
    + + +

    Python Module Index

    + +
    + s +
    + + + + + + + + + + + + + + + + + + + +
     
    + s
    + src +
        + src.composition_analysis +
        + src.data_input +
        + src.decision_region_generation.generate +
        + src.decision_region_generation.vicinal_distribution +
    + + +
    + +
    +
    + +
    +
    + + + + + + + \ No newline at end of file diff --git a/docs/search.html b/docs/search.html new file mode 100644 index 0000000..bbba911 --- /dev/null +++ b/docs/search.html @@ -0,0 +1,101 @@ + + + + + + + + Search — RST: Decision Region Analysis documentation + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + +
    + +

    Search

    + + + + +

    + Searching for multiple words only shows matches that contain + all words. +

    + + +
    + + + +
    + + + +
    + +
    + + +
    + +
    +
    + +
    +
    + + + + + + + \ No newline at end of file diff --git a/docs/searchindex.js b/docs/searchindex.js new file mode 100644 index 0000000..31f3d15 --- /dev/null +++ b/docs/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"docnames": ["args", "index", "src"], "filenames": ["args.rst", "index.rst", "src.rst"], "titles": ["Command Line Arguments", "DRAGen", "Data Input"], "terms": {"us": [0, 1, 2], "file": [0, 1, 2], "test": [0, 1], "directori": 0, "class": [0, 1, 2], "src": [0, 1, 2], "arg": [0, 1], "custompars": [0, 1], "mode": [0, 2], "str": [0, 2], "A": [0, 1], "custom": 0, "parser": 0, "paramet": [0, 2], "The": [0, 1, 2], "purpos": 0, "which": [0, 1, 2], "i": [0, 1, 2], "being": [0, 2], "creat": [0, 1, 2], "must": [0, 1, 2], "analyz": [0, 1], "complet": 0, "rais": [0, 2], "valueerror": 0, "valu": [0, 2], "dure": [0, 2], "both": 0, "decis": [0, 1], "region": [0, 1], "save_loc": [0, 2], "save": [0, 1, 2], "loc": 0, "program": 0, "output": [0, 1, 2], "save_nam": 0, "name": [0, 2], "hdf5": [0, 2], "model": [0, 1, 2], "format": [0, 1, 2], "categori": 0, "option1": 0, "option2": 0, "ex": [0, 2], "covid_posit": 0, "ye": 0, "No": 0, "class_ord": 0, "design": 0, "correspond": 0, "0": [0, 2], "1": [0, 2], "default": 0, "subgroup_attribut": [0, 2], "attribut": [0, 1, 2], "addit": 0, "group": [0, 1, 2], "sampl": [0, 1, 2], "overwrit": [0, 2], "pass": [0, 2], "previous": 0, "mai": 0, "overwritten": 0, "exclusivli": 0, "model_fil": 0, "path": [0, 1, 2], "onnx": [0, 1, 2], "data_csv": 0, "data": [0, 1], "csv": [0, 1, 2], "pair": 0, "id": [0, 2], "imag": [0, 1, 2], "inform": [0, 1, 2], "batch_siz": [0, 2], "batch": [0, 2], "size": [0, 2], "loader": 0, "shape": [0, 2], "vicin": [0, 1, 2], "distribut": [0, 1, 2], "triangl": 0, "step": [0, 2], "number": [0, 2], "take": [0, 2], "between": [0, 1, 2], "triplet": [0, 1, 2], "when": 0, "With": 0, "rectangl": [0, 2], "approxim": 0, "2": [0, 2], "virtual": [0, 1, 2], "n_triplet": 0, "n": 0, "each": [0, 2], "img_rel_path": 0, "img": 0, "rel": [0, 2], "common": 0, "includ": [0, 1, 2], "ar": [0, 1], "random_se": [0, 2], "random": 0, "seed": 0, "random_st": 0, "state": 0, "select": 0, "plot": [0, 2], "out_funct": 0, "function": [0, 2], "appli": [0, 2], "score": [0, 2], "see": 0, "util": [0, 1], "py": [0, 1], "option": [0, 2], "aggreg": [0, 2], "agg": 0, "how": [0, 1, 2], "composit": [0, 1], "all": [0, 1, 2], "note": [0, 2], "alwai": 0, "calcul": [0, 2], "befor": [0, 2], "ensur": 0, "ha": 0, "same": [0, 2], "impact": 0, "compositiond": 0, "despit": 0, "slight": 0, "variat": 0, "threshold": [0, 2], "treshold": 0, "doe": [0, 1, 2], "affect": [0, 2], "plot_onli": 0, "onli": [0, 1, 2], "type": [0, 2], "perform": [0, 2], "show": [0, 2], "displai": [0, 2], "hide": 0, "percent": [0, 2], "text": 0, "errorbar": [0, 2], "save_dpi": [0, 2], "dpi": [0, 2], "summari": [0, 2], "plot_output_format": 0, "should": [0, 2], "plot_palett": 0, "palett": [0, 2], "color": [0, 2], "can": [0, 1], "either": 0, "matplotlib": [0, 2], "colorpalett": 0, "plot_threshold": 0, "ouput": [0, 2], "none": [0, 2], "n_per_group": [0, 2], "per": [0, 2], "decsion": 1, "analysi": 1, "generaliz": 1, "tool": 1, "space": 1, "an": [1, 2], "classif": [1, 2], "increas": 1, "understand": 1, "": [1, 2], "map": 1, "chang": 1, "input": 1, "gener": 1, "linearli": 1, "interpol": 1, "These": 1, "densiti": 1, "avail": [1, 2], "allow": 1, "character": 1, "beyond": [1, 2], "origin": [1, 2], "finit": 1, "set": [1, 2], "thi": 1, "insight": 1, "like": 1, "behav": 1, "upon": 1, "cannot": 1, "well": 1, "three": [1, 2], "requir": 1, "rst": 1, "train": 1, "convert": [1, 2], "your": 1, "found": 1, "github": 1, "page": 1, "ani": 1, "support": [1, 2], "pil": [1, 2], "subgroup": [1, 2], "exampl": 1, "implement": 1, "folder": [1, 2], "script": 1, "argument": [1, 2], "run": 1, "locat": [1, 2], "classifi": 1, "binari": [1, 2], "current": 1, "portion": 1, "wa": 1, "modifi": 1, "exist": [1, 2], "rather": 1, "than": 1, "obtain": 1, "through": 1, "typic": 1, "acquisit": 1, "method": [1, 2], "collect": 1, "load_attribut": [1, 2], "load_imag": [1, 2], "tripletmanag": [1, 2], "generate_decision_region": [1, 2], "get_plan": [1, 2], "plane_dataload": [1, 2], "plane_dataset": [1, 2], "get_composit": [1, 2], "plot_decision_region": [1, 2], "plot_figur": [1, 2], "save_composit": [1, 2], "set_param": [1, 2], "command": 1, "line": 1, "index": [1, 2], "modul": 1, "search": 1, "indic": [], "data_input": 2, "csv_file": 2, "subgroup_inform": 2, "dict": 2, "image_path_column": 2, "id_column": 2, "missing_inform": 2, "info_format": 2, "categor": 2, "rel_path": 2, "n_process": 2, "int": 2, "datafram": 2, "load": 2, "filepath": 2, "patient": 2, "from": 2, "provid": 2, "sex": 2, "male": 2, "femal": 2, "column": 2, "list": 2, "uniqu": 2, "identifi": 2, "handl": 2, "miss": 2, "except": 2, "remov": 2, "return": 2, "declar": 2, "process": 2, "while": 2, "check": 2, "core": 2, "panda": 2, "image_path": 2, "rgb": 2, "scale": 2, "tupl": 2, "arrai": 2, "specifi": 2, "resiz": 2, "numpi": 2, "decision_region_gener": 2, "triplet_manag": 2, "input_csv": 2, "triplets_per_group": 2, "image_rel_path": 2, "sample_id_column": 2, "mix_subgroup": 2, "fals": 2, "mix_class": 2, "wrapper": 2, "potenti": 2, "organ": 2, "task": 2, "f": 2, "m": 2, "bool": 2, "If": 2, "true": 2, "separ": 2, "__getitem__": 2, "kei": 2, "input_csv_path": 2, "onnx_model_path": 2, "output_path": 2, "manager_kwarg": 2, "vicinal_kwarg": 2, "evalu": 2, "plane_load": 2, "keyword": 2, "vicinal_distribut": 2, "img1": 2, "img2": 2, "img3": 2, "plane": 2, "basi": 2, "vec": 2, "span": 2, "3": 2, "b_orthog": 2, "orthogon": 2, "vector": 2, "b": 2, "second": 2, "vecotr": 2, "made": 2, "coord": 2, "coordin": 2, "img0": 2, "dataset": 2, "output_dtyp": 2, "channel_idx": 2, "output_channel_idx": 2, "dataload": 2, "alongsid": 2, "plane_data": 2, "dimens": 2, "channel": 2, "desir": 2, "5": 2, "expand": 2, "construct": 2, "float": 2, "far": 2, "work": 2, "composition_analysi": 2, "output_funct": 2, "get": 2, "set2": 2, "todo": 2, "legend": 2, "figur": 2, "pyplot": 2, "df": 2, "show_perc": 2, "800": 2, "output_format": 2, "svg": 2, "contain": 2, "infrom": 2, "descript": 2, "result": 2, "match": 2, "exact": 2, "error": 2, "bar": 2, "whether": 2, "part": 2, "convent": 2, "style": 2}, "objects": {"src.args": [[0, 0, 1, "", "CustomParser"]], "src": [[2, 1, 0, "-", "composition_analysis"], [2, 1, 0, "-", "data_input"]], "src.composition_analysis": [[2, 2, 1, "", "get_compositions"], [2, 2, 1, "", "plot_decision_regions"], [2, 2, 1, "", "plot_figures"], [2, 2, 1, "", "save_compositions"], [2, 2, 1, "", "set_params"]], "src.data_input": [[2, 2, 1, "", "load_attributes"], [2, 2, 1, "", "load_image"]], "src.decision_region_generation": [[2, 1, 0, "-", "generate"], [2, 1, 0, "-", "vicinal_distribution"]], "src.decision_region_generation.generate": [[2, 2, 1, "", "generate_decision_regions"]], "src.decision_region_generation.triplet_manager": [[2, 0, 1, "", "TripletManager"]], "src.decision_region_generation.triplet_manager.TripletManager": [[2, 3, 1, "", "__getitem__"]], "src.decision_region_generation.vicinal_distribution": [[2, 2, 1, "", "get_plane"], [2, 0, 1, "", "plane_dataloader"], [2, 0, 1, "", "plane_dataset"]]}, "objtypes": {"0": "py:class", "1": "py:module", "2": "py:function", "3": "py:method"}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "module", "Python module"], "2": ["py", "function", "Python function"], "3": ["py", "method", "Python method"]}, "titleterms": {"command": 0, "line": 0, "argument": 0, "gener": [0, 2], "analysi": [0, 2], "dragen": 1, "get": 1, "start": 1, "terminologi": 1, "content": 1, "indic": 1, "tabl": 1, "data": 2, "input": 2, "decis": 2, "region": 2, "composit": 2}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 57}, "alltitles": {"Command Line Arguments": [[0, "command-line-arguments"]], "General Arguments": [[0, "general-arguments"]], "Generation Arguments": [[0, "generation-arguments"]], "Analysis Arguments": [[0, "analysis-arguments"]], "DRAGen": [[1, "dragen"]], "Getting Started": [[1, "getting-started"]], "Terminology": [[1, "terminology"]], "Contents": [[1, "contents"]], "Indices and tables": [[1, "indices-and-tables"]], "Data Input": [[2, "module-src.data_input"]], "Decision Region Generation": [[2, "decision-region-generation"]], "Composition Analysis": [[2, "module-src.composition_analysis"]]}, "indexentries": {"customparser (class in src.args)": [[0, "src.args.CustomParser"]], "tripletmanager (class in src.decision_region_generation.triplet_manager)": [[2, "src.decision_region_generation.triplet_manager.TripletManager"]], "__getitem__() (src.decision_region_generation.triplet_manager.tripletmanager method)": [[2, "src.decision_region_generation.triplet_manager.TripletManager.__getitem__"]], "generate_decision_regions() (in module src.decision_region_generation.generate)": [[2, "src.decision_region_generation.generate.generate_decision_regions"]], "get_compositions() (in module src.composition_analysis)": [[2, "src.composition_analysis.get_compositions"]], "get_plane() (in module src.decision_region_generation.vicinal_distribution)": [[2, "src.decision_region_generation.vicinal_distribution.get_plane"]], "load_attributes() (in module src.data_input)": [[2, "src.data_input.load_attributes"]], "load_image() (in module src.data_input)": [[2, "src.data_input.load_image"]], "module": [[2, "module-src.composition_analysis"], [2, "module-src.data_input"], [2, "module-src.decision_region_generation.generate"], [2, "module-src.decision_region_generation.vicinal_distribution"]], "plane_dataloader (class in src.decision_region_generation.vicinal_distribution)": [[2, "src.decision_region_generation.vicinal_distribution.plane_dataloader"]], "plane_dataset (class in src.decision_region_generation.vicinal_distribution)": [[2, "src.decision_region_generation.vicinal_distribution.plane_dataset"]], "plot_decision_regions() (in module src.composition_analysis)": [[2, "src.composition_analysis.plot_decision_regions"]], "plot_figures() (in module src.composition_analysis)": [[2, "src.composition_analysis.plot_figures"]], "save_compositions() (in module src.composition_analysis)": [[2, "src.composition_analysis.save_compositions"]], "set_params() (in module src.composition_analysis)": [[2, "src.composition_analysis.set_params"]], "src.composition_analysis": [[2, "module-src.composition_analysis"]], "src.data_input": [[2, "module-src.data_input"]], "src.decision_region_generation.generate": [[2, "module-src.decision_region_generation.generate"]], "src.decision_region_generation.vicinal_distribution": [[2, "module-src.decision_region_generation.vicinal_distribution"]]}}) \ No newline at end of file diff --git a/docs/src.html b/docs/src.html new file mode 100644 index 0000000..537937a --- /dev/null +++ b/docs/src.html @@ -0,0 +1,379 @@ + + + + + + + + + Data Input — RST: Decision Region Analysis documentation + + + + + + + + + + + + + + + + + + +
    +
    +
    + + +
    + +
    +

    Data Input

    +
    +
    +src.data_input.load_attributes(csv_file: str, subgroup_information: dict, image_path_column: str = 'Path', id_column: str | None = None, missing_information: str = 'raise', info_format: str = 'categorical', rel_path: str | None = None, n_processes: int | None = None) DataFrame
    +

    Loads image filepaths and patient attributes from provided csv file.

    +
    +
    Parameters:
    +
      +
    • csv_file (str) – Filepath to summary csv.

    • +
    • subgroup_information (dict) – Subgroup attributes in the format Group:[subgroups] (ex. {"Sex":["Male","Female"]}).

    • +
    • image_path_column (str) – Name of column in csv_file listing image filepaths.

    • +
    • id_column (str | None) – Name of columns in csv_file listing unique patient/sample identifiers.

    • +
    • missing_information (str) – How to handle samples missing information; ‘raise’: raise an exception, ‘remove’: remove samples missing information

    • +
    • info_format (str) – Format to return patient information in.

    • +
    • rel_path (str | None) – Declare a relative path for image file paths.

    • +
    • n_processes (int | None) – Number of processes to use while checking image file paths, if None, uses number of available cores.

    • +
    +
    +
    Returns:
    +

    DataFrame of ids, image filepaths, and subgroup information.

    +
    +
    Return type:
    +

    pandas.DataFrame

    +
    +
    +
    + +
    +
    +src.data_input.load_image(image_path, mode: str = 'RGB', scale: int | tuple[int] | None = None) array
    +

    Loads image in the specified image mode.

    +
    +
    Parameters:
    +
      +
    • image_path – File path to image.

    • +
    • mode (str) – PIL.Image mode to use.

    • +
    • scale (int | tuple[int] | None) – Scale to resize image; if int, will resize to (scale, scale); if None, will not resize.

    • +
    +
    +
    Returns:
    +

    Image array.

    +
    +
    Return type:
    +

    numpy.array

    +
    +
    Raises:
    +

    Exception – The provided scale is not in a supported format

    +
    +
    +
    + +
    +
    +

    Decision Region Generation

    +
    +
    +class src.decision_region_generation.triplet_manager.TripletManager(input_csv, classes, triplets_per_group, subgroup_attributes=None, image_rel_path=None, sample_id_column=None, mix_subgroups=False, mix_classes=False, random_seed=None)
    +

    Wrapper class to generate vicinal distributions for specified groups.

    +
    +
    Parameters:
    +
      +
    • input_csv (str) – File path for input csv; passed to data_input.load_attributes.

    • +
    • classes (dict) – All potential output classes, organized by task.

    • +
    • triplets_per_group (int) – Number of triplets to generate for each group of samples.

    • +
    • subgroup_attributes (dict, optional) – All subgroup attributes options, organized by attribute; ex. {‘Sex’:[‘F’,’M’]}

    • +
    • image_rel_path (str, optional) – Relative image path; passed to data_input.load_attributes.

    • +
    • sample_id_column (str, optional) – ID column for input csv; passed to data_input.load_attributes.

    • +
    • mix_subgroups (bool, optional) – If true, will not separate groups by subgroup attributes.

    • +
    • mix_classes (bool, optional) – If true, will not separate groups by class.

    • +
    +
    +
    +
    +
    +__getitem__(key)
    +
    +
    Returns:
    +

    The triplet, group, key and images for the specified triplet

    +
    +
    Return type:
    +

    dict

    +
    +
    +
    + +
    + +
    +
    +src.decision_region_generation.generate.generate_decision_regions(input_csv_path: str, onnx_model_path: str, output_path: str, batch_size: int, manager_kwargs={}, vicinal_kwargs={}, overwrite=True)
    +

    Generates, evaluates and saves decision regions.

    +
    +
    Parameters:
    +
      +
    • input_csv_path (str) – File path to the input csv; passed to load_attributes.

    • +
    • onnx_model_path (str) – Onnx model file path.

    • +
    • output_path (str) – Name and path for output file.

    • +
    • batch_size (int) – Batch size for plane_loader.

    • +
    • manager_kwargs (dict) – Keyword arguments to be passed to TripletManager.

    • +
    • vicinal_kwargs (dict) – Keyword arguments to be passed to plane_dataset.

    • +
    • overwrite (bool) – If True, will overwrite existing file at output_path.

    • +
    +
    +
    +
    + +
    +
    +src.decision_region_generation.vicinal_distribution.get_plane(img1, img2, img3)
    +

    Calculate the plane (basis vecs) spanned by 3 images

    +
    +
    Parameters:
    +
      +
    • img1 (numpy.array) – Three numpy arrays of images; must all be the same size.

    • +
    • img2 (numpy.array) – Three numpy arrays of images; must all be the same size.

    • +
    • img3 (numpy.array) – Three numpy arrays of images; must all be the same size.

    • +
    +
    +
    Returns:
    +

      +
    • a, b_orthog (numpy.array) – 2 orthogonal basis vectors for the plane spanned by the input images

    • +
    • b (numpy.array) – The second basis vecotr, before being made orthogonal

    • +
    • coords (list) – Coordinates of img0, img1, and img2

    • +
    +

    +
    +
    +
    + +
    +
    +class src.decision_region_generation.vicinal_distribution.plane_dataloader(dataset: plane_dataset, batch_size: int, output_dtype=None, channel_idx=-1, output_channel_idx=0)
    +

    Dataloader to be used alongside the plane_dataset class.

    +
    +
    Parameters:
    +
      +
    • dataset (plane_dataset) – Plane_data to be loaded.

    • +
    • batch_size (int) – Number of images to include in each batch.

    • +
    • output_dtype (optional) – Data type of returned arrays.

    • +
    • channel_idx (int, optional) – Dimension index of the images’ channel dimension.

    • +
    • output_channel_idx (int, optional) – Desired output dimension index of the images’ channel dimension.

    • +
    +
    +
    +
    + +
    +
    +class src.decision_region_generation.vicinal_distribution.plane_dataset(img1, img2, img3, steps=5, expand=0, shape='rectangle')
    +

    Generates a vicinal distribution from the input images.

    +
    +
    Parameters:
    +
      +
    • img1 (numpy.array) – Images from which to construct the vicinal distribution, should all be the same shape and data type.

    • +
    • img2 (numpy.array) – Images from which to construct the vicinal distribution, should all be the same shape and data type.

    • +
    • img3 (numpy.array) – Images from which to construct the vicinal distribution, should all be the same shape and data type.

    • +
    • steps (int, optional) – Number of steps to take between images, affects the number of virtual images generated.

    • +
    • expand (float, optional) – How far beyond the original images to expand the plane; only works with shape=’rectangle’

    • +
    • shape (str, optional) – Shape of the region of plane to generate samples for.

    • +
    +
    +
    +
    + +
    +
    +

    Composition Analysis

    +
    +
    +src.composition_analysis.get_compositions(filepath: str, tasks: dict, output_function: str | None = None, thresholds: list = [0.5], aggregate: str | None = None) DataFrame
    +

    Gets the compositions of all decision regions in a decision region file.

    +
    +
    Parameters:
    +
      +
    • filepath (str) – File path of decision region hdf5 file.

    • +
    • tasks (dict) – Model classification tasks.

    • +
    • output_function (str | None) – Function to be applied to model output scores.

    • +
    • thresholds (list) – Thresholds for each task.

    • +
    • aggregate (str | None) – How to aggregate the compositions.

    • +
    +
    +
    Returns:
    +

    Dataframe of decision region compositions.

    +
    +
    Return type:
    +

    pandas.DataFrame

    +
    +
    +
    + +
    +
    +src.composition_analysis.plot_decision_regions(filepath: str, save_loc: str, n_per_group: int | None = None, threshold: int = 1, palette: str = 'Set2')
    +

    Plot decision regions from decision region files.

    +

    Notes

    +

    TODO: +- threshold support: legend + palette

    +
    +
    Parameters:
    +
      +
    • filepath (str) – File path to decision region hdf5 file.

    • +
    • save_loc (str) – Save location.

    • +
    • n_per_group (int | None) – Number per group to plot; if None, plots all.

    • +
    • threshold (int) – Ouput score threshold; if None, does not threshold

    • +
    • palette (str) –

    • +
    +
    +
    Returns:
    +

    Plot figure.

    +
    +
    Return type:
    +

    matplotlib.pyplot.figure

    +
    +
    +
    + +
    +
    +src.composition_analysis.plot_figures(df, plot: str, save_loc: str, tasks: dict, palette: str | dict | list = 'Set2', aggregate: str = 'group', show_percent: bool = True, errorbar: bool = True, show: bool = False, save: bool = True, save_dpi: int = 800, filepath: str | None = None, n_per_group: int | None = None, threshold: float | None = None, output_formats=['.svg'])
    +

    Plots composition, performance or region figures.

    +
    +
    Parameters:
    +
      +
    • df (pandas.DataFrame) – DataFrame containing decision region composition infromation; used with plot = composition or performance.

    • +
    • plot (str) – Type of plot to create, options: composition, performance, region.

    • +
    • save_loc (str) – Folder in which to save plot images.

    • +
    • tasks (dict) – Model classification tasks.

    • +
    • palette (str | dict | list) – Color palette to use. (TODO: expand description)

    • +
    • aggregate (str) – Method by which to aggregate results, must match the aggregation used during composition analysis.

    • +
    • show_percent (bool) – If True, exact percent values will be included on composition/performance plots.

    • +
    • errorbar (bool) – If True, error bars will be included on composition/performance plots.

    • +
    • show (bool) – If True, will display plots.

    • +
    • save (bool) – If True, will save plot files to save_loc.

    • +
    • save_dpi (int) – DPI of saved output image(s).

    • +
    • filepath (str | None) – File path of decision region hdf5 file; only used for region figures.

    • +
    • n_per_group (int | None) – Number of triplets per group to create plots for; only used for region figures.

    • +
    • threshold (float | None) – The threshold to be applied to convert output scores to a binary classification, if None, no threshold is applied; only used for region figures.

    • +
    • output_formats – The image formats in which to save the output figures.

    • +
    +
    +
    +
    + +
    +
    +src.composition_analysis.save_compositions(compositions: DataFrame, save_loc: str, overwrite: bool = False, aggregate: str | None = None)
    +

    Saves the compositions analysis in a csv file

    +
    +
    Parameters:
    +
      +
    • compositions (DataFrame) – Decision region compositions, as output by get_compositions().

    • +
    • save_loc (str) – Save location

    • +
    • overwrite (bool) – Whether or not to overwriting existing files.

    • +
    • aggregate (str | None) – The aggregation method; used as part of naming convention.

    • +
    +
    +
    +
    + +
    +
    +src.composition_analysis.set_params(plot: str)
    +

    Sets figure style parameters.

    +
    +
    Parameters:
    +

    plot (str) – The type of plot being created (composition, performance or region).

    +
    +
    +
    + +
    + + +
    + +
    +
    + +
    +
    + + + + + + + \ No newline at end of file